first commit
This commit is contained in:
64
qingyun-framework/qingyun-core-config/pom.xml
Normal file
64
qingyun-framework/qingyun-core-config/pom.xml
Normal file
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<parent>
|
||||
<groupId>com.qingyun</groupId>
|
||||
<artifactId>qingyun-framework</artifactId>
|
||||
<version>4.7.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>qingyun-core-config</artifactId>
|
||||
<description>系统配置</description>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<!-- SpringBoot Web容器 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- web 容器使用 undertow 性能更强 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- SpringBoot 拦截器 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.codecentric</groupId>
|
||||
<artifactId>spring-boot-admin-starter-client</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 系统模块-->
|
||||
<dependency>
|
||||
<groupId>com.qingyun</groupId>
|
||||
<artifactId>qingyun-common</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,195 @@
|
||||
package com.qingyun.framework.aspectj;
|
||||
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.qingyun.common.annotation.Log;
|
||||
import com.qingyun.common.core.domain.event.OperLogEvent;
|
||||
import com.qingyun.common.enums.BusinessStatus;
|
||||
import com.qingyun.common.enums.HttpMethod;
|
||||
import com.qingyun.common.helper.LoginHelper;
|
||||
import com.qingyun.common.utils.JsonUtils;
|
||||
import com.qingyun.common.utils.ServletUtils;
|
||||
import com.qingyun.common.utils.StringUtils;
|
||||
import com.qingyun.common.utils.spring.SpringUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 操作日志记录处理
|
||||
*
|
||||
* @author jianlu
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
public class LogAspect {
|
||||
|
||||
/**
|
||||
* 排除敏感属性字段
|
||||
*/
|
||||
public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
|
||||
|
||||
/**
|
||||
* 处理完请求后执行
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
*/
|
||||
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
|
||||
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
|
||||
handleLog(joinPoint, controllerLog, null, jsonResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截异常操作
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param e 异常
|
||||
*/
|
||||
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
|
||||
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
|
||||
handleLog(joinPoint, controllerLog, e, null);
|
||||
}
|
||||
|
||||
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
|
||||
try {
|
||||
|
||||
// *========数据库日志=========*//
|
||||
OperLogEvent operLog = new OperLogEvent();
|
||||
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
|
||||
// 请求的地址
|
||||
String ip = ServletUtils.getClientIP();
|
||||
operLog.setOperIp(ip);
|
||||
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
|
||||
operLog.setOperName(LoginHelper.getUsername());
|
||||
|
||||
if (e != null) {
|
||||
operLog.setStatus(BusinessStatus.FAIL.ordinal());
|
||||
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
|
||||
}
|
||||
// 设置方法名称
|
||||
String className = joinPoint.getTarget().getClass().getName();
|
||||
String methodName = joinPoint.getSignature().getName();
|
||||
operLog.setMethod(className + "." + methodName + "()");
|
||||
// 设置请求方式
|
||||
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
|
||||
// 处理设置注解上的参数
|
||||
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
|
||||
// 发布事件保存数据库
|
||||
SpringUtils.context().publishEvent(operLog);
|
||||
} catch (Exception exp) {
|
||||
// 记录本地异常日志
|
||||
log.error("异常信息:{}", exp.getMessage());
|
||||
exp.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注解中对方法的描述信息 用于Controller层注解
|
||||
*
|
||||
* @param log 日志
|
||||
* @param operLog 操作日志
|
||||
* @throws Exception
|
||||
*/
|
||||
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, OperLogEvent operLog, Object jsonResult) throws Exception {
|
||||
// 设置action动作
|
||||
operLog.setBusinessType(log.businessType().ordinal());
|
||||
// 设置标题
|
||||
operLog.setTitle(log.title());
|
||||
// 设置操作人类别
|
||||
operLog.setOperatorType(log.operatorType().ordinal());
|
||||
// 是否需要保存request,参数和值
|
||||
if (log.isSaveRequestData()) {
|
||||
// 获取参数的信息,传入到数据库中。
|
||||
setRequestValue(joinPoint, operLog, log.excludeParamNames());
|
||||
}
|
||||
// 是否需要保存response,参数和值
|
||||
if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) {
|
||||
operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 2000));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的参数,放到log中
|
||||
*
|
||||
* @param operLog 操作日志
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
private void setRequestValue(JoinPoint joinPoint, OperLogEvent operLog, String[] excludeParamNames) throws Exception {
|
||||
Map<String, String> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
|
||||
String requestMethod = operLog.getRequestMethod();
|
||||
if (MapUtil.isEmpty(paramsMap)
|
||||
&& HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
|
||||
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
|
||||
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
|
||||
} else {
|
||||
MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES);
|
||||
MapUtil.removeAny(paramsMap, excludeParamNames);
|
||||
operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 2000));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数拼装
|
||||
*/
|
||||
private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) {
|
||||
StringBuilder params = new StringBuilder();
|
||||
if (paramsArray != null && paramsArray.length > 0) {
|
||||
for (Object o : paramsArray) {
|
||||
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
|
||||
try {
|
||||
String str = JsonUtils.toJsonString(o);
|
||||
Dict dict = JsonUtils.parseMap(str);
|
||||
if (MapUtil.isNotEmpty(dict)) {
|
||||
MapUtil.removeAny(dict, EXCLUDE_PROPERTIES);
|
||||
MapUtil.removeAny(dict, excludeParamNames);
|
||||
str = JsonUtils.toJsonString(dict);
|
||||
}
|
||||
params.append(str).append(" ");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return params.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要过滤的对象。
|
||||
*
|
||||
* @param o 对象信息。
|
||||
* @return 如果是需要过滤的对象,则返回true;否则返回false。
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public boolean isFilterObject(final Object o) {
|
||||
Class<?> clazz = o.getClass();
|
||||
if (clazz.isArray()) {
|
||||
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
|
||||
} else if (Collection.class.isAssignableFrom(clazz)) {
|
||||
Collection collection = (Collection) o;
|
||||
for (Object value : collection) {
|
||||
return value instanceof MultipartFile;
|
||||
}
|
||||
} else if (Map.class.isAssignableFrom(clazz)) {
|
||||
Map map = (Map) o;
|
||||
for (Object value : map.entrySet()) {
|
||||
Map.Entry entry = (Map.Entry) value;
|
||||
return entry.getValue() instanceof MultipartFile;
|
||||
}
|
||||
}
|
||||
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|
||||
|| o instanceof BindingResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.qingyun.framework.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
/**
|
||||
* 程序注解配置
|
||||
*
|
||||
* @author jianlu
|
||||
*/
|
||||
@Configuration
|
||||
// 表示通过aop框架暴露该代理对象,AopContext能够访问
|
||||
@EnableAspectJAutoProxy(exposeProxy = true)
|
||||
public class ApplicationConfig {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.qingyun.framework.config;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import com.qingyun.common.exception.ServiceException;
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
/**
|
||||
* 异步配置
|
||||
*
|
||||
* @author jianlu
|
||||
*/
|
||||
@EnableAsync(proxyTargetClass = true)
|
||||
@Configuration
|
||||
public class AsyncConfig extends AsyncConfigurerSupport {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("scheduledExecutorService")
|
||||
private ScheduledExecutorService scheduledExecutorService;
|
||||
|
||||
/**
|
||||
* 自定义 @Async 注解使用系统线程池
|
||||
*/
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
return scheduledExecutorService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步执行异常处理
|
||||
*/
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return (throwable, method, objects) -> {
|
||||
throwable.printStackTrace();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Exception message - ").append(throwable.getMessage())
|
||||
.append(", Method name - ").append(method.getName());
|
||||
if (ArrayUtil.isNotEmpty(objects)) {
|
||||
sb.append(", Parameter value - ").append(Arrays.toString(objects));
|
||||
}
|
||||
throw new ServiceException(sb.toString());
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.qingyun.framework.config;
|
||||
|
||||
import com.qingyun.common.filter.RepeatableFilter;
|
||||
import com.qingyun.common.filter.XssFilter;
|
||||
import com.qingyun.common.utils.StringUtils;
|
||||
import com.qingyun.framework.config.properties.XssProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Filter配置
|
||||
*
|
||||
* @author jianlu
|
||||
*/
|
||||
@Configuration
|
||||
public class FilterConfig {
|
||||
|
||||
@Autowired
|
||||
private XssProperties xssProperties;
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
|
||||
public FilterRegistrationBean xssFilterRegistration() {
|
||||
FilterRegistrationBean registration = new FilterRegistrationBean();
|
||||
registration.setDispatcherTypes(DispatcherType.REQUEST);
|
||||
registration.setFilter(new XssFilter());
|
||||
registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), StringUtils.SEPARATOR));
|
||||
registration.setName("xssFilter");
|
||||
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
|
||||
Map<String, String> initParameters = new HashMap<String, String>();
|
||||
initParameters.put("excludes", xssProperties.getExcludes());
|
||||
registration.setInitParameters(initParameters);
|
||||
return registration;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Bean
|
||||
public FilterRegistrationBean someFilterRegistration() {
|
||||
FilterRegistrationBean registration = new FilterRegistrationBean();
|
||||
registration.setFilter(new RepeatableFilter());
|
||||
registration.addUrlPatterns("/*");
|
||||
registration.setName("repeatableFilter");
|
||||
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
|
||||
return registration;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.qingyun.framework.config;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 国际化配置
|
||||
*
|
||||
* @author jianlu
|
||||
*/
|
||||
@Configuration
|
||||
public class I18nConfig {
|
||||
|
||||
@Bean
|
||||
public LocaleResolver localeResolver() {
|
||||
return new I18nLocaleResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求头国际化信息
|
||||
*/
|
||||
static class I18nLocaleResolver implements LocaleResolver {
|
||||
|
||||
@Override
|
||||
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
|
||||
String language = httpServletRequest.getHeader("content-language");
|
||||
Locale locale = Locale.getDefault();
|
||||
if (StrUtil.isNotBlank(language)) {
|
||||
String[] split = language.split("_");
|
||||
locale = new Locale(split[0], split[1]);
|
||||
}
|
||||
return locale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.qingyun.framework.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import com.qingyun.framework.jackson.BigNumberSerializer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* jackson 配置
|
||||
*
|
||||
* @author jianlu
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer customizer() {
|
||||
return builder -> {
|
||||
// 全局配置序列化返回 JSON 处理
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
|
||||
javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
|
||||
javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
|
||||
javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
|
||||
builder.modules(javaTimeModule);
|
||||
builder.timeZone(TimeZone.getDefault());
|
||||
log.info("初始化 jackson 配置");
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.qingyun.framework.config;
|
||||
|
||||
import com.qingyun.common.config.QingYunConfig;
|
||||
import com.qingyun.common.constant.Constants;
|
||||
import com.qingyun.framework.interceptor.PlusWebInvokeTimeInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* 通用配置
|
||||
*
|
||||
* @author jianlu
|
||||
*/
|
||||
@Configuration
|
||||
public class ResourcesConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 全局访问性能拦截
|
||||
registry.addInterceptor(new PlusWebInvokeTimeInterceptor());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
/* 本地文件上传路径 */
|
||||
registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**")
|
||||
.addResourceLocations("file:" + QingYunConfig.getProfile() + "/");
|
||||
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨域配置
|
||||
*/
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowCredentials(true);
|
||||
// 设置访问源地址
|
||||
config.addAllowedOriginPattern("*");
|
||||
// 设置访问源请求头
|
||||
config.addAllowedHeader("*");
|
||||
// 设置访问源请求方法
|
||||
config.addAllowedMethod("*");
|
||||
// 有效期 1800秒
|
||||
config.setMaxAge(1800L);
|
||||
// 添加映射路径,拦截一切请求
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
// 返回新的CorsFilter
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.qingyun.framework.config;
|
||||
|
||||
import com.qingyun.common.utils.Threads;
|
||||
import com.qingyun.framework.config.properties.ThreadPoolProperties;
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* 线程池配置
|
||||
*
|
||||
* @author jianlu
|
||||
**/
|
||||
@Configuration
|
||||
public class ThreadPoolConfig {
|
||||
|
||||
/**
|
||||
* 核心线程数 = cpu 核心数 + 1
|
||||
*/
|
||||
private final int core = Runtime.getRuntime().availableProcessors() + 1;
|
||||
|
||||
@Autowired
|
||||
private ThreadPoolProperties threadPoolProperties;
|
||||
|
||||
@Bean(name = "threadPoolTaskExecutor")
|
||||
@ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true")
|
||||
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(core);
|
||||
executor.setMaxPoolSize(core * 2);
|
||||
executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
|
||||
executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行周期性或定时任务
|
||||
*/
|
||||
@Bean(name = "scheduledExecutorService")
|
||||
protected ScheduledExecutorService scheduledExecutorService() {
|
||||
return new ScheduledThreadPoolExecutor(core,
|
||||
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
|
||||
new ThreadPoolExecutor.CallerRunsPolicy()) {
|
||||
@Override
|
||||
protected void afterExecute(Runnable r, Throwable t) {
|
||||
super.afterExecute(r, t);
|
||||
Threads.printException(r, t);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.qingyun.framework.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qingyun.common.annotation.TranslationType;
|
||||
import com.qingyun.common.translation.TranslationInterface;
|
||||
import com.qingyun.common.translation.handler.TranslationBeanSerializerModifier;
|
||||
import com.qingyun.common.translation.handler.TranslationHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 翻译模块配置类
|
||||
*
|
||||
* @author jianlu
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class TranslationConfig {
|
||||
|
||||
@Autowired
|
||||
private List<TranslationInterface<?>> list;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
Map<String, TranslationInterface<?>> map = new HashMap<>(list.size());
|
||||
for (TranslationInterface<?> trans : list) {
|
||||
if (trans.getClass().isAnnotationPresent(TranslationType.class)) {
|
||||
TranslationType annotation = trans.getClass().getAnnotation(TranslationType.class);
|
||||
map.put(annotation.type(), trans);
|
||||
} else {
|
||||
log.warn(trans.getClass().getName() + " 翻译实现类未标注 TranslationType 注解!");
|
||||
}
|
||||
}
|
||||
TranslationHandler.TRANSLATION_MAPPER.putAll(map);
|
||||
// 设置 Bean 序列化修改器
|
||||
objectMapper.setSerializerFactory(
|
||||
objectMapper.getSerializerFactory()
|
||||
.withSerializerModifier(new TranslationBeanSerializerModifier()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.qingyun.framework.config;
|
||||
|
||||
import io.undertow.server.DefaultByteBufferPool;
|
||||
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
|
||||
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Undertow 自定义配置
|
||||
*
|
||||
* @author jianlu
|
||||
*/
|
||||
@Configuration
|
||||
public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
|
||||
|
||||
/**
|
||||
* 设置 Undertow 的 websocket 缓冲池
|
||||
*/
|
||||
@Override
|
||||
public void customize(UndertowServletWebServerFactory factory) {
|
||||
// 默认不直接分配内存 如果项目中使用了 websocket 建议直接分配
|
||||
factory.addDeploymentInfoCustomizers(deploymentInfo -> {
|
||||
WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
|
||||
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 512));
|
||||
deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.qingyun.framework.config;
|
||||
|
||||
import org.hibernate.validator.HibernateValidator;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
|
||||
import javax.validation.Validator;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 校验框架配置类
|
||||
*
|
||||
* @author jianlu
|
||||
*/
|
||||
@Configuration
|
||||
public class ValidatorConfig {
|
||||
|
||||
@Autowired
|
||||
private MessageSource messageSource;
|
||||
|
||||
/**
|
||||
* 配置校验框架 快速返回模式
|
||||
*/
|
||||
//@Bean
|
||||
public Validator validator() {
|
||||
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
|
||||
// 国际化
|
||||
factoryBean.setValidationMessageSource(messageSource);
|
||||
// 设置使用 HibernateValidator 校验器
|
||||
factoryBean.setProviderClass(HibernateValidator.class);
|
||||
Properties properties = new Properties();
|
||||
// 设置 快速异常返回
|
||||
properties.setProperty("hibernate.validator.fail_fast", "true");
|
||||
factoryBean.setValidationProperties(properties);
|
||||
// 加载配置
|
||||
factoryBean.afterPropertiesSet();
|
||||
return factoryBean.getValidator();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.qingyun.framework.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 线程池 配置属性
|
||||
*
|
||||
* @author jianlu
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "thread-pool")
|
||||
public class ThreadPoolProperties {
|
||||
|
||||
/**
|
||||
* 是否开启线程池
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
/**
|
||||
* 队列最大长度
|
||||
*/
|
||||
private int queueCapacity;
|
||||
|
||||
/**
|
||||
* 线程池维护线程所允许的空闲时间
|
||||
*/
|
||||
private int keepAliveSeconds;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.qingyun.framework.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* xss过滤 配置属性
|
||||
*
|
||||
* @author jianlu
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "xss")
|
||||
public class XssProperties {
|
||||
|
||||
/**
|
||||
* 过滤开关
|
||||
*/
|
||||
private String enabled;
|
||||
|
||||
/**
|
||||
* 排除链接(多个用逗号分隔)
|
||||
*/
|
||||
private String excludes;
|
||||
|
||||
/**
|
||||
* 匹配链接
|
||||
*/
|
||||
private String urlPatterns;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.qingyun.framework.encrypt.advice;
|
||||
|
||||
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class DecryptHttpInputMessage implements HttpInputMessage {
|
||||
|
||||
private HttpHeaders headers;
|
||||
private InputStream body;
|
||||
|
||||
|
||||
public DecryptHttpInputMessage(HttpInputMessage inputMessage, String aesKey, boolean showLog) throws Exception {
|
||||
|
||||
this.headers = inputMessage.getHeaders();
|
||||
String content = new BufferedReader(new InputStreamReader(inputMessage.getBody()))
|
||||
.lines().collect(Collectors.joining(System.lineSeparator()));
|
||||
String decryptBody = "";
|
||||
if (content.startsWith("{")) {
|
||||
log.info("原内容:{}", content);
|
||||
decryptBody = content;
|
||||
} else {
|
||||
content = content.replaceAll(" ", "+");
|
||||
if (StringUtils.isNotEmpty(content)) {
|
||||
decryptBody = SecureUtil.aes(aesKey.getBytes()).decryptStr(content);
|
||||
}
|
||||
if (showLog) {
|
||||
log.info("原内容:{},解析结果:{}", content, decryptBody);
|
||||
}
|
||||
}
|
||||
this.body = new ByteArrayInputStream(decryptBody.getBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.qingyun.framework.encrypt.advice;
|
||||
|
||||
|
||||
|
||||
import com.qingyun.framework.encrypt.annotation.Decrypt;
|
||||
import com.qingyun.framework.encrypt.config.SecretKeyConfig;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
@Slf4j
|
||||
@ControllerAdvice
|
||||
public class EncryptRequestBodyAdvice implements RequestBodyAdvice {
|
||||
|
||||
|
||||
private boolean encrypt;
|
||||
|
||||
@Autowired
|
||||
private SecretKeyConfig secretKeyConfig;
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
encrypt = false;
|
||||
if (methodParameter.getMethod().isAnnotationPresent(Decrypt.class) && secretKeyConfig.isOpen()) {
|
||||
encrypt = true;
|
||||
} else if (methodParameter.getDeclaringClass().isAnnotationPresent(Decrypt.class) && secretKeyConfig.isOpen()) {
|
||||
encrypt = true;
|
||||
}
|
||||
return encrypt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
|
||||
Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
if (encrypt) {
|
||||
try {
|
||||
return new DecryptHttpInputMessage(inputMessage, secretKeyConfig.getAesKey(), secretKeyConfig.isShowLog());
|
||||
} catch (Exception e) {
|
||||
log.error("解析失败", e);
|
||||
}
|
||||
}
|
||||
return inputMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
|
||||
Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
return body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.qingyun.framework.encrypt.advice;
|
||||
|
||||
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qingyun.framework.encrypt.annotation.Encrypt;
|
||||
import com.qingyun.framework.encrypt.config.SecretKeyConfig;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@ControllerAdvice
|
||||
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
|
||||
|
||||
private boolean encrypt;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private SecretKeyConfig secretKeyConfig;
|
||||
|
||||
private static ThreadLocal<Boolean> encryptLocal = new ThreadLocal<>();
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
encrypt = false;
|
||||
if (returnType.getMethod().isAnnotationPresent(Encrypt.class) && secretKeyConfig.isOpen()) {
|
||||
encrypt = true;
|
||||
} else if (returnType.getDeclaringClass().isAnnotationPresent(Encrypt.class) && secretKeyConfig.isOpen()) {
|
||||
encrypt = true;
|
||||
}
|
||||
return encrypt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
|
||||
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
|
||||
// EncryptResponseBodyAdvice.setEncryptStatus(false);
|
||||
// Dynamic Settings Not Encrypted
|
||||
if (body == null) {
|
||||
return null;
|
||||
}
|
||||
response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
|
||||
if (encrypt) {
|
||||
try {
|
||||
String content = mapper.writeValueAsString(body);
|
||||
String encodedData = SecureUtil.aes(secretKeyConfig.getAesKey().getBytes()).encryptHex(content);
|
||||
if (secretKeyConfig.isShowLog()) {
|
||||
log.info("返回前数据:{},返回后数据:{}", content, encodedData);
|
||||
}
|
||||
return encodedData;
|
||||
} catch (Exception e) {
|
||||
log.error("Encrypted data exception", e);
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.qingyun.framework.encrypt.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Decrypt {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.qingyun.framework.encrypt.annotation;
|
||||
|
||||
|
||||
import com.qingyun.framework.encrypt.advice.EncryptRequestBodyAdvice;
|
||||
import com.qingyun.framework.encrypt.advice.EncryptResponseBodyAdvice;
|
||||
import com.qingyun.framework.encrypt.config.SecretKeyConfig;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
@Import({SecretKeyConfig.class,
|
||||
EncryptResponseBodyAdvice.class,
|
||||
EncryptRequestBodyAdvice.class})
|
||||
public @interface EnableSecurity {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.qingyun.framework.encrypt.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Encrypt {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.qingyun.framework.encrypt.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "encrypt.body")
|
||||
@Configuration
|
||||
public class SecretKeyConfig {
|
||||
|
||||
private String aesKey;
|
||||
|
||||
private String charset = "UTF-8";
|
||||
|
||||
private boolean open = true;
|
||||
|
||||
private boolean showLog = false;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.qingyun.framework.encrypt.util;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
|
||||
public class Base64Util {
|
||||
|
||||
/**
|
||||
* Decoding to binary
|
||||
*
|
||||
* @param base64 base64
|
||||
* @return byte
|
||||
* @throws Exception Exception
|
||||
*/
|
||||
public static byte[] decode(String base64) throws Exception {
|
||||
return Base64.decodeBase64(base64);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary encoding as a string
|
||||
*
|
||||
* @param bytes byte
|
||||
* @return String
|
||||
* @throws Exception Exception
|
||||
*/
|
||||
public static String encode(byte[] bytes) throws Exception {
|
||||
return new String(Base64.encodeBase64(bytes));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.qingyun.framework.encrypt.util;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
|
||||
public class RSAUtil {
|
||||
|
||||
/**
|
||||
* encryption algorithm RSA
|
||||
*/
|
||||
public static final String KEY_ALGORITHM = "RSA";
|
||||
|
||||
/**
|
||||
* RSA Maximum Encrypted Plaintext Size
|
||||
*/
|
||||
private static final int MAX_ENCRYPT_BLOCK = 117;
|
||||
|
||||
/**
|
||||
* RSA Maximum decrypted ciphertext size
|
||||
*/
|
||||
private static final int MAX_DECRYPT_BLOCK = 256;
|
||||
|
||||
/**
|
||||
* encryption
|
||||
*
|
||||
* @param data data
|
||||
* @param publicKey publicKey
|
||||
* @return byte
|
||||
* @throws Exception Exception
|
||||
*/
|
||||
public static byte[] encrypt(byte[] data, String publicKey)
|
||||
throws Exception {
|
||||
byte[] keyBytes = Base64Util.decode(publicKey);
|
||||
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
|
||||
Key publicK = keyFactory.generatePublic(x509KeySpec);
|
||||
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicK);
|
||||
int inputLen = data.length;
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
int offSet = 0;
|
||||
byte[] cache;
|
||||
int i = 0;
|
||||
// Sectional Encryption of Data
|
||||
while (inputLen - offSet > 0) {
|
||||
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
|
||||
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
|
||||
} else {
|
||||
cache = cipher.doFinal(data, offSet, inputLen - offSet);
|
||||
}
|
||||
out.write(cache, 0, cache.length);
|
||||
i++;
|
||||
offSet = i * MAX_ENCRYPT_BLOCK;
|
||||
}
|
||||
byte[] encryptedData = out.toByteArray();
|
||||
out.close();
|
||||
return encryptedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt
|
||||
*
|
||||
* @param text text
|
||||
* @param privateKey privateKey
|
||||
* @return byte
|
||||
* @throws Exception Exception
|
||||
*/
|
||||
public static byte[] decrypt(byte[] text, String privateKey)
|
||||
throws Exception {
|
||||
byte[] keyBytes = Base64Util.decode(privateKey);
|
||||
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
|
||||
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
|
||||
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateK);
|
||||
int inputLen = text.length;
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
int offSet = 0;
|
||||
byte[] cache;
|
||||
int i = 0;
|
||||
// Sectional Encryption of Data
|
||||
while (inputLen - offSet > 0) {
|
||||
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
|
||||
cache = cipher.doFinal(text, offSet, MAX_DECRYPT_BLOCK);
|
||||
} else {
|
||||
cache = cipher.doFinal(text, offSet, inputLen - offSet);
|
||||
}
|
||||
out.write(cache, 0, cache.length);
|
||||
i++;
|
||||
offSet = i * MAX_DECRYPT_BLOCK;
|
||||
}
|
||||
byte[] decryptedData = out.toByteArray();
|
||||
out.close();
|
||||
return decryptedData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.qingyun.framework.interceptor;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import com.qingyun.common.filter.RepeatedlyRequestWrapper;
|
||||
import com.qingyun.common.utils.JsonUtils;
|
||||
import com.qingyun.common.utils.StringUtils;
|
||||
import com.qingyun.common.utils.spring.SpringUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.time.StopWatch;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.BufferedReader;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* web的调用时间统计拦截器
|
||||
* dev环境有效
|
||||
*
|
||||
* @author jianlu
|
||||
* @since 3.3.0
|
||||
*/
|
||||
@Slf4j
|
||||
public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final String prodProfile = "prod";
|
||||
|
||||
private final TransmittableThreadLocal<StopWatch> invokeTimeTL = new TransmittableThreadLocal<>();
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
if (!prodProfile.equals(SpringUtils.getActiveProfile())) {
|
||||
String url = request.getMethod() + " " + request.getRequestURI();
|
||||
|
||||
// 打印请求参数
|
||||
if (isJsonRequest(request)) {
|
||||
String jsonParam = "";
|
||||
if (request instanceof RepeatedlyRequestWrapper) {
|
||||
BufferedReader reader = request.getReader();
|
||||
jsonParam = IoUtil.read(reader);
|
||||
}
|
||||
log.debug("[PLUS]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam);
|
||||
} else {
|
||||
Map<String, String[]> parameterMap = request.getParameterMap();
|
||||
if (MapUtil.isNotEmpty(parameterMap)) {
|
||||
String parameters = JsonUtils.toJsonString(parameterMap);
|
||||
log.debug("[PLUS]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, parameters);
|
||||
} else {
|
||||
log.debug("[PLUS]开始请求 => URL[{}],无参数", url);
|
||||
}
|
||||
}
|
||||
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
invokeTimeTL.set(stopWatch);
|
||||
stopWatch.start();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
if (!prodProfile.equals(SpringUtils.getActiveProfile())) {
|
||||
StopWatch stopWatch = invokeTimeTL.get();
|
||||
stopWatch.stop();
|
||||
log.debug("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime());
|
||||
invokeTimeTL.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断本次请求的数据类型是否为json
|
||||
*
|
||||
* @param request request
|
||||
* @return boolean
|
||||
*/
|
||||
private boolean isJsonRequest(HttpServletRequest request) {
|
||||
String contentType = request.getContentType();
|
||||
if (contentType != null) {
|
||||
return StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.qingyun.framework.jackson;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
|
||||
import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 超出 JS 最大最小值 处理
|
||||
*
|
||||
* @author jianlu
|
||||
*/
|
||||
@JacksonStdImpl
|
||||
public class BigNumberSerializer extends NumberSerializer {
|
||||
|
||||
/**
|
||||
* 根据 JS Number.MAX_SAFE_INTEGER 与 Number.MIN_SAFE_INTEGER 得来
|
||||
*/
|
||||
private static final long MAX_SAFE_INTEGER = 9007199254740991L;
|
||||
private static final long MIN_SAFE_INTEGER = -9007199254740991L;
|
||||
|
||||
/**
|
||||
* 提供实例
|
||||
*/
|
||||
public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class);
|
||||
|
||||
public BigNumberSerializer(Class<? extends Number> rawType) {
|
||||
super(rawType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {
|
||||
// 超出范围 序列化位字符串
|
||||
if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
|
||||
super.serialize(value, gen, provider);
|
||||
} else {
|
||||
gen.writeString(value.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.qingyun.framework.manager;
|
||||
|
||||
import com.qingyun.common.utils.Threads;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
/**
|
||||
* 确保应用退出时能关闭后台线程
|
||||
*
|
||||
* @author jianlu
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ShutdownManager {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("scheduledExecutorService")
|
||||
private ScheduledExecutorService scheduledExecutorService;
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
shutdownAsyncManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止异步执行任务
|
||||
*/
|
||||
private void shutdownAsyncManager() {
|
||||
try {
|
||||
log.info("====关闭后台任务任务线程池====");
|
||||
Threads.shutdownAndAwaitTermination(scheduledExecutorService);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user