提交 1c9fcbf4 编写于 作者: zlt2000's avatar zlt2000

增加全功能spring-cloud-gateway,优化部分公共模块适配webFlux

上级 eecaf5bb
......@@ -109,6 +109,7 @@ central-platform -- 父项目,公共依赖
├─search-server -- 搜索中心服务端[7100]
│─zlt-commons -- 通用工具一级工程
├─zlt-auth-client-spring-boot-starter -- 封装spring security client端的通用操作逻辑
├─zlt-common-core -- 封装通用操作逻辑
├─zlt-common-spring-boot-starter -- 封装通用操作逻辑
├─zlt-db-spring-boot-starter -- 封装数据库通用操作逻辑
├─zlt-log-spring-boot-starter -- 封装log通用操作逻辑
......@@ -119,6 +120,7 @@ central-platform -- 父项目,公共依赖
├─zlt-config -- 配置中心
├─zlt-doc -- 项目文档
├─zlt-gateway -- api网关一级工程
├─sc-gateway -- spring-cloud-gateway[9900]
├─zuul-gateway -- netflix-zuul[9900]
├─zlt-job -- 分布式任务调度一级工程
├─job-admin -- 任务管理器[8081]
......
......@@ -46,7 +46,6 @@
<spring-social-security.version>1.1.6.RELEASE</spring-social-security.version>
<commons-io.version>2.6</commons-io.version>
<servlet-api.version>4.0.1</servlet-api.version>
<!--<platform-bom>Cairo-SR3</platform-bom>-->
<spring-cloud-alibaba-dependencies.version>2.1.0.RELEASE</spring-cloud-alibaba-dependencies.version>
<spring-boot-dependencies.version>2.1.8.RELEASE</spring-boot-dependencies.version>
<spring-cloud-dependencies.version>Greenwich.SR3</spring-cloud-dependencies.version>
......@@ -140,7 +139,11 @@
<artifactId>zlt-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.zlt</groupId>
<artifactId>zlt-common-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
......@@ -343,13 +346,6 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<!--<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>${platform-bom}</version>
<type>pom</type>
<scope>import</scope>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
......@@ -441,6 +437,8 @@
<developer>
<name>LeTao Zhu</name>
<email>zltdiablo@163.com</email>
<organizationUrl>https://github.com/zlt2000</organizationUrl>
<url>https://blog.csdn.net/zlt2000</url>
</developer>
</developers>
</project>
\ No newline at end of file
......@@ -35,6 +35,10 @@
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
......
......@@ -14,10 +14,18 @@
<groupId>com.zlt</groupId>
<artifactId>zlt-config</artifactId>
</dependency>
<dependency>
<groupId>com.zlt</groupId>
<artifactId>zlt-common-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.zlt</groupId>
<artifactId>search-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
......
......@@ -44,6 +44,10 @@
<artifactId>search-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
......@@ -67,6 +71,11 @@
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
</dependency>
</dependencies>
<build>
......
......@@ -19,5 +19,6 @@
<module>zlt-ribbon-spring-boot-starter</module>
<module>zlt-auth-client-spring-boot-starter</module>
<module>zlt-sentinel-spring-boot-starter</module>
<module>zlt-common-core</module>
</modules>
</project>
\ No newline at end of file
......@@ -12,10 +12,6 @@
<artifactId>zlt-auth-client-spring-boot-starter</artifactId>
<description>认证客户端通用组件</description>
<dependencies>
<dependency>
<groupId>com.zlt</groupId>
<artifactId>zlt-common-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
......@@ -23,6 +19,7 @@
<dependency>
<groupId>com.zlt</groupId>
<artifactId>zlt-redis-spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
......
......@@ -6,6 +6,7 @@ import org.springframework.context.annotation.Import;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
......@@ -52,12 +53,14 @@ public class DefaultResourceServerConf extends ResourceServerConfigurerAdapter {
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest();
setAuthenticate(authorizedUrl);
//允许使用iframe 嵌套,避免swagger-ui 不被加载的问题
http.headers()
.frameOptions()
.disable()
.and()
.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.httpBasic().disable()
.headers()
.frameOptions().disable()
.and()
.csrf().disable();
}
/**
......
package com.central.oauth2.common.config;
import com.central.oauth2.common.properties.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* @author zlt
* @date 2019/10/7
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityPropertiesConfig {
}
package com.central.oauth2.common.service;
import org.springframework.security.core.Authentication;
import javax.servlet.http.HttpServletRequest;
/**
* 请求权限判断service
*
* @author zlt
* @date 2018/10/28
*/
public interface IPermissionService {
/**
* 判断请求是否有权限
*
* @param authentication 认证信息
* @return 是否有权限
*/
boolean hasPermission(HttpServletRequest request, Authentication authentication);
}
......@@ -6,7 +6,6 @@ import com.central.common.constant.CommonConstant;
import com.central.common.context.TenantContextHolder;
import com.central.common.model.SysMenu;
import com.central.oauth2.common.properties.SecurityProperties;
import com.central.oauth2.common.service.IPermissionService;
import com.central.oauth2.common.util.AuthUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
......@@ -18,7 +17,6 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.util.AntPathMatcher;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.stream.Collectors;
......@@ -29,7 +27,7 @@ import java.util.stream.Collectors;
* @date 2018/10/28
*/
@Slf4j
public abstract class DefaultPermissionServiceImpl implements IPermissionService {
public abstract class DefaultPermissionServiceImpl {
@Autowired
private SecurityProperties securityProperties;
......@@ -43,10 +41,9 @@ public abstract class DefaultPermissionServiceImpl implements IPermissionService
*/
public abstract List<SysMenu> findMenuByRoleCodes(String roleCodes);
@Override
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
public boolean hasPermission(Authentication authentication, String requestMethod, String requestURI) {
// 前端跨域OPTIONS请求预检放行 也可通过前端配置代理实现
if (HttpMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod())) {
if (HttpMethod.OPTIONS.name().equalsIgnoreCase(requestMethod)) {
return true;
}
if (!(authentication instanceof AnonymousAuthenticationToken)) {
......@@ -68,7 +65,7 @@ public abstract class DefaultPermissionServiceImpl implements IPermissionService
//判断不进行url权限认证的api,所有已登录用户都能访问的url
for (String path : securityProperties.getAuth().getUrlPermission().getIgnoreUrls()) {
if (antPathMatcher.match(path, request.getRequestURI())) {
if (antPathMatcher.match(path, requestURI)) {
return true;
}
}
......@@ -86,9 +83,9 @@ public abstract class DefaultPermissionServiceImpl implements IPermissionService
String roleCodes = grantedAuthorityList.stream().map(SimpleGrantedAuthority::getAuthority).collect(Collectors.joining(", "));
List<SysMenu> menuList = findMenuByRoleCodes(roleCodes);
for (SysMenu menu : menuList) {
if (StringUtils.isNotEmpty(menu.getUrl()) && antPathMatcher.match(menu.getUrl(), request.getRequestURI())) {
if (StringUtils.isNotEmpty(menu.getUrl()) && antPathMatcher.match(menu.getUrl(), requestURI)) {
if (StrUtil.isNotEmpty(menu.getPathMethod())) {
return request.getMethod().equalsIgnoreCase(menu.getPathMethod());
return requestMethod.equalsIgnoreCase(menu.getPathMethod());
} else {
return true;
}
......
......@@ -52,7 +52,7 @@ public class AuthUtils {
Enumeration<String> headers = request.getHeaders(CommonConstant.TOKEN_HEADER);
while (headers.hasMoreElements()) {
String value = headers.nextElement();
if ((value.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase()))) {
if ((value.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE))) {
String authHeaderValue = value.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim();
int commaIndex = authHeaderValue.indexOf(',');
if (commaIndex > 0) {
......
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.central.oauth2.common.config.SecurityPropertiesConfig
\ No newline at end of file
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.zlt</groupId>
<artifactId>zlt-commons</artifactId>
<version>2.7.2</version>
</parent>
<artifactId>zlt-common-core</artifactId>
<description>公共通用组件</description>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.zlt</groupId>
<artifactId>zlt-log-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</dependency>
<!-- easypoi -->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>banner</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package com.central.common.annotation;
import java.lang.annotation.*;
/**
* 请求的方法参数SysUser上添加该注解,则注入当前登录人信息
* 例1:public void test(@LoginUser SysUser user) //只有username 和 roles
* 例2:public void test(@LoginUser(isFull = true) SysUser user) //能获取SysUser对象的所有信息
*
* @author zlt
* @date 2018/7/24 16:44
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginUser {
/**
* 是否查询SysUser对象所有信息,true则通过rpc接口查询
*/
boolean isFull() default false;
}
package com.central.common.annotation;
import java.lang.annotation.*;
/**
* 请求的方法参数SysUser上添加该注解,则注入当前登录人信息
* 例1:public void test(@LoginUser SysUser user) //只有username 和 roles
* 例2:public void test(@LoginUser(isFull = true) SysUser user) //能获取SysUser对象的所有信息
*
* @author zlt
* @date 2018/7/24 16:44
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginUser {
/**
* 是否查询SysUser对象所有信息,true则通过rpc接口查询
*/
boolean isFull() default false;
}
package com.central.common.config;
import com.central.common.utils.CustomThreadPoolTaskExecutor;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @author zlt
* @date 2018/12/13
*/
@Setter
@Getter
@EnableAsync(proxyTargetClass = true)
public class DefaultAsycTaskConfig {
/**
* 线程池维护线程的最小数量.
*/
@Value("${asyc-task.corePoolSize:10}")
private int corePoolSize;
/**
* 线程池维护线程的最大数量
*/
@Value("${asyc-task.maxPoolSize:200}")
private int maxPoolSize;
/**
* 队列最大长度
*/
@Value("${asyc-task.queueCapacity:10}")
private int queueCapacity;
/**
* 线程池前缀
*/
@Value("${asyc-task.threadNamePrefix:ZltExecutor-}")
private String threadNamePrefix;
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new CustomThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix(threadNamePrefix);
/*
rejection-policy:当pool已经达到max size的时候,如何处理新任务
CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
package com.central.common.config;
import com.central.common.utils.CustomThreadPoolTaskExecutor;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @author zlt
* @date 2018/12/13
*/
@Setter
@Getter
@EnableAsync(proxyTargetClass = true)
public class DefaultAsycTaskConfig {
/**
* 线程池维护线程的最小数量.
*/
@Value("${asyc-task.corePoolSize:10}")
private int corePoolSize;
/**
* 线程池维护线程的最大数量
*/
@Value("${asyc-task.maxPoolSize:200}")
private int maxPoolSize;
/**
* 队列最大长度
*/
@Value("${asyc-task.queueCapacity:10}")
private int queueCapacity;
/**
* 线程池前缀
*/
@Value("${asyc-task.threadNamePrefix:ZltExecutor-}")
private String threadNamePrefix;
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new CustomThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix(threadNamePrefix);
/*
rejection-policy:当pool已经达到max size的时候,如何处理新任务
CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
package com.central.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author zlt
* 密码工具类
*/
public class DefaultPasswordConfig {
/**
* 装配BCryptPasswordEncoder用户密码的匹配
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
package com.central.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author zlt
* 密码工具类
*/
public class DefaultPasswordConfig {
/**
* 装配BCryptPasswordEncoder用户密码的匹配
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
package com.central.common.config;
import com.central.common.feign.UserService;
import com.central.common.resolver.ClientArgumentResolver;
import com.central.common.resolver.TokenArgumentResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* 公共配置类, 一些公共工具配置
*
* @author zlt
* @date 2018/8/25
*/
public class LoginArgResolverConfig implements WebMvcConfigurer {
@Lazy
@Autowired
private UserService userService;
/**
* Token参数解析
*
* @param argumentResolvers 解析类
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
//注入用户信息
argumentResolvers.add(new TokenArgumentResolver(userService));
//注入应用信息
argumentResolvers.add(new ClientArgumentResolver());
}
}
package com.central.common.config;
import com.central.common.feign.UserService;
import com.central.common.resolver.ClientArgumentResolver;
import com.central.common.resolver.TokenArgumentResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* 公共配置类, 一些公共工具配置
*
* @author zlt
* @date 2018/8/25
*/
public class LoginArgResolverConfig implements WebMvcConfigurer {
@Lazy
@Autowired
private UserService userService;
/**
* Token参数解析
*
* @param argumentResolvers 解析类
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
//注入用户信息
argumentResolvers.add(new TokenArgumentResolver(userService));
//注入应用信息
argumentResolvers.add(new ClientArgumentResolver());
}
}
package com.central.common.constant;
/**
* 全局公共常量
*
* @author zlt
* @date 2018/10/29
*/
public interface CommonConstant {
/**
* 项目版本号(banner使用)
*/
String PROJECT_VERSION = "2.7.2";
/**
* token请求头名称
*/
String TOKEN_HEADER = "Authorization";
/**
* The access token issued by the authorization server. This value is REQUIRED.
*/
String ACCESS_TOKEN = "access_token";
String BEARER_TYPE = "Bearer";
/**
* 标签 header key
*/
String HEADER_LABEL = "x-label";
/**
* 标签 header 分隔符
*/
String HEADER_LABEL_SPLIT = ",";
/**
* 标签或 名称
*/
String LABEL_OR = "labelOr";
/**
* 标签且 名称
*/
String LABEL_AND = "labelAnd";
/**
* 权重key
*/
String WEIGHT_KEY = "weight";
/**
* 删除
*/
String STATUS_DEL = "1";
/**
* 正常
*/
String STATUS_NORMAL = "0";
/**
* 锁定
*/
String STATUS_LOCK = "9";
/**
* 目录
*/
Integer CATALOG = -1;
/**
* 菜单
*/
Integer MENU = 1;
/**
* 权限
*/
Integer PERMISSION = 2;
/**
* 删除标记
*/
String DEL_FLAG = "is_del";
/**
* 超级管理员用户名
*/
String ADMIN_USER_NAME = "admin";
/**
* 公共日期格式
*/
String MONTH_FORMAT = "yyyy-MM";
String DATE_FORMAT = "yyyy-MM-dd";
String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
String SIMPLE_MONTH_FORMAT = "yyyyMM";
String SIMPLE_DATE_FORMAT = "yyyyMMdd";
String SIMPLE_DATETIME_FORMAT = "yyyyMMddHHmmss";
String DEF_USER_PASSWORD = "123456";
String LOCK_KEY_PREFIX = "LOCK_KEY:";
/**
* 租户id参数
*/
String TENANT_ID_PARAM = "tenantId";
/**
* 日志链路追踪id信息头
*/
String TRACE_ID_HEADER = "x-traceId-header";
/**
* 日志链路追踪id日志标志
*/
String LOG_TRACE_ID = "traceId";
/**
* 负载均衡策略-版本号 信息头
*/
String Z_L_T_VERSION = "z-l-t-version";
/**
* 注册中心元数据 版本号
*/
String METADATA_VERSION = "version";
}
package com.central.common.constant;
/**
* 全局公共常量
*
* @author zlt
* @date 2018/10/29
*/
public interface CommonConstant {
/**
* 项目版本号(banner使用)
*/
String PROJECT_VERSION = "2.7.2";
/**
* token请求头名称
*/
String TOKEN_HEADER = "Authorization";
/**
* The access token issued by the authorization server. This value is REQUIRED.
*/
String ACCESS_TOKEN = "access_token";
String BEARER_TYPE = "Bearer";
/**
* 标签 header key
*/
String HEADER_LABEL = "x-label";
/**
* 标签 header 分隔符
*/
String HEADER_LABEL_SPLIT = ",";
/**
* 标签或 名称
*/
String LABEL_OR = "labelOr";
/**
* 标签且 名称
*/
String LABEL_AND = "labelAnd";
/**
* 权重key
*/
String WEIGHT_KEY = "weight";
/**
* 删除
*/
String STATUS_DEL = "1";
/**
* 正常
*/
String STATUS_NORMAL = "0";
/**
* 锁定
*/
String STATUS_LOCK = "9";
/**
* 目录
*/
Integer CATALOG = -1;
/**
* 菜单
*/
Integer MENU = 1;
/**
* 权限
*/
Integer PERMISSION = 2;
/**
* 删除标记
*/
String DEL_FLAG = "is_del";
/**
* 超级管理员用户名
*/
String ADMIN_USER_NAME = "admin";
/**
* 公共日期格式
*/
String MONTH_FORMAT = "yyyy-MM";
String DATE_FORMAT = "yyyy-MM-dd";
String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
String SIMPLE_MONTH_FORMAT = "yyyyMM";
String SIMPLE_DATE_FORMAT = "yyyyMMdd";
String SIMPLE_DATETIME_FORMAT = "yyyyMMddHHmmss";
String DEF_USER_PASSWORD = "123456";
String LOCK_KEY_PREFIX = "LOCK_KEY:";
/**
* 租户id参数
*/
String TENANT_ID_PARAM = "tenantId";
/**
* 日志链路追踪id信息头
*/
String TRACE_ID_HEADER = "x-traceId-header";
/**
* 日志链路追踪id日志标志
*/
String LOG_TRACE_ID = "traceId";
/**
* 负载均衡策略-版本号 信息头
*/
String Z_L_T_VERSION = "z-l-t-version";
/**
* 注册中心元数据 版本号
*/
String METADATA_VERSION = "version";
}
package com.central.common.constant;
/**
* Security 权限常量
*
* @author zlt
*/
public interface SecurityConstants {
/**
* 用户信息分隔符
*/
String USER_SPLIT = ":";
/**
* 用户信息头
*/
String USER_HEADER = "x-user-header";
/**
* 用户id信息头
*/
String USER_ID_HEADER = "x-userid-header";
/**
* 角色信息头
*/
String ROLE_HEADER = "x-role-header";
/**
* 租户信息头(应用)
*/
String TENANT_HEADER = "x-tenant-header";
/**
* 基础角色
*/
String BASE_ROLE = "ROLE_USER";
/**
* 授权码模式
*/
String AUTHORIZATION_CODE = "authorization_code";
/**
* 密码模式
*/
String PASSWORD = "password";
/**
* 刷新token
*/
String REFRESH_TOKEN = "refresh_token";
/**
* oauth token
*/
String OAUTH_TOKEN_URL = "/oauth/token";
/**
* 默认的处理验证码的url前缀
*/
String DEFAULT_VALIDATE_CODE_URL_PREFIX = "/validata/code";
/**
* 手机号的处理验证码的url前缀
*/
String MOBILE_VALIDATE_CODE_URL_PREFIX = "/validata/smsCode";
/**
* 默认生成图形验证码宽度
*/
String DEFAULT_IMAGE_WIDTH = "100";
/**
* 默认生成图像验证码高度
*/
String DEFAULT_IMAGE_HEIGHT = "35";
/**
* 默认生成图形验证码长度
*/
String DEFAULT_IMAGE_LENGTH = "4";
/**
* 默认生成图形验证码过期时间
*/
int DEFAULT_IMAGE_EXPIRE = 60;
/**
* 边框颜色,合法值: r,g,b (and optional alpha) 或者 white,black,blue.
*/
String DEFAULT_COLOR_FONT = "blue";
/**
* 图片边框
*/
String DEFAULT_IMAGE_BORDER = "no";
/**
* 默认图片间隔
*/
String DEFAULT_CHAR_SPACE = "5";
/**
* 默认保存code的前缀
*/
String DEFAULT_CODE_KEY = "DEFAULT_CODE_KEY";
/**
* 验证码文字大小
*/
String DEFAULT_IMAGE_FONT_SIZE = "30";
/**
* zlt公共前缀
*/
String ZLT_PREFIX = "zlt:";
/**
* 缓存client的redis key,这里是hash结构存储
*/
String CACHE_CLIENT_KEY = "oauth_client_details";
/**
* OAUTH模式登录处理地址
*/
String OAUTH_LOGIN_PRO_URL = "/user/login";
/**
* PASSWORD模式登录处理地址
*/
String PASSWORD_LOGIN_PRO_URL = "/oauth/user/token";
/**
* 获取授权码地址
*/
String AUTH_CODE_URL = "/oauth/authorize";
/**
* 登录页面
*/
String LOGIN_PAGE = "/login.html";
/**
* 默认的OPENID登录请求处理url
*/
String OPENID_TOKEN_URL = "/oauth/openId/token";
/**
* 手机登录URL
*/
String MOBILE_TOKEN_URL = "/oauth/mobile/token";
/**
* 登出URL
*/
String LOGOUT_URL = "/oauth/remove/token";
/**
* 默认token过期时间(1小时)
*/
Integer ACCESS_TOKEN_VALIDITY_SECONDS = 60 * 60;
/**
* redis中授权token对应的key
*/
String REDIS_TOKEN_AUTH = "auth:";
/**
* redis中应用对应的token集合的key
*/
String REDIS_CLIENT_ID_TO_ACCESS = "client_id_to_access:";
/**
* redis中用户名对应的token集合的key
*/
String REDIS_UNAME_TO_ACCESS = "uname_to_access:";
/**
* rsa公钥
*/
String RSA_PUBLIC_KEY = "pubkey.txt";
}
package com.central.common.constant;
/**
* Security 权限常量
*
* @author zlt
*/
public interface SecurityConstants {
/**
* 用户信息分隔符
*/
String USER_SPLIT = ":";
/**
* 用户信息头
*/
String USER_HEADER = "x-user-header";
/**
* 用户id信息头
*/
String USER_ID_HEADER = "x-userid-header";
/**
* 角色信息头
*/
String ROLE_HEADER = "x-role-header";
/**
* 租户信息头(应用)
*/
String TENANT_HEADER = "x-tenant-header";
/**
* 基础角色
*/
String BASE_ROLE = "ROLE_USER";
/**
* 授权码模式
*/
String AUTHORIZATION_CODE = "authorization_code";
/**
* 密码模式
*/
String PASSWORD = "password";
/**
* 刷新token
*/
String REFRESH_TOKEN = "refresh_token";
/**
* oauth token
*/
String OAUTH_TOKEN_URL = "/oauth/token";
/**
* 默认的处理验证码的url前缀
*/
String DEFAULT_VALIDATE_CODE_URL_PREFIX = "/validata/code";
/**
* 手机号的处理验证码的url前缀
*/
String MOBILE_VALIDATE_CODE_URL_PREFIX = "/validata/smsCode";
/**
* 默认生成图形验证码宽度
*/
String DEFAULT_IMAGE_WIDTH = "100";
/**
* 默认生成图像验证码高度
*/
String DEFAULT_IMAGE_HEIGHT = "35";
/**
* 默认生成图形验证码长度
*/
String DEFAULT_IMAGE_LENGTH = "4";
/**
* 默认生成图形验证码过期时间
*/
int DEFAULT_IMAGE_EXPIRE = 60;
/**
* 边框颜色,合法值: r,g,b (and optional alpha) 或者 white,black,blue.
*/
String DEFAULT_COLOR_FONT = "blue";
/**
* 图片边框
*/
String DEFAULT_IMAGE_BORDER = "no";
/**
* 默认图片间隔
*/
String DEFAULT_CHAR_SPACE = "5";
/**
* 默认保存code的前缀
*/
String DEFAULT_CODE_KEY = "DEFAULT_CODE_KEY";
/**
* 验证码文字大小
*/
String DEFAULT_IMAGE_FONT_SIZE = "30";
/**
* zlt公共前缀
*/
String ZLT_PREFIX = "zlt:";
/**
* 缓存client的redis key,这里是hash结构存储
*/
String CACHE_CLIENT_KEY = "oauth_client_details";
/**
* OAUTH模式登录处理地址
*/
String OAUTH_LOGIN_PRO_URL = "/user/login";
/**
* PASSWORD模式登录处理地址
*/
String PASSWORD_LOGIN_PRO_URL = "/oauth/user/token";
/**
* 获取授权码地址
*/
String AUTH_CODE_URL = "/oauth/authorize";
/**
* 登录页面
*/
String LOGIN_PAGE = "/login.html";
/**
* 默认的OPENID登录请求处理url
*/
String OPENID_TOKEN_URL = "/oauth/openId/token";
/**
* 手机登录URL
*/
String MOBILE_TOKEN_URL = "/oauth/mobile/token";
/**
* 登出URL
*/
String LOGOUT_URL = "/oauth/remove/token";
/**
* 默认token过期时间(1小时)
*/
Integer ACCESS_TOKEN_VALIDITY_SECONDS = 60 * 60;
/**
* redis中授权token对应的key
*/
String REDIS_TOKEN_AUTH = "auth:";
/**
* redis中应用对应的token集合的key
*/
String REDIS_CLIENT_ID_TO_ACCESS = "client_id_to_access:";
/**
* redis中用户名对应的token集合的key
*/
String REDIS_UNAME_TO_ACCESS = "uname_to_access:";
/**
* rsa公钥
*/
String RSA_PUBLIC_KEY = "pubkey.txt";
}
package com.central.common.constant;
/**
* 服务名称常量
*
* @author zlt
* @date 2018/7/27 13:50
*/
public interface ServiceNameConstants {
/**
* 用户权限服务
*/
String USER_SERVICE = "user-center";
/**
* 搜索中心服务
*/
String SEARCH_SERVICE = "search-center";
}
package com.central.common.constant;
/**
* 服务名称常量
*
* @author zlt
* @date 2018/7/27 13:50
*/
public interface ServiceNameConstants {
/**
* 用户权限服务
*/
String USER_SERVICE = "user-center";
/**
* 搜索中心服务
*/
String SEARCH_SERVICE = "search-center";
}
package com.central.common.exception;
/**
* 业务异常
*
* @author zlt
*/
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 6610083281801529147L;
public BusinessException(String message) {
super(message);
}
}
package com.central.common.exception;
/**
* 业务异常
*
* @author zlt
*/
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 6610083281801529147L;
public BusinessException(String message) {
super(message);
}
}
package com.central.common.exception;
import com.central.common.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;
import java.sql.SQLException;
/**
* 异常通用处理
*
* @author zlt
*/
@ResponseBody
@Slf4j
public class DefaultExceptionAdvice {
/**
* IllegalArgumentException异常处理返回json
* 返回状态码:400
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({IllegalArgumentException.class})
public Result badRequestException(IllegalArgumentException e) {
return defHandler("参数解析失败", e);
}
/**
* AccessDeniedException异常处理返回json
* 返回状态码:403
*/
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler({AccessDeniedException.class})
public Result badMethodExpressException(AccessDeniedException e) {
return defHandler("没有权限请求当前方法", e);
}
/**
* 返回状态码:405
*/
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
@ExceptionHandler({HttpRequestMethodNotSupportedException.class})
public Result handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
return defHandler("不支持当前请求方法", e);
}
/**
* 返回状态码:415
*/
@ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
@ExceptionHandler({HttpMediaTypeNotSupportedException.class})
public Result handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
return defHandler("不支持当前媒体类型", e);
}
/**
* SQLException sql异常处理
* 返回状态码:500
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler({SQLException.class})
public Result handleSQLException(SQLException e) {
return defHandler("服务运行SQLException异常", e);
}
/**
* BusinessException 业务异常处理
* 返回状态码:500
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(BusinessException.class)
public Result handleException(BusinessException e) {
return defHandler("业务异常", e);
}
/**
* IdempotencyException 幂等性异常
* 返回状态码:200
*/
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(IdempotencyException.class)
public Result handleException(IdempotencyException e) {
return Result.failed(e.getMessage());
}
/**
* 所有异常统一处理
* 返回状态码:500
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
return defHandler("未知异常", e);
}
private Result defHandler(String msg, Exception e) {
log.error(msg, e);
return Result.failed(msg);
}
}
package com.central.common.exception;
import com.central.common.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;
import java.sql.SQLException;
/**
* 异常通用处理
*
* @author zlt
*/
@ResponseBody
@Slf4j
public class DefaultExceptionAdvice {
/**
* IllegalArgumentException异常处理返回json
* 返回状态码:400
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({IllegalArgumentException.class})
public Result badRequestException(IllegalArgumentException e) {
return defHandler("参数解析失败", e);
}
/**
* AccessDeniedException异常处理返回json
* 返回状态码:403
*/
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler({AccessDeniedException.class})
public Result badMethodExpressException(AccessDeniedException e) {
return defHandler("没有权限请求当前方法", e);
}
/**
* 返回状态码:405
*/
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
@ExceptionHandler({HttpRequestMethodNotSupportedException.class})
public Result handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
return defHandler("不支持当前请求方法", e);
}
/**
* 返回状态码:415
*/
@ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
@ExceptionHandler({HttpMediaTypeNotSupportedException.class})
public Result handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
return defHandler("不支持当前媒体类型", e);
}
/**
* SQLException sql异常处理
* 返回状态码:500
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler({SQLException.class})
public Result handleSQLException(SQLException e) {
return defHandler("服务运行SQLException异常", e);
}
/**
* BusinessException 业务异常处理
* 返回状态码:500
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(BusinessException.class)
public Result handleException(BusinessException e) {
return defHandler("业务异常", e);
}
/**
* IdempotencyException 幂等性异常
* 返回状态码:200
*/
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(IdempotencyException.class)
public Result handleException(IdempotencyException e) {
return Result.failed(e.getMessage());
}
/**
* 所有异常统一处理
* 返回状态码:500
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
return defHandler("未知异常", e);
}
private Result defHandler(String msg, Exception e) {
log.error(msg, e);
return Result.failed(msg);
}
}
package com.central.common.exception;
/**
* 幂等性异常
*
* @author zlt
*/
public class IdempotencyException extends RuntimeException {
private static final long serialVersionUID = 6610083281801529147L;
public IdempotencyException(String message) {
super(message);
}
}
package com.central.common.exception;
/**
* 幂等性异常
*
* @author zlt
*/
public class IdempotencyException extends RuntimeException {
private static final long serialVersionUID = 6610083281801529147L;
public IdempotencyException(String message) {
super(message);
}
}
package com.central.common.exception;
/**
* 分布式锁异常
*
* @author zlt
*/
public class LockException extends RuntimeException {
private static final long serialVersionUID = 6610083281801529147L;
public LockException(String message) {
super(message);
}
}
package com.central.common.exception;
/**
* 分布式锁异常
*
* @author zlt
*/
public class LockException extends RuntimeException {
private static final long serialVersionUID = 6610083281801529147L;
public LockException(String message) {
super(message);
}
}
package com.central.common.feign;
import com.central.common.constant.ServiceNameConstants;
import com.central.common.feign.fallback.UserServiceFallbackFactory;
import com.central.common.model.LoginAppUser;
import com.central.common.model.SysUser;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @author zlt
*/
@FeignClient(name = ServiceNameConstants.USER_SERVICE, fallbackFactory = UserServiceFallbackFactory.class, decode404 = true)
public interface UserService {
/**
* feign rpc访问远程/users/{username}接口
* 查询用户实体对象SysUser
*
* @param username
* @return
*/
@GetMapping(value = "/users/name/{username}")
SysUser selectByUsername(@PathVariable("username") String username);
/**
* feign rpc访问远程/users-anon/login接口
*
* @param username
* @return
*/
@GetMapping(value = "/users-anon/login", params = "username")
LoginAppUser findByUsername(@RequestParam("username") String username);
/**
* 通过手机号查询用户、角色信息
*
* @param mobile 手机号
*/
@GetMapping(value = "/users-anon/mobile", params = "mobile")
LoginAppUser findByMobile(@RequestParam("mobile") String mobile);
/**
* 根据OpenId查询用户信息
*
* @param openId openId
*/
@GetMapping(value = "/users-anon/openId", params = "openId")
LoginAppUser findByOpenId(@RequestParam("openId") String openId);
}
package com.central.common.feign;
import com.central.common.constant.ServiceNameConstants;
import com.central.common.feign.fallback.UserServiceFallbackFactory;
import com.central.common.model.LoginAppUser;
import com.central.common.model.SysUser;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @author zlt
*/
@FeignClient(name = ServiceNameConstants.USER_SERVICE, fallbackFactory = UserServiceFallbackFactory.class, decode404 = true)
public interface UserService {
/**
* feign rpc访问远程/users/{username}接口
* 查询用户实体对象SysUser
*
* @param username
* @return
*/
@GetMapping(value = "/users/name/{username}")
SysUser selectByUsername(@PathVariable("username") String username);
/**
* feign rpc访问远程/users-anon/login接口
*
* @param username
* @return
*/
@GetMapping(value = "/users-anon/login", params = "username")
LoginAppUser findByUsername(@RequestParam("username") String username);
/**
* 通过手机号查询用户、角色信息
*
* @param mobile 手机号
*/
@GetMapping(value = "/users-anon/mobile", params = "mobile")
LoginAppUser findByMobile(@RequestParam("mobile") String mobile);
/**
* 根据OpenId查询用户信息
*
* @param openId openId
*/
@GetMapping(value = "/users-anon/openId", params = "openId")
LoginAppUser findByOpenId(@RequestParam("openId") String openId);
}
package com.central.common.feign.fallback;
import com.central.common.feign.UserService;
import com.central.common.model.LoginAppUser;
import com.central.common.model.SysUser;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* userService降级工场
*
* @author zlt
* @date 2019/1/18
*/
@Slf4j
@Component
public class UserServiceFallbackFactory implements FallbackFactory<UserService> {
@Override
public UserService create(Throwable throwable) {
return new UserService() {
@Override
public SysUser selectByUsername(String username) {
log.error("通过用户名查询用户异常:{}", username, throwable);
return new SysUser();
}
@Override
public LoginAppUser findByUsername(String username) {
log.error("通过用户名查询用户异常:{}", username, throwable);
return new LoginAppUser();
}
@Override
public LoginAppUser findByMobile(String mobile) {
log.error("通过手机号查询用户异常:{}", mobile, throwable);
return new LoginAppUser();
}
@Override
public LoginAppUser findByOpenId(String openId) {
log.error("通过openId查询用户异常:{}", openId, throwable);
return new LoginAppUser();
}
};
}
}
package com.central.common.feign.fallback;
import com.central.common.feign.UserService;
import com.central.common.model.LoginAppUser;
import com.central.common.model.SysUser;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* userService降级工场
*
* @author zlt
* @date 2019/1/18
*/
@Slf4j
@Component
public class UserServiceFallbackFactory implements FallbackFactory<UserService> {
@Override
public UserService create(Throwable throwable) {
return new UserService() {
@Override
public SysUser selectByUsername(String username) {
log.error("通过用户名查询用户异常:{}", username, throwable);
return new SysUser();
}
@Override
public LoginAppUser findByUsername(String username) {
log.error("通过用户名查询用户异常:{}", username, throwable);
return new LoginAppUser();
}
@Override
public LoginAppUser findByMobile(String mobile) {
log.error("通过手机号查询用户异常:{}", mobile, throwable);
return new LoginAppUser();
}
@Override
public LoginAppUser findByOpenId(String openId) {
log.error("通过openId查询用户异常:{}", openId, throwable);
return new LoginAppUser();
}
};
}
}
package com.central.common.lock;
/**
* 分布式锁抽象类
*
* @author zlt
* @date 2018/5/29 14:14
*/
public abstract class AbstractDistributedLock implements DistributedLock{
@Override
public boolean lock(String key) {
return lock(key, TIMEOUT_MILLIS, RETRY_TIMES, SLEEP_MILLIS);
}
@Override
public boolean lock(String key, int retryTimes) {
return lock(key, TIMEOUT_MILLIS, retryTimes, SLEEP_MILLIS);
}
@Override
public boolean lock(String key, int retryTimes, long sleepMillis) {
return lock(key, TIMEOUT_MILLIS, retryTimes, sleepMillis);
}
@Override
public boolean lock(String key, long expire) {
return lock(key, expire, RETRY_TIMES, SLEEP_MILLIS);
}
@Override
public boolean lock(String key, long expire, int retryTimes) {
return lock(key, expire, retryTimes, SLEEP_MILLIS);
}
}
package com.central.common.lock;
/**
* 分布式锁抽象类
*
* @author zlt
* @date 2018/5/29 14:14
*/
public abstract class AbstractDistributedLock implements DistributedLock{
@Override
public boolean lock(String key) {
return lock(key, TIMEOUT_MILLIS, RETRY_TIMES, SLEEP_MILLIS);
}
@Override
public boolean lock(String key, int retryTimes) {
return lock(key, TIMEOUT_MILLIS, retryTimes, SLEEP_MILLIS);
}
@Override
public boolean lock(String key, int retryTimes, long sleepMillis) {
return lock(key, TIMEOUT_MILLIS, retryTimes, sleepMillis);
}
@Override
public boolean lock(String key, long expire) {
return lock(key, expire, RETRY_TIMES, SLEEP_MILLIS);
}
@Override
public boolean lock(String key, long expire, int retryTimes) {
return lock(key, expire, retryTimes, SLEEP_MILLIS);
}
}
package com.central.common.lock;
/**
* 分布式锁顶级接口
* 例如:
* RETRY_TIMES=100,SLEEP_MILLIS=100
* RETRY_TIMES * SLEEP_MILLIS = 10000 意味着如果一直获取不了锁,最长会等待10秒后抛超时异常
*
* @author zlt
* @date 2018/5/29 14:12
*/
public interface DistributedLock {
/**
* 默认超时时间
*/
long TIMEOUT_MILLIS = 5000;
/**
* 重试次数
*/
int RETRY_TIMES = 100;
/**
* 每次重试后等待的时间
*/
long SLEEP_MILLIS = 100;
/**
* 获取锁
*
* @param key key
* @return 成功/失败
*/
boolean lock(String key);
/**
* 获取锁
*
* @param key key
* @param retryTimes 重试次数
* @return 成功/失败
*/
boolean lock(String key, int retryTimes);
/**
* 获取锁
*
* @param key key
* @param retryTimes 重试次数
* @param sleepMillis 获取锁失败的重试间隔
* @return 成功/失败
*/
boolean lock(String key, int retryTimes, long sleepMillis);
/**
* 获取锁
*
* @param key key
* @param expire 获取锁超时时间
* @return 成功/失败
*/
boolean lock(String key, long expire);
/**
* 获取锁
*
* @param key key
* @param expire 获取锁超时时间
* @param retryTimes 重试次数
* @return 成功/失败
*/
boolean lock(String key, long expire, int retryTimes);
/**
* 获取锁
*
* @param key key
* @param expire 获取锁超时时间
* @param retryTimes 重试次数
* @param sleepMillis 获取锁失败的重试间隔
* @return 成功/失败
*/
boolean lock(String key, long expire, int retryTimes, long sleepMillis);
/**
* 释放锁
*
* @param key key值
* @return 释放结果
*/
boolean releaseLock(String key);
}
package com.central.common.lock;
/**
* 分布式锁顶级接口
* 例如:
* RETRY_TIMES=100,SLEEP_MILLIS=100
* RETRY_TIMES * SLEEP_MILLIS = 10000 意味着如果一直获取不了锁,最长会等待10秒后抛超时异常
*
* @author zlt
* @date 2018/5/29 14:12
*/
public interface DistributedLock {
/**
* 默认超时时间
*/
long TIMEOUT_MILLIS = 5000;
/**
* 重试次数
*/
int RETRY_TIMES = 100;
/**
* 每次重试后等待的时间
*/
long SLEEP_MILLIS = 100;
/**
* 获取锁
*
* @param key key
* @return 成功/失败
*/
boolean lock(String key);
/**
* 获取锁
*
* @param key key
* @param retryTimes 重试次数
* @return 成功/失败
*/
boolean lock(String key, int retryTimes);
/**
* 获取锁
*
* @param key key
* @param retryTimes 重试次数
* @param sleepMillis 获取锁失败的重试间隔
* @return 成功/失败
*/
boolean lock(String key, int retryTimes, long sleepMillis);
/**
* 获取锁
*
* @param key key
* @param expire 获取锁超时时间
* @return 成功/失败
*/
boolean lock(String key, long expire);
/**
* 获取锁
*
* @param key key
* @param expire 获取锁超时时间
* @param retryTimes 重试次数
* @return 成功/失败
*/
boolean lock(String key, long expire, int retryTimes);
/**
* 获取锁
*
* @param key key
* @param expire 获取锁超时时间
* @param retryTimes 重试次数
* @param sleepMillis 获取锁失败的重试间隔
* @return 成功/失败
*/
boolean lock(String key, long expire, int retryTimes, long sleepMillis);
/**
* 释放锁
*
* @param key key值
* @return 释放结果
*/
boolean releaseLock(String key);
}
package com.central.common.model;
/**
* @Author: zlt
*/
public enum CodeEnum {
SUCCESS(0),
ERROR(1);
private Integer code;
CodeEnum(Integer code){
this.code = code;
}
public Integer getCode() {
return code;
}
}
package com.central.common.model;
/**
* @Author: zlt
*/
public enum CodeEnum {
SUCCESS(0),
ERROR(1);
private Integer code;
CodeEnum(Integer code){
this.code = code;
}
public Integer getCode() {
return code;
}
}
package com.central.common.model;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.social.security.SocialUserDetails;
import org.springframework.util.CollectionUtils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
/**
* @author zlt
* 用户实体绑定spring security
*/
@Getter
@Setter
public class LoginAppUser extends SysUser implements SocialUserDetails {
private static final long serialVersionUID = -3685249101751401211L;
private Set<String> permissions;
/***
* 权限重写
*/
@JsonIgnore
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new HashSet<>();
if (!CollectionUtils.isEmpty(super.getRoles())) {
super.getRoles().parallelStream().forEach(role -> collection.add(new SimpleGrantedAuthority(role.getCode())));
}
return collection;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return getEnabled();
}
@Override
public String getUserId() {
return getOpenId();
}
}
package com.central.common.model;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.social.security.SocialUserDetails;
import org.springframework.util.CollectionUtils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
/**
* @author zlt
* 用户实体绑定spring security
*/
@Getter
@Setter
public class LoginAppUser extends SysUser implements SocialUserDetails {
private static final long serialVersionUID = -3685249101751401211L;
private Set<String> permissions;
/***
* 权限重写
*/
@JsonIgnore
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new HashSet<>();
if (!CollectionUtils.isEmpty(super.getRoles())) {
super.getRoles().parallelStream().forEach(role -> collection.add(new SimpleGrantedAuthority(role.getCode())));
}
return collection;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return getEnabled();
}
@Override
public String getUserId() {
return getOpenId();
}
}
package com.central.common.model;
import java.io.Serializable;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 分页实体类
*
* @author zlt
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PageResult<T> implements Serializable {
private static final long serialVersionUID = -275582248840137389L;
/**
* 总数
*/
private Long count;
/**
* 是否成功:0 成功、1 失败
*/
private int code;
/**
* 当前页结果集
*/
private List<T> data;
}
package com.central.common.model;
import java.io.Serializable;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 分页实体类
*
* @author zlt
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PageResult<T> implements Serializable {
private static final long serialVersionUID = -275582248840137389L;
/**
* 总数
*/
private Long count;
/**
* 是否成功:0 成功、1 失败
*/
private int code;
/**
* 当前页结果集
*/
private List<T> data;
}
package com.central.common.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @Author: zlt
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> implements Serializable {
private T datas;
private Integer resp_code;
private String resp_msg;
public static <T> Result<T> succeed(String msg) {
return succeedWith(null, CodeEnum.SUCCESS.getCode(), msg);
}
public static <T> Result<T> succeed(T model, String msg) {
return succeedWith(model, CodeEnum.SUCCESS.getCode(), msg);
}
public static <T> Result<T> succeed(T model) {
return succeedWith(model, CodeEnum.SUCCESS.getCode(), "");
}
public static <T> Result<T> succeedWith(T datas, Integer code, String msg) {
return new Result<>(datas, code, msg);
}
public static <T> Result<T> failed(String msg) {
return failedWith(null, CodeEnum.ERROR.getCode(), msg);
}
public static <T> Result<T> failed(T model, String msg) {
return failedWith(model, CodeEnum.ERROR.getCode(), msg);
}
public static <T> Result<T> failedWith(T datas, Integer code, String msg) {
return new Result<>(datas, code, msg);
}
}
package com.central.common.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @Author: zlt
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> implements Serializable {
private T datas;
private Integer resp_code;
private String resp_msg;
public static <T> Result<T> succeed(String msg) {
return succeedWith(null, CodeEnum.SUCCESS.getCode(), msg);
}
public static <T> Result<T> succeed(T model, String msg) {
return succeedWith(model, CodeEnum.SUCCESS.getCode(), msg);
}
public static <T> Result<T> succeed(T model) {
return succeedWith(model, CodeEnum.SUCCESS.getCode(), "");
}
public static <T> Result<T> succeedWith(T datas, Integer code, String msg) {
return new Result<>(datas, code, msg);
}
public static <T> Result<T> failed(String msg) {
return failedWith(null, CodeEnum.ERROR.getCode(), msg);
}
public static <T> Result<T> failed(T model, String msg) {
return failedWith(model, CodeEnum.ERROR.getCode(), msg);
}
public static <T> Result<T> failedWith(T datas, Integer code, String msg) {
return new Result<>(datas, code, msg);
}
}
package com.central.common.model;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Date;
/**
* 实体父类
*
* @author zlt
*/
@Setter
@Getter
public class SuperEntity<T extends Model<?>> extends Model<T> {
/**
* 主键ID
*/
@TableId
private Long id;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@Override
protected Serializable pkVal() {
return this.id;
}
}
package com.central.common.model;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Date;
/**
* 实体父类
*
* @author zlt
*/
@Setter
@Getter
public class SuperEntity<T extends Model<?>> extends Model<T> {
/**
* 主键ID
*/
@TableId
private Long id;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@Override
protected Serializable pkVal() {
return this.id;
}
}
package com.central.common.model;
import java.util.List;
import java.util.Set;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author zlt
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("sys_menu")
public class SysMenu extends SuperEntity {
private static final long serialVersionUID = 749360940290141180L;
private Long parentId;
private String name;
private String css;
private String url;
private String path;
private Integer sort;
private Integer type;
private Boolean hidden;
/**
* 请求的类型
*/
private String pathMethod;
@TableField(exist = false)
private List<SysMenu> subMenus;
@TableField(exist = false)
private Long roleId;
@TableField(exist = false)
private Set<Long> menuIds;
}
package com.central.common.model;
import java.util.List;
import java.util.Set;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author zlt
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("sys_menu")
public class SysMenu extends SuperEntity {
private static final long serialVersionUID = 749360940290141180L;
private Long parentId;
private String name;
private String css;
private String url;
private String path;
private Integer sort;
private Integer type;
private Boolean hidden;
/**
* 请求的类型
*/
private String pathMethod;
@TableField(exist = false)
private List<SysMenu> subMenus;
@TableField(exist = false)
private Long roleId;
@TableField(exist = false)
private Set<Long> menuIds;
}
package com.central.common.model;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author zlt
* 角色
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("sys_role")
public class SysRole extends SuperEntity {
private static final long serialVersionUID = 4497149010220586111L;
private String code;
private String name;
private Long userId;
}
package com.central.common.model;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author zlt
* 角色
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("sys_role")
public class SysRole extends SuperEntity {
private static final long serialVersionUID = 4497149010220586111L;
private String code;
private String name;
private Long userId;
}
package com.central.common.model;
import java.util.List;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author zlt
* 用户实体
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("sys_user")
public class SysUser extends SuperEntity {
private static final long serialVersionUID = -5886012896705137070L;
private String username;
private String password;
private String nickname;
private String headImgUrl;
private String mobile;
private Integer sex;
private Boolean enabled;
private String type;
private String openId;
@TableLogic
private boolean isDel;
@TableField(exist = false)
private List<SysRole> roles;
@TableField(exist = false)
private String roleId;
@TableField(exist = false)
private String oldPassword;
@TableField(exist = false)
private String newPassword;
}
package com.central.common.model;
import java.util.List;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author zlt
* 用户实体
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("sys_user")
public class SysUser extends SuperEntity {
private static final long serialVersionUID = -5886012896705137070L;
private String username;
private String password;
private String nickname;
private String headImgUrl;
private String mobile;
private Integer sex;
private Boolean enabled;
private String type;
private String openId;
@TableLogic
private boolean isDel;
@TableField(exist = false)
private List<SysRole> roles;
@TableField(exist = false)
private String roleId;
@TableField(exist = false)
private String oldPassword;
@TableField(exist = false)
private String newPassword;
}
package com.central.common.model;
/**
* @author zlt
* 用户类型
*/
public enum UserType {
/**
* 前端app用户
*/
APP,
/**
* 后端管理用户
*/
BACKEND
}
package com.central.common.model;
/**
* @author zlt
* 用户类型
*/
public enum UserType {
/**
* 前端app用户
*/
APP,
/**
* 后端管理用户
*/
BACKEND
}
package com.central.common.resolver;
import cn.hutool.core.util.StrUtil;
import com.central.common.annotation.LoginUser;
import com.central.common.constant.SecurityConstants;
import com.central.common.feign.UserService;
import com.central.common.model.SysRole;
import com.central.common.model.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Token转化SysUser
*
* @author zlt
* @date 2018/12/21
*/
@Slf4j
public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
private UserService userService;
public TokenArgumentResolver(UserService userService) {
this.userService = userService;
}
/**
* 入参筛选
*
* @param methodParameter 参数集合
* @return 格式化后的参数
*/
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(LoginUser.class) && methodParameter.getParameterType().equals(SysUser.class);
}
/**
* @param methodParameter 入参集合
* @param modelAndViewContainer model 和 view
* @param nativeWebRequest web相关
* @param webDataBinderFactory 入参解析
* @return 包装对象
*/
@Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) {
LoginUser loginUser = methodParameter.getParameterAnnotation(LoginUser.class);
boolean isFull = loginUser.isFull();
HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
String userId = request.getHeader(SecurityConstants.USER_ID_HEADER);
String username = request.getHeader(SecurityConstants.USER_HEADER);
String roles = request.getHeader(SecurityConstants.ROLE_HEADER);
if (StrUtil.isBlank(username)) {
log.warn("resolveArgument error username is empty");
return null;
}
SysUser user;
if (isFull) {
user = userService.selectByUsername(username);
} else {
user = new SysUser();
user.setId(Long.valueOf(userId));
user.setUsername(username);
}
List<SysRole> sysRoleList = new ArrayList<>();
Arrays.stream(roles.split(",")).forEach(role -> {
SysRole sysRole = new SysRole();
sysRole.setCode(role);
sysRoleList.add(sysRole);
});
user.setRoles(sysRoleList);
return user;
}
}
package com.central.common.resolver;
import cn.hutool.core.util.StrUtil;
import com.central.common.annotation.LoginUser;
import com.central.common.constant.SecurityConstants;
import com.central.common.feign.UserService;
import com.central.common.model.SysRole;
import com.central.common.model.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Token转化SysUser
*
* @author zlt
* @date 2018/12/21
*/
@Slf4j
public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
private UserService userService;
public TokenArgumentResolver(UserService userService) {
this.userService = userService;
}
/**
* 入参筛选
*
* @param methodParameter 参数集合
* @return 格式化后的参数
*/
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(LoginUser.class) && methodParameter.getParameterType().equals(SysUser.class);
}
/**
* @param methodParameter 入参集合
* @param modelAndViewContainer model 和 view
* @param nativeWebRequest web相关
* @param webDataBinderFactory 入参解析
* @return 包装对象
*/
@Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) {
LoginUser loginUser = methodParameter.getParameterAnnotation(LoginUser.class);
boolean isFull = loginUser.isFull();
HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
String userId = request.getHeader(SecurityConstants.USER_ID_HEADER);
String username = request.getHeader(SecurityConstants.USER_HEADER);
String roles = request.getHeader(SecurityConstants.ROLE_HEADER);
if (StrUtil.isBlank(username)) {
log.warn("resolveArgument error username is empty");
return null;
}
SysUser user;
if (isFull) {
user = userService.selectByUsername(username);
} else {
user = new SysUser();
user.setId(Long.valueOf(userId));
user.setUsername(username);
}
List<SysRole> sysRoleList = new ArrayList<>();
Arrays.stream(roles.split(",")).forEach(role -> {
SysRole sysRole = new SysRole();
sysRole.setCode(role);
sysRoleList.add(sysRole);
});
user.setRoles(sysRoleList);
return user;
}
}
package com.central.common.service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.central.common.lock.DistributedLock;
/**
* service接口父类
*
* @author zlt
* @date 2019/1/10
*/
public interface ISuperService<T> extends IService<T> {
/**
* 幂等性新增记录
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @param msg 对象已存在提示信息
* @return
*/
boolean saveIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper, String msg);
/**
* 幂等性新增记录
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @return
*/
boolean saveIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper);
/**
* 幂等性新增或更新记录
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @param msg 对象已存在提示信息
* @return
*/
boolean saveOrUpdateIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper, String msg);
/**
* 幂等性新增或更新记录
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @return
*/
boolean saveOrUpdateIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper);
}
package com.central.common.service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.central.common.lock.DistributedLock;
/**
* service接口父类
*
* @author zlt
* @date 2019/1/10
*/
public interface ISuperService<T> extends IService<T> {
/**
* 幂等性新增记录
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @param msg 对象已存在提示信息
* @return
*/
boolean saveIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper, String msg);
/**
* 幂等性新增记录
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @return
*/
boolean saveIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper);
/**
* 幂等性新增或更新记录
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @param msg 对象已存在提示信息
* @return
*/
boolean saveOrUpdateIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper, String msg);
/**
* 幂等性新增或更新记录
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @return
*/
boolean saveOrUpdateIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper);
}
package com.central.common.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.central.common.exception.IdempotencyException;
import com.central.common.exception.LockException;
import com.central.common.lock.DistributedLock;
import com.central.common.service.ISuperService;
import java.io.Serializable;
import java.util.Objects;
/**
* service实现父类
*
* @author zlt
* @date 2019/1/10
*/
public class SuperServiceImpl<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> implements ISuperService<T> {
/**
* 幂等性新增记录
* 例子如下:
* String username = sysUser.getUsername();
* boolean result = super.saveIdempotency(sysUser, lock
* , LOCK_KEY_USERNAME+username
* , new QueryWrapper<SysUser>().eq("username", username));
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @param msg 对象已存在提示信息
* @return
*/
@Override
public boolean saveIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper, String msg) {
if (lock == null) {
throw new LockException("DistributedLock is null");
}
if (StrUtil.isEmpty(lockKey)) {
throw new LockException("lockKey is null");
}
try {
//加锁
boolean isLock = lock.lock(lockKey);
if (isLock) {
//判断记录是否已存在
int count = super.count(countWrapper);
if (count == 0) {
return super.save(entity);
} else {
if (StrUtil.isEmpty(msg)) {
msg = "已存在";
}
throw new IdempotencyException(msg);
}
} else {
throw new LockException("锁等待超时");
}
} finally {
lock.releaseLock(lockKey);
}
}
/**
* 幂等性新增记录
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @return
*/
@Override
public boolean saveIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper) {
return saveIdempotency(entity, lock, lockKey, countWrapper, null);
}
/**
* 幂等性新增或更新记录
* 例子如下:
* String username = sysUser.getUsername();
* boolean result = super.saveOrUpdateIdempotency(sysUser, lock
* , LOCK_KEY_USERNAME+username
* , new QueryWrapper<SysUser>().eq("username", username));
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @param msg 对象已存在提示信息
* @return
*/
@Override
public boolean saveOrUpdateIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper, String msg) {
if (null != entity) {
Class<?> cls = entity.getClass();
TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
if (null != tableInfo && StringUtils.isNotEmpty(tableInfo.getKeyProperty())) {
Object idVal = ReflectionKit.getMethodValue(cls, entity, tableInfo.getKeyProperty());
if (StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal))) {
if (StrUtil.isEmpty(msg)) {
msg = "已存在";
}
return this.saveIdempotency(entity, lock, lockKey, countWrapper, msg);
} else {
return updateById(entity);
}
} else {
throw ExceptionUtils.mpe("Error: Can not execute. Could not find @TableId.");
}
}
return false;
}
/**
* 幂等性新增或更新记录
* 例子如下:
* String username = sysUser.getUsername();
* boolean result = super.saveOrUpdateIdempotency(sysUser, lock
* , LOCK_KEY_USERNAME+username
* , new QueryWrapper<SysUser>().eq("username", username));
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @return
*/
@Override
public boolean saveOrUpdateIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper) {
return this.saveOrUpdateIdempotency(entity, lock, lockKey, countWrapper, null);
}
}
package com.central.common.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.central.common.exception.IdempotencyException;
import com.central.common.exception.LockException;
import com.central.common.lock.DistributedLock;
import com.central.common.service.ISuperService;
import java.io.Serializable;
import java.util.Objects;
/**
* service实现父类
*
* @author zlt
* @date 2019/1/10
*/
public class SuperServiceImpl<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> implements ISuperService<T> {
/**
* 幂等性新增记录
* 例子如下:
* String username = sysUser.getUsername();
* boolean result = super.saveIdempotency(sysUser, lock
* , LOCK_KEY_USERNAME+username
* , new QueryWrapper<SysUser>().eq("username", username));
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @param msg 对象已存在提示信息
* @return
*/
@Override
public boolean saveIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper, String msg) {
if (lock == null) {
throw new LockException("DistributedLock is null");
}
if (StrUtil.isEmpty(lockKey)) {
throw new LockException("lockKey is null");
}
try {
//加锁
boolean isLock = lock.lock(lockKey);
if (isLock) {
//判断记录是否已存在
int count = super.count(countWrapper);
if (count == 0) {
return super.save(entity);
} else {
if (StrUtil.isEmpty(msg)) {
msg = "已存在";
}
throw new IdempotencyException(msg);
}
} else {
throw new LockException("锁等待超时");
}
} finally {
lock.releaseLock(lockKey);
}
}
/**
* 幂等性新增记录
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @return
*/
@Override
public boolean saveIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper) {
return saveIdempotency(entity, lock, lockKey, countWrapper, null);
}
/**
* 幂等性新增或更新记录
* 例子如下:
* String username = sysUser.getUsername();
* boolean result = super.saveOrUpdateIdempotency(sysUser, lock
* , LOCK_KEY_USERNAME+username
* , new QueryWrapper<SysUser>().eq("username", username));
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @param msg 对象已存在提示信息
* @return
*/
@Override
public boolean saveOrUpdateIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper, String msg) {
if (null != entity) {
Class<?> cls = entity.getClass();
TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
if (null != tableInfo && StringUtils.isNotEmpty(tableInfo.getKeyProperty())) {
Object idVal = ReflectionKit.getMethodValue(cls, entity, tableInfo.getKeyProperty());
if (StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal))) {
if (StrUtil.isEmpty(msg)) {
msg = "已存在";
}
return this.saveIdempotency(entity, lock, lockKey, countWrapper, msg);
} else {
return updateById(entity);
}
} else {
throw ExceptionUtils.mpe("Error: Can not execute. Could not find @TableId.");
}
}
return false;
}
/**
* 幂等性新增或更新记录
* 例子如下:
* String username = sysUser.getUsername();
* boolean result = super.saveOrUpdateIdempotency(sysUser, lock
* , LOCK_KEY_USERNAME+username
* , new QueryWrapper<SysUser>().eq("username", username));
*
* @param entity 实体对象
* @param lock 锁实例
* @param lockKey 锁的key
* @param countWrapper 判断是否存在的条件
* @return
*/
@Override
public boolean saveOrUpdateIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper) {
return this.saveOrUpdateIdempotency(entity, lock, lockKey, countWrapper, null);
}
}
package com.central.common.utils;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Excel工具类
*
* @author zlt
* @date 2019/1/6
*/
public class ExcelUtil {
private ExcelUtil() {
throw new IllegalStateException("Utility class");
}
/**
* 导出
*
* @param list 数据列表
* @param title 标题
* @param sheetName sheet名称
* @param pojoClass 元素类型
* @param fileName 文件名
* @param isCreateHeader 是否创建列头
* @param response
* @throws IOException
*/
public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName
, boolean isCreateHeader, HttpServletResponse response) throws IOException {
ExportParams exportParams = new ExportParams(title, sheetName, ExcelType.XSSF);
exportParams.setCreateHeadRows(isCreateHeader);
defaultExport(list, pojoClass, fileName, response, exportParams);
}
/**
* 导出
*
* @param list 数据列表
* @param title 标题
* @param sheetName sheet名称
* @param pojoClass 元素类型
* @param fileName 文件名
* @param response
* @throws IOException
*/
public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName
, HttpServletResponse response) throws IOException {
defaultExport(list, pojoClass, fileName, response, new ExportParams(title, sheetName, ExcelType.XSSF));
}
/**
* 导出
*
* @param list 数据列表(元素是Map)
* @param fileName 文件名
* @param response
* @throws IOException
*/
public static void exportExcel(List<Map<String, Object>> list, String fileName, HttpServletResponse response) throws IOException {
defaultExport(list, fileName, response);
}
private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName
, HttpServletResponse response, ExportParams exportParams) throws IOException {
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
if (workbook != null) {
downLoadExcel(fileName, response, workbook);
}
}
private static void defaultExport(List<Map<String, Object>> list, String fileName, HttpServletResponse response) throws IOException {
Workbook workbook = ExcelExportUtil.exportExcel(list, ExcelType.XSSF);
if (workbook != null) {
downLoadExcel(fileName, response, workbook);
}
}
private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
workbook.write(response.getOutputStream());
}
public static <T> List<T> importExcel(String filePath, Integer titleRows, Integer headerRows, Class<T> pojoClass) {
if (StringUtils.isBlank(filePath)) {
return Collections.emptyList();
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
return ExcelImportUtil.importExcel(new File(filePath), pojoClass, params);
}
public static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows, Class<T> pojoClass) throws Exception {
if (file == null) {
return Collections.emptyList();
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
return ExcelImportUtil.importExcel(file.getInputStream(), pojoClass, params);
}
}
package com.central.common.utils;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Excel工具类
*
* @author zlt
* @date 2019/1/6
*/
public class ExcelUtil {
private ExcelUtil() {
throw new IllegalStateException("Utility class");
}
/**
* 导出
*
* @param list 数据列表
* @param title 标题
* @param sheetName sheet名称
* @param pojoClass 元素类型
* @param fileName 文件名
* @param isCreateHeader 是否创建列头
* @param response
* @throws IOException
*/
public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName
, boolean isCreateHeader, HttpServletResponse response) throws IOException {
ExportParams exportParams = new ExportParams(title, sheetName, ExcelType.XSSF);
exportParams.setCreateHeadRows(isCreateHeader);
defaultExport(list, pojoClass, fileName, response, exportParams);
}
/**
* 导出
*
* @param list 数据列表
* @param title 标题
* @param sheetName sheet名称
* @param pojoClass 元素类型
* @param fileName 文件名
* @param response
* @throws IOException
*/
public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName
, HttpServletResponse response) throws IOException {
defaultExport(list, pojoClass, fileName, response, new ExportParams(title, sheetName, ExcelType.XSSF));
}
/**
* 导出
*
* @param list 数据列表(元素是Map)
* @param fileName 文件名
* @param response
* @throws IOException
*/
public static void exportExcel(List<Map<String, Object>> list, String fileName, HttpServletResponse response) throws IOException {
defaultExport(list, fileName, response);
}
private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName
, HttpServletResponse response, ExportParams exportParams) throws IOException {
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
if (workbook != null) {
downLoadExcel(fileName, response, workbook);
}
}
private static void defaultExport(List<Map<String, Object>> list, String fileName, HttpServletResponse response) throws IOException {
Workbook workbook = ExcelExportUtil.exportExcel(list, ExcelType.XSSF);
if (workbook != null) {
downLoadExcel(fileName, response, workbook);
}
}
private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
workbook.write(response.getOutputStream());
}
public static <T> List<T> importExcel(String filePath, Integer titleRows, Integer headerRows, Class<T> pojoClass) {
if (StringUtils.isBlank(filePath)) {
return Collections.emptyList();
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
return ExcelImportUtil.importExcel(new File(filePath), pojoClass, params);
}
public static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows, Class<T> pojoClass) throws Exception {
if (file == null) {
return Collections.emptyList();
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
return ExcelImportUtil.importExcel(file.getInputStream(), pojoClass, params);
}
}
package com.central.common.utils;
import com.central.common.model.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
/**
* @author zlt
* @date 2018/12/20
*/
public class ResponseUtil {
private ResponseUtil() {
throw new IllegalStateException("Utility class");
}
/**
* 通过流写到前端
*
* @param objectMapper 对象序列化
* @param response
* @param msg 返回信息
* @param httpStatus 返回状态码
* @throws IOException
*/
public static void responseWriter(ObjectMapper objectMapper, HttpServletResponse response, String msg, int httpStatus) throws IOException {
Result result = Result.succeedWith(null, httpStatus, msg);
responseWrite(objectMapper, response, result);
}
/**
* 通过流写到前端
* @param objectMapper 对象序列化
* @param response
* @param obj
*/
public static void responseSucceed(ObjectMapper objectMapper, HttpServletResponse response, Object obj) throws IOException {
Result result = Result.succeed(obj);
responseWrite(objectMapper, response, result);
}
/**
* 通过流写到前端
* @param objectMapper
* @param response
* @param msg
* @throws IOException
*/
public static void responseFailed(ObjectMapper objectMapper, HttpServletResponse response, String msg) throws IOException {
Result result = Result.failed(msg);
responseWrite(objectMapper, response, result);
}
private static void responseWrite(ObjectMapper objectMapper, HttpServletResponse response, Result result) throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
try (
Writer writer = response.getWriter()
) {
writer.write(objectMapper.writeValueAsString(result));
writer.flush();
}
}
}
package com.central.common.utils;
import com.alibaba.fastjson.JSONObject;
import com.central.common.model.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
/**
* @author zlt
* @date 2018/12/20
*/
public class ResponseUtil {
private ResponseUtil() {
throw new IllegalStateException("Utility class");
}
/**
* 通过流写到前端
*
* @param objectMapper 对象序列化
* @param response
* @param msg 返回信息
* @param httpStatus 返回状态码
* @throws IOException
*/
public static void responseWriter(ObjectMapper objectMapper, HttpServletResponse response, String msg, int httpStatus) throws IOException {
Result result = Result.succeedWith(null, httpStatus, msg);
responseWrite(objectMapper, response, result);
}
/**
* 通过流写到前端
* @param objectMapper 对象序列化
* @param response
* @param obj
*/
public static void responseSucceed(ObjectMapper objectMapper, HttpServletResponse response, Object obj) throws IOException {
Result result = Result.succeed(obj);
responseWrite(objectMapper, response, result);
}
/**
* 通过流写到前端
* @param objectMapper
* @param response
* @param msg
* @throws IOException
*/
public static void responseFailed(ObjectMapper objectMapper, HttpServletResponse response, String msg) throws IOException {
Result result = Result.failed(msg);
responseWrite(objectMapper, response, result);
}
private static void responseWrite(ObjectMapper objectMapper, HttpServletResponse response, Result result) throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
try (
Writer writer = response.getWriter()
) {
writer.write(objectMapper.writeValueAsString(result));
writer.flush();
}
}
/**
* webflux的response返回json对象
*/
public static Mono<Void> responseWriter(ServerWebExchange exchange, int httpStatus, String msg) {
Result result = Result.succeedWith(null, httpStatus, msg);
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.valueOf(result.getResp_code()));
response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
DataBufferFactory dataBufferFactory = response.bufferFactory();
DataBuffer buffer = dataBufferFactory.wrap(JSONObject.toJSONString(result).getBytes(Charset.defaultCharset()));
return response.writeWith(Mono.just(buffer)).doOnError((error) -> {
DataBufferUtils.release(buffer);
});
}
}
package com.central.common.utils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
/**
* spring获取bean工具类
*
* @author 作者 owen E-mail: 624191343@qq.com
*/
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
public static <T> T getBean(Class<T> cla) {
return applicationContext.getBean(cla);
}
public static <T> T getBean(String name, Class<T> cal) {
return applicationContext.getBean(name, cal);
}
public static String getProperty(String key) {
return applicationContext.getBean(Environment.class).getProperty(key);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringUtil.applicationContext = applicationContext;
}
}
package com.central.common.utils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
/**
* spring获取bean工具类
*
* @author 作者 owen E-mail: 624191343@qq.com
*/
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
public static <T> T getBean(Class<T> cla) {
return applicationContext.getBean(cla);
}
public static <T> T getBean(String name, Class<T> cal) {
return applicationContext.getBean(name, cal);
}
public static String getProperty(String key) {
return applicationContext.getBean(Environment.class).getProperty(key);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringUtil.applicationContext = applicationContext;
}
}
org.springframework.context.ApplicationContextInitializer=\
com.central.common.config.BannerInitializer
\ No newline at end of file
......@@ -12,81 +12,13 @@
<dependencies>
<dependency>
<groupId>com.zlt</groupId>
<artifactId>zlt-log-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</dependency>
<!-- easypoi -->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<artifactId>zlt-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>banner</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
......@@ -4,8 +4,10 @@ import cn.hutool.core.util.StrUtil;
import com.central.common.constant.CommonConstant;
import com.central.common.constant.SecurityConstants;
import com.central.common.context.TenantContextHolder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
......@@ -18,6 +20,7 @@ import java.io.IOException;
* @author zlt
* @date 2019/9/15
*/
@ConditionalOnClass(Filter.class)
public class TenantFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
......
......@@ -4,9 +4,11 @@ import cn.hutool.core.util.StrUtil;
import com.central.common.constant.CommonConstant;
import com.central.log.properties.TraceProperties;
import org.slf4j.MDC;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
......@@ -19,6 +21,7 @@ import java.io.IOException;
* @author zlt
* @date 2019/9/15
*/
@ConditionalOnClass(Filter.class)
public class TraceFilter extends OncePerRequestFilter {
@Resource
private TraceProperties traceProperties;
......
org.springframework.context.ApplicationContextInitializer=\
com.central.common.config.BannerInitializer
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.central.common.filter.TenantFilter,\
com.central.common.filter.TraceFilter
\ No newline at end of file
......@@ -14,7 +14,7 @@
<dependencies>
<dependency>
<groupId>com.zlt</groupId>
<artifactId>zlt-common-spring-boot-starter</artifactId>
<artifactId>zlt-common-core</artifactId>
</dependency>
<dependency>
......
......@@ -14,7 +14,7 @@
<dependencies>
<dependency>
<groupId>com.zlt</groupId>
<artifactId>zlt-common-spring-boot-starter</artifactId>
<artifactId>zlt-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
......
......@@ -14,7 +14,7 @@
<dependencies>
<dependency>
<groupId>com.zlt</groupId>
<artifactId>zlt-common-spring-boot-starter</artifactId>
<artifactId>zlt-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
......@@ -36,6 +36,7 @@
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
......
package com.central.common.ribbon;
import com.central.common.constant.ConfigConstants;
import com.central.common.ribbon.config.RuleConfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
/**
* Ribbon扩展配置类
*
* @author zlt
* @date 2018/11/17 9:24
*/
@ConditionalOnProperty(value = ConfigConstants.CONFIG_RIBBON_ISOLATION_ENABLED, havingValue = "true")
@RibbonClients(defaultConfiguration = {RuleConfigure.class})
public class LbIsolationAutoConfigure {
}
package com.central.common.ribbon;
import com.central.common.constant.ConfigConstants;
import com.central.common.ribbon.config.RestTemplateProperties;
import com.central.common.ribbon.config.RuleConfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.ribbon.DefaultPropertiesFactory;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Ribbon扩展配置类
......@@ -22,10 +17,4 @@ public class RibbonAutoConfigure {
public DefaultPropertiesFactory defaultPropertiesFactory() {
return new DefaultPropertiesFactory();
}
@Configuration
@ConditionalOnProperty(value = ConfigConstants.CONFIG_RIBBON_ISOLATION_ENABLED, havingValue = "true")
@RibbonClients(defaultConfiguration = {RuleConfigure.class})
public class LbIsolationConfig {
}
}
......@@ -87,7 +87,7 @@ public class FeignInterceptorConfig {
Enumeration<String> headers = request.getHeaders(CommonConstant.TOKEN_HEADER);
while (headers.hasMoreElements()) {
String value = headers.nextElement();
if ((value.toLowerCase().startsWith(CommonConstant.BEARER_TYPE.toLowerCase()))) {
if ((value.toLowerCase().startsWith(CommonConstant.BEARER_TYPE))) {
String authHeaderValue = value.substring(CommonConstant.BEARER_TYPE.length()).trim();
int commaIndex = authHeaderValue.indexOf(',');
if (commaIndex > 0) {
......
......@@ -5,8 +5,12 @@ import com.central.common.constant.CommonConstant;
import com.central.common.constant.ConfigConstants;
import com.central.common.context.LbIsolationContextHolder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
......@@ -19,6 +23,7 @@ import java.io.IOException;
* @author zlt
* @date 2019/9/15
*/
@ConditionalOnClass(Filter.class)
public class LbIsolationFilter extends OncePerRequestFilter {
@Value("${" + ConfigConstants.CONFIG_RIBBON_ISOLATION_ENABLED + ":false}")
private boolean enableIsolation;
......
......@@ -18,6 +18,7 @@ import java.util.stream.Collectors;
* @date 2019/9/3
*/
public class CustomIsolationRule extends RoundRobinRule {
private final static String KEY_DEFAULT = "default";
/**
* 优先根据版本号取实例
*/
......@@ -26,7 +27,13 @@ public class CustomIsolationRule extends RoundRobinRule {
if (lb == null) {
return null;
}
String version = LbIsolationContextHolder.getVersion();
String version;
if (key != null && !KEY_DEFAULT.equals(key)) {
version = key.toString();
} else {
version = LbIsolationContextHolder.getVersion();
}
List<Server> targetList = null;
List<Server> upList = lb.getReachableServers();
if (StrUtil.isNotEmpty(version)) {
......
......@@ -2,4 +2,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.central.common.ribbon.RibbonAutoConfigure,\
com.central.common.ribbon.FeignAutoConfigure,\
com.central.common.ribbon.RestTemplateAutoConfigure,\
com.central.common.ribbon.filter.LbIsolationFilter
com.central.common.ribbon.filter.LbIsolationFilter,\
com.central.common.ribbon.LbIsolationAutoConfigure
......@@ -14,7 +14,7 @@
<dependencies>
<dependency>
<groupId>com.zlt</groupId>
<artifactId>zlt-common-spring-boot-starter</artifactId>
<artifactId>zlt-common-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
......
......@@ -14,7 +14,7 @@
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
......
......@@ -11,7 +11,7 @@ spring.redis.timeout=5000
##### elasticsearch配置
zlt.elasticsearch.cluster-name=my-es
zlt.elasticsearch.cluster-nodes=119.3.105.180
zlt.elasticsearch.cluster-nodes=192.168.28.130
##### sentinel配置
zlt.sentinel.dashboard=192.168.28.130:6999
......
......@@ -9,6 +9,9 @@
<artifactId>zlt-gateway</artifactId>
<packaging>pom</packaging>
<modules>
<!-- zuul -->
<module>zuul-gateway</module>
<!-- spring cloud gateway -->
<module>sc-gateway</module>
</modules>
</project>
\ No newline at end of file
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.zlt</groupId>
<artifactId>zlt-gateway</artifactId>
<version>2.7.2</version>
</parent>
<artifactId>sc-gateway</artifactId>
<description>spring cloud gateway网关</description>
<dependencies>
<dependency>
<groupId>com.zlt</groupId>
<artifactId>zlt-config</artifactId>
</dependency>
<dependency>
<groupId>com.zlt</groupId>
<artifactId>zlt-ribbon-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.zlt</groupId>
<artifactId>zlt-sentinel-spring-boot-starter</artifactId>
</dependency>
<!--<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>-->
<dependency>
<groupId>com.zlt</groupId>
<artifactId>zlt-auth-client-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>com.zlt</groupId>
<artifactId>zlt-redis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<imageName>${docker.image.prefix}/${project.artifactId}</imageName>
<imageTags>
<imageTag>${project.version}</imageTag>
<imageTag>latest</imageTag>
</imageTags>
<forceTags>true</forceTags>
<baseImage>${docker.baseImage}</baseImage>
<volumes>${docker.volumes}</volumes>
<env>
<JAVA_OPTS>${docker.java.opts}</JAVA_OPTS>
</env>
<entryPoint>["sh","-c","java $JAVA_OPTS ${docker.java.security.egd} -jar /${project.build.finalName}.jar"]</entryPoint>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
<finalName>${project.artifactId}</finalName>
</build>
</project>
\ No newline at end of file
package com.central;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author zlt
* @date 2019/10/5
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class SCGatewayApp {
public static void main(String[] args) {
SpringApplication.run(SCGatewayApp.class, args);
}
}
\ No newline at end of file
package com.central.gateway.auth;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import reactor.core.publisher.Mono;
/**
* @author zlt
* @date 2019/10/6
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
public class CustomAuthenticationManager implements ReactiveAuthenticationManager {
private TokenStore tokenStore;
public CustomAuthenticationManager(TokenStore tokenStore) {
this.tokenStore = tokenStore;
}
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
return Mono.justOrEmpty(authentication)
.filter(a -> a instanceof BearerTokenAuthenticationToken)
.cast(BearerTokenAuthenticationToken.class)
.map(BearerTokenAuthenticationToken::getToken)
.flatMap((accessTokenValue -> {
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
if (accessToken == null) {
return Mono.error(new InvalidTokenException("Invalid access token: " + accessTokenValue));
} else if (accessToken.isExpired()) {
tokenStore.removeAccessToken(accessToken);
return Mono.error(new InvalidTokenException("Access token expired: " + accessTokenValue));
}
OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
if (result == null) {
return Mono.error(new InvalidTokenException("Invalid access token: " + accessTokenValue));
}
return Mono.just(result);
}))
.cast(Authentication.class);
}
}
package com.central.gateway.auth;
import com.central.common.utils.ResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 403拒绝访问异常处理,转换为JSON
*
* @author zlt
* @date 2019/10/7
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
@Slf4j
public class JsonAccessDeniedHandler implements ServerAccessDeniedHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException e) {
return ResponseUtil.responseWriter(exchange, HttpStatus.FORBIDDEN.value(), e.getMessage());
}
}
package com.central.gateway.auth;
import com.central.common.utils.ResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 401未授权异常处理,转换为JSON
*
* @author zlt
* @date 2019/10/7
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
@Slf4j
public class JsonAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
@Override
public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
return ResponseUtil.responseWriter(exchange, HttpStatus.UNAUTHORIZED.value(), e.getMessage());
}
}
package com.central.gateway.auth;
import cn.hutool.core.collection.CollectionUtil;
import com.central.common.constant.SecurityConstants;
import com.central.common.model.SysUser;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 认证成功处理类
*
* @author zlt
* @date 2019/10/7
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
public class Oauth2AuthSuccessHandler implements ServerAuthenticationSuccessHandler {
@Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
SysUser user = (SysUser)authentication.getPrincipal();
Long userId = user.getId();
String username = user.getUsername();
OAuth2Authentication oauth2Authentication = (OAuth2Authentication)authentication;
String clientId = oauth2Authentication.getOAuth2Request().getClientId();
ServerWebExchange exchange = webFilterExchange.getExchange();
ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate()
.headers(h -> {
h.add(SecurityConstants.USER_ID_HEADER, String.valueOf(userId));
h.add(SecurityConstants.USER_HEADER, username);
h.add(SecurityConstants.TENANT_HEADER, clientId);
h.add(SecurityConstants.ROLE_HEADER, CollectionUtil.join(authentication.getAuthorities(), ","));
})
.build();
ServerWebExchange build = exchange.mutate().request(serverHttpRequest).build();
return webFilterExchange.getChain().filter(build);
}
}
package com.central.gateway.auth;
import com.central.common.model.SysMenu;
import com.central.gateway.feign.MenuService;
import com.central.oauth2.common.service.impl.DefaultPermissionServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.List;
/**
* url权限认证
*
* @author zlt
* @date 2019/10/6
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
@Slf4j
@Component
public class PermissionAuthManager extends DefaultPermissionServiceImpl implements ReactiveAuthorizationManager<AuthorizationContext> {
@Resource
private MenuService menuService;
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext authorizationContext) {
return authentication.map(auth -> {
ServerWebExchange exchange = authorizationContext.getExchange();
ServerHttpRequest request = exchange.getRequest();
boolean isPermission = super.hasPermission(auth, request.getMethodValue(), request.getURI().getPath());
return new AuthorizationDecision(isPermission);
}).defaultIfEmpty(new AuthorizationDecision(false));
}
@Override
public List<SysMenu> findMenuByRoleCodes(String roleCodes) {
return menuService.findByRoleCodes(roleCodes);
}
}
package com.central.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* 跨域配置
*
* @author zlt
* @date 2019/10/5
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
@Configuration
public class CorsConfig {
private static final String ALL = "*";
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// cookie跨域
config.setAllowCredentials(Boolean.TRUE);
config.addAllowedMethod(ALL);
config.addAllowedOrigin(ALL);
config.addAllowedHeader(ALL);
// 配置前端js允许访问的自定义响应头
config.addExposedHeader("setToken");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
package com.central.gateway.config;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.central.gateway.route.NacosRouteDefinitionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 动态路由配置
*
* @author zlt
* @date 2019/10/7
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
@Configuration
@ConditionalOnProperty(prefix = "zlt.gateway.dynamicRoute", name = "enabled", havingValue = "true")
public class DynamicRouteConfig {
@Autowired
private ApplicationEventPublisher publisher;
/**
* Nacos实现方式
*/
@Configuration
@ConditionalOnProperty(prefix = "zlt.gateway.dynamicRoute", name = "dataType", havingValue = "nacos", matchIfMissing = true)
public class NacosDynRoute {
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Bean
public NacosRouteDefinitionRepository nacosRouteDefinitionRepository() {
return new NacosRouteDefinitionRepository(publisher, nacosConfigProperties);
}
}
}
package com.central.gateway.config;
import com.central.gateway.auth.*;
import com.central.oauth2.common.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.authentication.ServerAuthenticationEntryPointFailureHandler;
/**
* 资源服务器配置
*
* @author zlt
* @date 2019/10/5
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
@Configuration
public class ResourceServerConfiguration {
@Autowired
private SecurityProperties securityProperties;
@Autowired
private TokenStore tokenStore;
@Autowired
private PermissionAuthManager permissionAuthManager;
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
//认证处理器
ReactiveAuthenticationManager customAuthenticationManager = new CustomAuthenticationManager(tokenStore);
JsonAuthenticationEntryPoint entryPoint = new JsonAuthenticationEntryPoint();
//token转换器
ServerBearerTokenAuthenticationConverter tokenAuthenticationConverter = new ServerBearerTokenAuthenticationConverter();
tokenAuthenticationConverter.setAllowUriQueryParameter(true);
//oauth2认证过滤器
AuthenticationWebFilter oauth2Filter = new AuthenticationWebFilter(customAuthenticationManager);
oauth2Filter.setServerAuthenticationConverter(tokenAuthenticationConverter);
oauth2Filter.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(entryPoint));
oauth2Filter.setAuthenticationSuccessHandler(new Oauth2AuthSuccessHandler());
http.addFilterAt(oauth2Filter, SecurityWebFiltersOrder.AUTHENTICATION);
ServerHttpSecurity.AuthorizeExchangeSpec authorizeExchange = http.authorizeExchange();
if (securityProperties.getAuth().getHttpUrls().length > 0) {
authorizeExchange.pathMatchers(securityProperties.getAuth().getHttpUrls()).authenticated();
}
if (securityProperties.getIgnore().getUrls().length > 0) {
authorizeExchange.pathMatchers(securityProperties.getIgnore().getUrls()).permitAll();
}
authorizeExchange
.pathMatchers(HttpMethod.OPTIONS).permitAll()
.anyExchange()
.access(permissionAuthManager)
.and()
.exceptionHandling()
.accessDeniedHandler(new JsonAccessDeniedHandler())
.authenticationEntryPoint(entryPoint)
.and()
.headers()
.frameOptions()
.disable()
.and()
.httpBasic().disable()
.csrf().disable();
return http.build();
}
}
package com.central.gateway.feign;
import com.central.common.constant.ServiceNameConstants;
import com.central.common.model.SysMenu;
import com.central.gateway.feign.fallback.MenuServiceFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
/**
* @author zlt
*/
@FeignClient(name = ServiceNameConstants.USER_SERVICE, fallbackFactory = MenuServiceFallbackFactory.class, decode404 = true)
public interface MenuService {
/**
* 角色菜单列表
* @param roleCodes
*/
@GetMapping(value = "/menus/{roleCodes}")
List<SysMenu> findByRoleCodes(@PathVariable("roleCodes") String roleCodes);
}
package com.central.gateway.feign.fallback;
import cn.hutool.core.collection.CollectionUtil;
import com.central.gateway.feign.MenuService;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* menuService降级工场
*
* @author zlt
* @date 2019/1/18
*/
@Slf4j
@Component
public class MenuServiceFallbackFactory implements FallbackFactory<MenuService> {
@Override
public MenuService create(Throwable throwable) {
return roleIds -> {
log.error("调用findByRoleCodes异常:{}", roleIds, throwable);
return CollectionUtil.newArrayList();
};
}
}
package com.central.gateway.filter;
import cn.hutool.core.util.StrUtil;
import com.central.common.constant.CommonConstant;
import com.central.common.constant.ConfigConstants;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import java.net.URI;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
/**
* 传递负载均衡隔离值
*
* @author zlt
* @date 2019/10/7
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
@Component
@ConditionalOnProperty(name = ConfigConstants.CONFIG_RIBBON_ISOLATION_ENABLED, havingValue = "true")
public class LbIsolationFilter extends LoadBalancerClientFilter {
public LbIsolationFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
super(loadBalancer, properties);
}
@Override
protected ServiceInstance choose(ServerWebExchange exchange) {
String vresion = exchange.getRequest().getHeaders().getFirst(CommonConstant.Z_L_T_VERSION);
if (StrUtil.isNotEmpty(vresion)) {
if (this.loadBalancer instanceof RibbonLoadBalancerClient) {
RibbonLoadBalancerClient client = (RibbonLoadBalancerClient) this.loadBalancer;
String serviceId = ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost();
return client.choose(serviceId, vresion);
}
}
return super.choose(exchange);
}
}
package com.central.gateway.filter;
import eu.bitwalker.useragentutils.UserAgent;
import com.central.gateway.utils.ReactiveAddrUtil;
import com.central.log.monitor.PointUtil;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Map;
/**
* 请求统计分析埋点过滤器
*
* @author zlt
* @date 2019/10/7
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
@Component
public class RequestStatisticsFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
Map<String, String> headers = request.getHeaders().toSingleValueMap();
UserAgent userAgent = UserAgent.parseUserAgentString(headers.get("User-Agent"));
//埋点
PointUtil.debug("1", "request-statistics",
"ip=" + ReactiveAddrUtil.getRemoteAddr(request)
+ "&browser=" + userAgent.getBrowser()
+ "&operatingSystem=" + userAgent.getOperatingSystem());
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
package com.central.gateway.filter;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import com.central.common.constant.CommonConstant;
import com.central.common.constant.SecurityConstants;
import com.central.log.properties.TraceProperties;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 生成日志链路追踪id,并传入header中
*
* @author zlt
* @date 2019/10/7
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
@Component
public class TraceFilter implements GlobalFilter, Ordered {
@Autowired
private TraceProperties traceProperties;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (traceProperties.getEnable()) {
//链路追踪id
String traceId = IdUtil.fastSimpleUUID();
MDC.put(CommonConstant.LOG_TRACE_ID, traceId);
ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate()
.headers(h -> h.add(CommonConstant.TRACE_ID_HEADER, traceId))
.build();
ServerWebExchange build = exchange.mutate().request(serverHttpRequest).build();
return chain.filter(build);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
package com.central.gateway.route;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.context.ApplicationEventPublisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
/**
* nacos路由数据源
*
* @author zlt
* @date 2019/10/7
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
@Slf4j
public class NacosRouteDefinitionRepository implements RouteDefinitionRepository {
private static final String SCG_DATA_ID = "scg-routes";
private static final String SCG_GROUP_ID = "SCG_GATEWAY";
private ApplicationEventPublisher publisher;
private NacosConfigProperties nacosConfigProperties;
public NacosRouteDefinitionRepository(ApplicationEventPublisher publisher, NacosConfigProperties nacosConfigProperties) {
this.publisher = publisher;
this.nacosConfigProperties = nacosConfigProperties;
addListener();
}
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
try {
String content = nacosConfigProperties.configServiceInstance().getConfig(SCG_DATA_ID, SCG_GROUP_ID,5000);
List<RouteDefinition> routeDefinitions = getListByStr(content);
return Flux.fromIterable(routeDefinitions);
} catch (NacosException e) {
log.error("getRouteDefinitions by nacos error", e);
}
return Flux.fromIterable(CollUtil.newArrayList());
}
/**
* 添加Nacos监听
*/
private void addListener() {
try {
nacosConfigProperties.configServiceInstance().addListener(SCG_DATA_ID, SCG_GROUP_ID, new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
publisher.publishEvent(new RefreshRoutesEvent(this));
}
});
} catch (NacosException e) {
log.error("nacos-addListener-error", e);
}
}
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return null;
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
return null;
}
private List<RouteDefinition> getListByStr(String content) {
if (StrUtil.isNotEmpty(content)) {
return JSONObject.parseArray(content, RouteDefinition.class);
}
return new ArrayList<>(0);
}
}
package com.central.gateway.swagger;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import java.util.HashSet;
import java.util.Set;
/**
* swagger聚合配置
*
* @author zlt
* @date 2019/10/5
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
@Setter
@Getter
@ConfigurationProperties("zlt.swagger-agg")
@RefreshScope
public class SwaggerAggProperties {
/**
* Swagger返回JSON文档的接口路径(全局配置)
*/
private String apiDocsPath = "/v2/api-docs";
/**
* Swagger文档版本(全局配置)
*/
private String swaggerVersion = "2.0";
/**
* 自动生成文档的路由名称,设置了generateRoutes之后,ignoreRoutes不生效
*/
private Set<String> generateRoutes = new HashSet<>();
/**
* 不自动生成文档的路由名称,设置了generateRoutes之后,本配置不生效
*/
private Set<String> ignoreRoutes = new HashSet<>();
/**
* 是否显示该路由
*/
public boolean isShow(String route) {
int generateRoutesSize = generateRoutes.size();
int ignoreRoutesSize = ignoreRoutes.size();
if(generateRoutesSize > 0 && !generateRoutes.contains(route)) {
return false;
}
if(ignoreRoutesSize > 0 && ignoreRoutes.contains(route)) {
return false;
}
return true;
}
}
package com.central.gateway.swagger;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Optional;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;
/**
* @author zlt
* @date 2019/10/5
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
@RestController
@RequestMapping("/swagger-resources" )
public class SwaggerHandler {
private final SwaggerResourcesProvider swaggerResources;
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
@Autowired
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
@GetMapping("/configuration/security" )
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/configuration/ui" )
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
\ No newline at end of file
package com.central.gateway.swagger;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 跨域配置
*
* @author zlt
* @date 2019/10/5
* <p>
* Blog: https://blog.csdn.net/zlt2000
* Github: https://github.com/zlt2000
*/
@Component
@EnableConfigurationProperties(SwaggerAggProperties.class)
@Primary
public class SwaggerProvider implements SwaggerResourcesProvider {
private final RouteLocator routeLocator;
private final GatewayProperties gatewayProperties;
@Resource
private SwaggerAggProperties swaggerAggProperties;
public SwaggerProvider(RouteLocator routeLocator, GatewayProperties gatewayProperties) {
this.routeLocator = routeLocator;
this.gatewayProperties = gatewayProperties;
}
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
Set<String> routes = new HashSet<>();
//取出Spring Cloud Gateway中的route
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
//结合application.yml中的路由配置,只获取有效的route节点
gatewayProperties.getRoutes().stream().filter(
routeDefinition -> (
routes.contains(routeDefinition.getId()) && swaggerAggProperties.isShow(routeDefinition.getId())
)
).forEach(routeDefinition -> routeDefinition.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition -> resources.add(
swaggerResource(
routeDefinition.getId(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("/**", swaggerAggProperties.getApiDocsPath())
)
)
)
);
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion(swaggerAggProperties.getSwaggerVersion());
return swaggerResource;
}
}
\ No newline at end of file
server:
port: 9900
zlt:
nacos:
server-addr: 192.168.28.130:8848
spring:
application:
name: sc-gateway
cloud:
nacos:
config:
server-addr: ${zlt.nacos.server-addr}
file-extension: yml
shared-dataids: common.yml
refreshable-dataids: common.yml
discovery:
server-addr: ${zlt.nacos.server-addr}
\ No newline at end of file
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjmgfejBXsPYynAIPczHA
eEvTDfAVaNKQudyI7VszdezbHDM1CStCIgwiMmLd7QYf1SrrmQoqxhcSRbhjE3ej
RF5qzhtx3kmepdpMrQptcsLjRkixaxCc4E2k6Us5707gGwbhoaTrRit5F2MnAdLY
C1TS3WwnO/hQfqUcAglbK8yrJ4AwAv0DAoIUSWnWqzuniV1SYbdV57uswxUssoWy
sEfPz+nv1ZLRs6Wz4eQ5Myqx2+CjWc9F8iXa2PV8Rmjms3dVbWcLUpCP18Dfzp8l
n8vF9LfYB7UaLSpfJe6FFF6+vCg4JHfo12djTUgwGjauMF3e9mmjU83KIoQS66lp
AQIDAQAB
-----END PUBLIC KEY-----
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册