1、结构调整

2、抽象工厂优化
This commit is contained in:
2026-03-31 19:35:21 +08:00
parent 87757b352c
commit ebdbdbeb41
667 changed files with 1240 additions and 50173 deletions

View File

@@ -2,18 +2,21 @@
<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.njcn</groupId>
<artifactId>msgpush-framework</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>msgpush-spring-boot-starter-web</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>Web 框架全局异常、API 日志、脱敏、错误码等</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
@@ -86,4 +89,4 @@
</dependency>
</dependencies>
</project>
</project>

View File

@@ -2,10 +2,10 @@ package com.njcn.msgpush.framework.apilog.config;
import com.njcn.msgpush.framework.apilog.core.filter.ApiAccessLogFilter;
import com.njcn.msgpush.framework.apilog.core.interceptor.ApiAccessLogInterceptor;
import com.njcn.msgpush.framework.common.biz.infra.logger.ApiAccessLogCommonApi;
import com.njcn.msgpush.framework.common.biz.system.logger.ApiAccessLogCommonApi;
import com.njcn.msgpush.framework.common.enums.WebFilterOrderEnum;
import com.njcn.msgpush.framework.web.config.WebProperties;
import com.njcn.msgpush.framework.web.config.MsgpushWebAutoConfiguration;
import com.njcn.msgpush.framework.web.config.WebProperties;
import jakarta.servlet.Filter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;

View File

@@ -1,7 +1,7 @@
package com.njcn.msgpush.framework.apilog.config;
import com.njcn.msgpush.framework.common.biz.infra.logger.ApiAccessLogCommonApi;
import com.njcn.msgpush.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
import com.njcn.msgpush.framework.common.biz.system.logger.ApiAccessLogCommonApi;
import com.njcn.msgpush.framework.common.biz.system.logger.ApiErrorLogCommonApi;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;

View File

@@ -6,7 +6,7 @@ import lombok.Getter;
/**
* 操作日志的操作类型
*
* @author ruoyi
* @author hongawen
*/
@Getter
@AllArgsConstructor

View File

@@ -7,10 +7,11 @@ import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.njcn.msgpush.framework.apilog.core.annotation.ApiAccessLog;
import com.njcn.msgpush.framework.apilog.core.enums.OperateTypeEnum;
import com.njcn.msgpush.framework.common.biz.infra.logger.ApiAccessLogCommonApi;
import com.njcn.msgpush.framework.common.biz.infra.logger.dto.ApiAccessLogCreateReqDTO;
import com.njcn.msgpush.framework.common.biz.system.logger.ApiAccessLogCommonApi;
import com.njcn.msgpush.framework.common.biz.system.logger.dto.ApiAccessLogCreateReqDTO;
import com.njcn.msgpush.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.util.json.JsonUtils;
@@ -19,7 +20,6 @@ import com.njcn.msgpush.framework.common.util.servlet.ServletUtils;
import com.njcn.msgpush.framework.web.config.WebProperties;
import com.njcn.msgpush.framework.web.core.filter.ApiRequestFilter;
import com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.FilterChain;

View File

@@ -1,38 +1,21 @@
package com.njcn.msgpush.framework.apilog.core.interceptor;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.StrUtil;
import com.njcn.msgpush.framework.common.util.servlet.ServletUtils;
import com.njcn.msgpush.framework.common.util.spring.SpringUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.IntStream;
/**
* API 访问日志 Interceptor
*
* 目的:在非 prod 环境时,打印 request 和 response 两条日志到日志文件(控制台)中。
* 目的:记录 HandlerMethod提供给 ApiAccessLogFilter 使用
*
* @author hongawen
*/
@Slf4j
public class ApiAccessLogInterceptor implements HandlerInterceptor {
public static final String ATTRIBUTE_HANDLER_METHOD = "HANDLER_METHOD";
private static final String ATTRIBUTE_STOP_WATCH = "ApiAccessLogInterceptor.StopWatch";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 记录 HandlerMethod提供给 ApiAccessLogFilter 使用
@@ -40,64 +23,11 @@ public class ApiAccessLogInterceptor implements HandlerInterceptor {
if (handlerMethod != null) {
request.setAttribute(ATTRIBUTE_HANDLER_METHOD, handlerMethod);
}
// 打印 request 日志
if (!SpringUtils.isProd()) {
Map<String, String> queryString = ServletUtils.getParamMap(request);
String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : null;
if (CollUtil.isEmpty(queryString) && StrUtil.isEmpty(requestBody)) {
log.info("[preHandle][开始请求 URL({}) 无参数]", request.getRequestURI());
} else {
log.info("[preHandle][开始请求 URL({}) 参数({})]", request.getRequestURI(),
StrUtil.blankToDefault(requestBody, queryString.toString()));
}
// 计时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
request.setAttribute(ATTRIBUTE_STOP_WATCH, stopWatch);
// 打印 Controller 路径
printHandlerMethodPosition(handlerMethod);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 打印 response 日志
if (!SpringUtils.isProd()) {
StopWatch stopWatch = (StopWatch) request.getAttribute(ATTRIBUTE_STOP_WATCH);
stopWatch.stop();
log.info("[afterCompletion][完成请求 URL({}) 耗时({} ms)]",
request.getRequestURI(), stopWatch.getTotalTimeMillis());
}
}
/**
* 打印 Controller 方法路径
*/
private void printHandlerMethodPosition(HandlerMethod handlerMethod) {
if (handlerMethod == null) {
return;
}
Method method = handlerMethod.getMethod();
Class<?> clazz = method.getDeclaringClass();
try {
// 获取 method 的 lineNumber
List<String> clazzContents = FileUtil.readUtf8Lines(
ResourceUtil.getResource(null, clazz).getPath().replace("/target/classes/", "/src/main/java/")
+ clazz.getSimpleName() + ".java");
Optional<Integer> lineNumber = IntStream.range(0, clazzContents.size())
.filter(i -> clazzContents.get(i).contains(" " + method.getName() + "(")) // 简单匹配,不考虑方法重名
.mapToObj(i -> i + 1) // 行号从 1 开始
.findFirst();
if (!lineNumber.isPresent()) {
return;
}
// 打印结果
System.out.printf("\tController 方法路径:%s(%s.java:%d)\n", clazz.getName(), clazz.getSimpleName(), lineNumber.get());
} catch (Exception ignore) {
// 忽略异常。原因:仅仅打印,非重要逻辑
}
}
}

View File

@@ -1,9 +1,9 @@
package com.njcn.msgpush.framework.desensitize.core.base.annotation;
import com.njcn.msgpush.framework.desensitize.core.base.handler.DesensitizationHandler;
import com.njcn.msgpush.framework.desensitize.core.base.serializer.StringDesensitizeSerializer;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.njcn.msgpush.framework.desensitize.core.base.handler.DesensitizationHandler;
import com.njcn.msgpush.framework.desensitize.core.base.serializer.StringDesensitizeSerializer;
import java.lang.annotation.*;

View File

@@ -5,14 +5,14 @@ import cn.hutool.core.lang.Singleton;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.njcn.msgpush.framework.desensitize.core.base.annotation.DesensitizeBy;
import com.njcn.msgpush.framework.desensitize.core.base.handler.DesensitizationHandler;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.njcn.msgpush.framework.desensitize.core.base.annotation.DesensitizeBy;
import com.njcn.msgpush.framework.desensitize.core.base.handler.DesensitizationHandler;
import lombok.Getter;
import lombok.Setter;

View File

@@ -1,8 +1,8 @@
package com.njcn.msgpush.framework.desensitize.core.regex.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.njcn.msgpush.framework.desensitize.core.base.annotation.DesensitizeBy;
import com.njcn.msgpush.framework.desensitize.core.regex.handler.EmailDesensitizationHandler;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.*;

View File

@@ -1,8 +1,8 @@
package com.njcn.msgpush.framework.desensitize.core.regex.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.njcn.msgpush.framework.desensitize.core.base.annotation.DesensitizeBy;
import com.njcn.msgpush.framework.desensitize.core.regex.handler.DefaultRegexDesensitizationHandler;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.*;

View File

@@ -1,8 +1,8 @@
package com.njcn.msgpush.framework.desensitize.core.slider.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.njcn.msgpush.framework.desensitize.core.base.annotation.DesensitizeBy;
import com.njcn.msgpush.framework.desensitize.core.slider.handler.BankCardDesensitization;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.*;

View File

@@ -1,8 +1,8 @@
package com.njcn.msgpush.framework.desensitize.core.slider.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.njcn.msgpush.framework.desensitize.core.base.annotation.DesensitizeBy;
import com.njcn.msgpush.framework.desensitize.core.slider.handler.CarLicenseDesensitization;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.*;

View File

@@ -1,8 +1,8 @@
package com.njcn.msgpush.framework.desensitize.core.slider.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.njcn.msgpush.framework.desensitize.core.base.annotation.DesensitizeBy;
import com.njcn.msgpush.framework.desensitize.core.slider.handler.ChineseNameDesensitization;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.*;

View File

@@ -1,8 +1,8 @@
package com.njcn.msgpush.framework.desensitize.core.slider.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.njcn.msgpush.framework.desensitize.core.base.annotation.DesensitizeBy;
import com.njcn.msgpush.framework.desensitize.core.slider.handler.FixedPhoneDesensitization;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.*;

View File

@@ -1,8 +1,8 @@
package com.njcn.msgpush.framework.desensitize.core.slider.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.njcn.msgpush.framework.desensitize.core.base.annotation.DesensitizeBy;
import com.njcn.msgpush.framework.desensitize.core.slider.handler.IdCardDesensitization;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.*;

View File

@@ -1,8 +1,8 @@
package com.njcn.msgpush.framework.desensitize.core.slider.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.njcn.msgpush.framework.desensitize.core.base.annotation.DesensitizeBy;
import com.njcn.msgpush.framework.desensitize.core.slider.handler.MobileDesensitization;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.*;

View File

@@ -1,8 +1,8 @@
package com.njcn.msgpush.framework.desensitize.core.slider.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.njcn.msgpush.framework.desensitize.core.base.annotation.DesensitizeBy;
import com.njcn.msgpush.framework.desensitize.core.slider.handler.PasswordDesensitization;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.*;

View File

@@ -1,8 +1,8 @@
package com.njcn.msgpush.framework.desensitize.core.slider.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.njcn.msgpush.framework.desensitize.core.base.annotation.DesensitizeBy;
import com.njcn.msgpush.framework.desensitize.core.slider.handler.DefaultDesensitizationHandler;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.*;

View File

@@ -63,8 +63,9 @@ public class ApiEncryptProperties {
* 注意:
* 1. 如果是【对称加密】时,它「后端」对应的是“密钥”。对应的,「前端」也对应的也是“密钥”。
* 2. 如果是【非对称加密】时,它「后端」对应的是“公钥”。对应的,「前端」对应的是“私钥”。(重要!!!)
*
* 当前 system 模块密码加密方案不启用响应加密,因此该配置可为空。
*/
@NotEmpty(message = "响应的加密密钥不能为空")
private String responseKey;
}

View File

@@ -4,6 +4,7 @@ import com.njcn.msgpush.framework.common.enums.WebFilterOrderEnum;
import com.njcn.msgpush.framework.encrypt.core.filter.ApiEncryptFilter;
import com.njcn.msgpush.framework.web.config.WebProperties;
import com.njcn.msgpush.framework.web.core.handler.GlobalExceptionHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -24,9 +25,10 @@ public class MsgpushApiEncryptAutoConfiguration {
public FilterRegistrationBean<ApiEncryptFilter> apiEncryptFilter(WebProperties webProperties,
ApiEncryptProperties apiEncryptProperties,
RequestMappingHandlerMapping requestMappingHandlerMapping,
GlobalExceptionHandler globalExceptionHandler) {
GlobalExceptionHandler globalExceptionHandler,
ObjectMapper objectMapper) {
ApiEncryptFilter filter = new ApiEncryptFilter(webProperties, apiEncryptProperties,
requestMappingHandlerMapping, globalExceptionHandler);
requestMappingHandlerMapping, globalExceptionHandler, objectMapper);
return createFilterBean(filter, WebFilterOrderEnum.API_ENCRYPT_FILTER);
}

View File

@@ -15,6 +15,13 @@ public @interface ApiEncrypt {
*/
boolean request() default true;
/**
* 需要解密的请求字段
*
* 仅在 request = true 时生效
*/
String[] requestFields() default {};
/**
* 是否对响应结果进行加密,默认 true
*/

View File

@@ -1,7 +1,11 @@
package com.njcn.msgpush.framework.encrypt.core.filter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import cn.hutool.crypto.asymmetric.AsymmetricDecryptor;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.symmetric.SymmetricDecryptor;
@@ -14,6 +18,9 @@ import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import static com.njcn.msgpush.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
/**
* 解密请求 {@link HttpServletRequestWrapper} 实现类
@@ -25,16 +32,41 @@ public class ApiDecryptRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public ApiDecryptRequestWrapper(HttpServletRequest request,
ObjectMapper objectMapper,
List<String> requestFields,
SymmetricDecryptor symmetricDecryptor,
AsymmetricDecryptor asymmetricDecryptor) throws IOException {
super(request);
// 读取 body允许 HEX、BASE64 传输
String requestBody = StrUtil.utf8Str(
IoUtil.readBytes(request.getInputStream(), false));
if (CollUtil.isEmpty(requestFields)) {
throw invalidParamException("请求解密失败,请刷新页面后重试");
}
String requestBody = StrUtil.utf8Str(IoUtil.readBytes(request.getInputStream(), false));
if (StrUtil.isBlank(requestBody)) {
throw invalidParamException("请求解密失败,请刷新页面后重试");
}
// 解密 body
body = symmetricDecryptor != null ? symmetricDecryptor.decrypt(requestBody)
: asymmetricDecryptor.decrypt(requestBody, KeyType.PrivateKey);
JsonNode requestJson;
try {
requestJson = objectMapper.readTree(requestBody);
} catch (Exception ex) {
throw invalidParamException("请求解密失败,请刷新页面后重试");
}
if (!(requestJson instanceof ObjectNode requestObject)) {
throw invalidParamException("请求解密失败,请刷新页面后重试");
}
for (String requestField : requestFields) {
JsonNode fieldNode = requestObject.get(requestField);
if (fieldNode == null || fieldNode.isNull() || !fieldNode.isTextual() || StrUtil.isBlank(fieldNode.asText())) {
throw invalidParamException("请求解密失败,请刷新页面后重试");
}
byte[] decryptedBytes = symmetricDecryptor != null
? symmetricDecryptor.decrypt(fieldNode.asText())
: asymmetricDecryptor.decrypt(fieldNode.asText(), KeyType.PrivateKey);
requestObject.put(requestField, StrUtil.utf8Str(decryptedBytes));
}
body = objectMapper.writeValueAsBytes(requestObject);
}
@Override

View File

@@ -6,6 +6,7 @@ import cn.hutool.crypto.asymmetric.AsymmetricDecryptor;
import cn.hutool.crypto.asymmetric.AsymmetricEncryptor;
import cn.hutool.crypto.symmetric.SymmetricDecryptor;
import cn.hutool.crypto.symmetric.SymmetricEncryptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.util.object.ObjectUtils;
import com.njcn.msgpush.framework.common.util.servlet.ServletUtils;
@@ -26,6 +27,7 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
import org.springframework.web.util.ServletRequestPathUtils;
import java.io.IOException;
import java.util.Arrays;
import static com.njcn.msgpush.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
@@ -43,12 +45,17 @@ import static com.njcn.msgpush.framework.common.exception.util.ServiceExceptionU
@Slf4j
public class ApiEncryptFilter extends ApiRequestFilter {
private static final String MISSING_ENCRYPT_HEADER_MESSAGE = "当前接口要求加密传输,请刷新页面后重试";
private static final String DECRYPT_FAILED_MESSAGE = "请求解密失败,请刷新页面后重试";
private final ApiEncryptProperties apiEncryptProperties;
private final RequestMappingHandlerMapping requestMappingHandlerMapping;
private final GlobalExceptionHandler globalExceptionHandler;
private final ObjectMapper objectMapper;
private final SymmetricDecryptor requestSymmetricDecryptor;
private final AsymmetricDecryptor requestAsymmetricDecryptor;
@@ -58,21 +65,25 @@ public class ApiEncryptFilter extends ApiRequestFilter {
public ApiEncryptFilter(WebProperties webProperties,
ApiEncryptProperties apiEncryptProperties,
RequestMappingHandlerMapping requestMappingHandlerMapping,
GlobalExceptionHandler globalExceptionHandler) {
GlobalExceptionHandler globalExceptionHandler,
ObjectMapper objectMapper) {
super(webProperties);
this.apiEncryptProperties = apiEncryptProperties;
this.requestMappingHandlerMapping = requestMappingHandlerMapping;
this.globalExceptionHandler = globalExceptionHandler;
this.objectMapper = objectMapper;
if (StrUtil.equalsIgnoreCase(apiEncryptProperties.getAlgorithm(), "AES")) {
this.requestSymmetricDecryptor = SecureUtil.aes(StrUtil.utf8Bytes(apiEncryptProperties.getRequestKey()));
this.requestAsymmetricDecryptor = null;
this.responseSymmetricEncryptor = SecureUtil.aes(StrUtil.utf8Bytes(apiEncryptProperties.getResponseKey()));
this.responseSymmetricEncryptor = StrUtil.isNotBlank(apiEncryptProperties.getResponseKey())
? SecureUtil.aes(StrUtil.utf8Bytes(apiEncryptProperties.getResponseKey())) : null;
this.responseAsymmetricEncryptor = null;
} else if (StrUtil.equalsIgnoreCase(apiEncryptProperties.getAlgorithm(), "RSA")) {
this.requestSymmetricDecryptor = null;
this.requestAsymmetricDecryptor = SecureUtil.rsa(apiEncryptProperties.getRequestKey(), null);
this.responseSymmetricEncryptor = null;
this.responseAsymmetricEncryptor = SecureUtil.rsa(null, apiEncryptProperties.getResponseKey());
this.responseAsymmetricEncryptor = StrUtil.isNotBlank(apiEncryptProperties.getResponseKey())
? SecureUtil.rsa(null, apiEncryptProperties.getResponseKey()) : null;
} else {
// 补充说明:如果要支持 SM2、SM4 等算法,可在此处增加对应实例的创建,并添加相应的 Maven 依赖即可。
throw new IllegalArgumentException("不支持的加密算法:" + apiEncryptProperties.getAlgorithm());
@@ -86,9 +97,10 @@ public class ApiEncryptFilter extends ApiRequestFilter {
// 获取 @ApiEncrypt 注解
ApiEncrypt apiEncrypt = getApiEncrypt(request);
boolean requestEnable = apiEncrypt != null && apiEncrypt.request();
boolean responseEnable = apiEncrypt != null && apiEncrypt.response();
boolean responseEnable = apiEncrypt != null && apiEncrypt.response()
&& (responseSymmetricEncryptor != null || responseAsymmetricEncryptor != null);
String encryptHeader = request.getHeader(apiEncryptProperties.getHeader());
if (!requestEnable && !responseEnable && StrUtil.isBlank(encryptHeader)) {
if (!requestEnable && !responseEnable) {
chain.doFilter(request, response);
return;
}
@@ -97,13 +109,19 @@ public class ApiEncryptFilter extends ApiRequestFilter {
if (ObjectUtils.equalsAny(HttpMethod.valueOf(request.getMethod()),
HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE)) {
try {
if (StrUtil.isNotBlank(encryptHeader)) {
request = new ApiDecryptRequestWrapper(request,
if (requestEnable) {
if (StrUtil.isBlank(encryptHeader)) {
throw invalidParamException(MISSING_ENCRYPT_HEADER_MESSAGE);
}
request = new ApiDecryptRequestWrapper(request, objectMapper,
Arrays.asList(apiEncrypt.requestFields()),
requestSymmetricDecryptor, requestAsymmetricDecryptor);
} else if (requestEnable) {
throw invalidParamException("请求未包含加密标头,请检查是否正确配置了加密标头");
}
} catch (Exception ex) {
if (!(ex instanceof com.njcn.msgpush.framework.common.exception.ServiceException)
|| !StrUtil.equals(ex.getMessage(), MISSING_ENCRYPT_HEADER_MESSAGE)) {
ex = invalidParamException(DECRYPT_FAILED_MESSAGE);
}
CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);
ServletUtils.writeJSON(response, result);
return;

View File

@@ -1,9 +1,5 @@
package com.njcn.msgpush.framework.jackson.config;
import com.njcn.msgpush.framework.common.util.json.JsonUtils;
import com.njcn.msgpush.framework.common.util.json.databind.NumberSerializer;
import com.njcn.msgpush.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer;
import com.njcn.msgpush.framework.common.util.json.databind.TimestampLocalDateTimeSerializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
@@ -11,6 +7,10 @@ import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.njcn.msgpush.framework.common.util.json.JsonUtils;
import com.njcn.msgpush.framework.common.util.json.databind.NumberSerializer;
import com.njcn.msgpush.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer;
import com.njcn.msgpush.framework.common.util.json.databind.TimestampLocalDateTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;

View File

@@ -1,4 +0,0 @@
/**
* Web 框架全局异常、API 日志等
*/
package com.njcn.msgpush.framework;

View File

@@ -6,7 +6,6 @@ import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.media.IntegerSchema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.security.SecurityRequirement;
@@ -34,41 +33,24 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* Swagger 自动配置类,基于 OpenAPI + Springdoc 实现。
*
* 友情提示:
* 1. Springdoc 文档地址:<a href="https://github.com/springdoc/springdoc-openapi">仓库</a>
* 2. Swagger 规范,于 2015 更名为 OpenAPI 规范,本质是一个东西
*
* @author hongawen
*/
@AutoConfiguration(before = Knife4jAutoConfiguration.class) // before 原因,保证覆写的 Knife4jOpenApiCustomizer 先生效!相关 https://github.com/YunaiV/ruoyi-vue-pro/issues/954 讨论
@AutoConfiguration(before = Knife4jAutoConfiguration.class)
@ConditionalOnClass({OpenAPI.class})
@EnableConfigurationProperties(SwaggerProperties.class)
@ConditionalOnProperty(prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = true) // 设置为 false 时,禁用
@ConditionalOnProperty(prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = true)
@Import(Knife4jOpenApiCustomizer.class)
public class MsgpushSwaggerAutoConfiguration {
// ========== 全局 OpenAPI 配置 ==========
@Bean
public OpenAPI createApi(SwaggerProperties properties) {
Map<String, SecurityScheme> securitySchemas = buildSecuritySchemes();
OpenAPI openAPI = new OpenAPI()
// 接口信息
.info(buildInfo(properties))
// 接口安全配置
.components(new Components().securitySchemes(securitySchemas))
.addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION));
securitySchemas.keySet().forEach(key -> openAPI.addSecurityItem(new SecurityRequirement().addList(key)));
return openAPI;
}
/**
* API 摘要信息
*/
private Info buildInfo(SwaggerProperties properties) {
return new Info()
.title(properties.getTitle())
@@ -78,24 +60,18 @@ public class MsgpushSwaggerAutoConfiguration {
.license(new License().name(properties.getLicense()).url(properties.getLicenseUrl()));
}
/**
* 安全模式,这里配置通过请求头 Authorization 传递 token 参数
*/
private Map<String, SecurityScheme> buildSecuritySchemes() {
Map<String, SecurityScheme> securitySchemes = new HashMap<>();
SecurityScheme securityScheme = new SecurityScheme()
.type(SecurityScheme.Type.APIKEY) // 类型
.name(HttpHeaders.AUTHORIZATION) // 请求头的 name
.in(SecurityScheme.In.HEADER); // token 所在位置
.type(SecurityScheme.Type.APIKEY)
.name(HttpHeaders.AUTHORIZATION)
.in(SecurityScheme.In.HEADER);
securitySchemes.put(HttpHeaders.AUTHORIZATION, securityScheme);
return securitySchemes;
}
/**
* 自定义 OpenAPI 处理器
*/
@Bean
@Primary // 目的:以我们创建的 OpenAPIService Bean 为主,避免一键改包后,启动报错!
@Primary
public OpenAPIService openApiBuilder(Optional<OpenAPI> openAPI,
SecurityService securityParser,
SpringDocConfigProperties springDocConfigProperties,
@@ -107,11 +83,6 @@ public class MsgpushSwaggerAutoConfiguration {
propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
}
// ========== 分组 OpenAPI 配置 ==========
/**
* 所有模块的 API 分组
*/
@Bean
public GroupedOpenApi allGroupedOpenApi() {
return buildGroupedOpenApi("all", "");
@@ -131,43 +102,24 @@ public class MsgpushSwaggerAutoConfiguration {
.build();
}
/**
* 构建 Authorization 认证请求头参数
*
* 解决 Knife4j <a href="https://gitee.com/xiaoym/knife4j/issues/I69QBU">Authorize 未生效请求header里未包含参数</a>
*
* @return 认证参数
*/
private static Parameter buildSecurityHeaderParameter() {
return new Parameter()
.name(HttpHeaders.AUTHORIZATION) // header 名
.description("认证 Token") // 描述
.in(String.valueOf(SecurityScheme.In.HEADER)) // 请求 header
.schema(new StringSchema()._default("Bearer test1").description("认证 Token")); // 默认:使用用户编号为 1
.name(HttpHeaders.AUTHORIZATION)
.description("认证 Token")
.in(String.valueOf(SecurityScheme.In.HEADER))
.schema(new StringSchema()._default("Bearer test1")
.name(HttpHeaders.AUTHORIZATION)
.description("认证 Token"));
}
/**
* 核心自定义OperationId生成规则组合「类名前缀 + 方法名」
*
* @see <a href="https://github.com/YunaiV/ruoyi-vue-pro/issues/957">app-api 前缀不生效,都是使用 admin-api</a>
*/
private static OperationCustomizer buildOperationIdCustomizer() {
return (operation, handlerMethod) -> {
// 1. 获取控制器类名(如 UserController
String className = handlerMethod.getBeanType().getSimpleName();
// 2. 提取类名前缀(去除 Controller 后缀,如 UserController -> User
String classPrefix = className.replaceAll("Controller$", "");
// 3. 获取方法名(如 list
String methodName = handlerMethod.getMethod().getName();
// 4. 组合生成 operationId如 User_list
String operationId = classPrefix + "_" + methodName;
// 5. 设置自定义 operationId
operation.setOperationId(operationId);
operation.setOperationId(classPrefix + "_" + methodName);
return operation;
};
}
}

View File

@@ -1,10 +1,9 @@
package com.njcn.msgpush.framework.swagger.config;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import jakarta.validation.constraints.NotEmpty;
/**
* Swagger 配置属性
*

View File

@@ -1,19 +1,17 @@
package com.njcn.msgpush.framework.web.config;
import cn.hutool.core.util.StrUtil;
import com.njcn.msgpush.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
import com.google.common.collect.Maps;
import com.njcn.msgpush.framework.common.biz.system.logger.ApiErrorLogCommonApi;
import com.njcn.msgpush.framework.common.enums.WebFilterOrderEnum;
import com.njcn.msgpush.framework.web.core.filter.CacheRequestBodyFilter;
import com.njcn.msgpush.framework.web.core.filter.DemoFilter;
import com.njcn.msgpush.framework.web.core.handler.GlobalExceptionHandler;
import com.njcn.msgpush.framework.web.core.handler.GlobalResponseBodyHandler;
import com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils;
import com.google.common.collect.Maps;
import jakarta.servlet.Filter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -133,12 +131,6 @@ public class MsgpushWebAutoConfiguration {
/**
* 创建 DemoFilter Bean演示模式
*/
@Bean
@ConditionalOnProperty(value = "msgpush.demo", havingValue = "true")
public FilterRegistrationBean<DemoFilter> demoFilter() {
return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER);
}
public static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);
bean.setOrder(order);

View File

@@ -1,5 +1,8 @@
package com.njcn.msgpush.framework.web.config;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -7,10 +10,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
@ConfigurationProperties(prefix = "msgpush.web")
@Validated
@Data

View File

@@ -2,11 +2,10 @@ package com.njcn.msgpush.framework.web.core.filter;
import cn.hutool.core.util.StrUtil;
import com.njcn.msgpush.framework.web.config.WebProperties;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.http.HttpServletRequest;
/**
* 过滤 /admin-api、/app-api 等 API 请求的过滤器
*

View File

@@ -2,12 +2,12 @@ package com.njcn.msgpush.framework.web.core.filter;
import cn.hutool.core.util.StrUtil;
import com.njcn.msgpush.framework.common.util.servlet.ServletUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
/**

View File

@@ -1,35 +0,0 @@
package com.njcn.msgpush.framework.web.core.filter;
import cn.hutool.core.util.StrUtil;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.util.servlet.ServletUtils;
import com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import static com.njcn.msgpush.framework.common.exception.enums.GlobalErrorCodeConstants.DEMO_DENY;
/**
* 演示 Filter禁止用户发起写操作避免影响测试数据
*
* @author hongawen
*/
public class DemoFilter extends OncePerRequestFilter {
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String method = request.getMethod();
return !StrUtil.equalsAnyIgnoreCase(method, "POST", "PUT", "DELETE") // 写操作时,不进行过滤率
|| WebFrameworkUtils.getLoginUserId(request) == null; // 非登录用户时,不进行过滤
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
// 直接返回 DEMO_DENY 的结果。即,请求不继续
ServletUtils.writeJSON(response, CommonResult.error(DEMO_DENY));
}
}

View File

@@ -5,8 +5,10 @@ import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.njcn.msgpush.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
import com.njcn.msgpush.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.njcn.msgpush.framework.common.biz.system.logger.ApiErrorLogCommonApi;
import com.njcn.msgpush.framework.common.biz.system.logger.dto.ApiErrorLogCreateReqDTO;
import com.njcn.msgpush.framework.common.exception.ServiceException;
import com.njcn.msgpush.framework.common.exception.util.ServiceExceptionUtil;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
@@ -15,8 +17,6 @@ import com.njcn.msgpush.framework.common.util.json.JsonUtils;
import com.njcn.msgpush.framework.common.util.monitor.TracerUtils;
import com.njcn.msgpush.framework.common.util.servlet.ServletUtils;
import com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.google.common.util.concurrent.UncheckedExecutionException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
@@ -393,61 +393,7 @@ public class GlobalExceptionHandler {
if (!message.contains("doesn't exist")) {
return null;
}
// 1. 数据报表
if (message.contains("report_")) {
log.error("[报表模块 msgpush-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[报表模块 msgpush-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]");
}
// 2. 工作流
if (message.contains("bpm_")) {
log.error("[工作流模块 msgpush-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[工作流模块 msgpush-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]");
}
// 3. 微信公众号
if (message.contains("mp_")) {
log.error("[微信公众号 msgpush-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[微信公众号 msgpush-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
}
// 4. 商城系统
if (StrUtil.containsAny(message, "product_", "promotion_", "trade_")) {
log.error("[商城系统 msgpush-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[商城系统 msgpush-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
}
// 5. ERP 系统
if (message.contains("erp_")) {
log.error("[ERP 系统 msgpush-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[ERP 系统 msgpush-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
}
// 6. CRM 系统
if (message.contains("crm_")) {
log.error("[CRM 系统 msgpush-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[CRM 系统 msgpush-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
}
// 7. 支付平台
if (message.contains("pay_")) {
log.error("[支付模块 msgpush-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[支付模块 msgpush-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
}
// 8. AI 大模型
if (message.contains("ai_")) {
log.error("[AI 大模型 msgpush-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[AI 大模型 msgpush-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
}
// 9. IoT 物联网
if (message.contains("iot_")) {
log.error("[IoT 物联网 msgpush-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[IoT 物联网 msgpush-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]");
}
return null;
return CommonResult.error(TABLE_NOT_EXISTS.getCode(), TABLE_NOT_EXISTS.getMsg());
}
}

View File

@@ -1,37 +1,23 @@
package com.njcn.msgpush.framework.web.core.util;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.njcn.msgpush.framework.common.enums.RpcConstants;
import com.njcn.msgpush.framework.common.enums.TerminalEnum;
import com.njcn.msgpush.framework.common.enums.UserTypeEnum;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.util.servlet.ServletUtils;
import com.njcn.msgpush.framework.web.config.WebProperties;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
/**
* 专属于 web 包的工具类
*
* @author hongawen
*/
public class WebFrameworkUtils {
private static final String REQUEST_ATTRIBUTE_LOGIN_USER_ID = "login_user_id";
private static final String REQUEST_ATTRIBUTE_LOGIN_USER_TYPE = "login_user_type";
private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = "common_result";
/**
* 终端的 Header
*
* @see com.njcn.msgpush.framework.common.enums.TerminalEnum
*/
public static final String HEADER_TERMINAL = "terminal";
private static WebProperties properties;
@@ -40,30 +26,14 @@ public class WebFrameworkUtils {
WebFrameworkUtils.properties = webProperties;
}
public static void setLoginUserId(ServletRequest request, Long userId) {
request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId);
}
/**
* 设置用户类型
*
* @param request 请求
* @param userType 用户类型
*/
public static void setLoginUserType(ServletRequest request, Integer userType) {
request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE, userType);
}
/**
* 获得当前用户的编号,从请求中
* 注意:该方法仅限于 framework 框架使用!!!
*
* @param request 请求
* @return 用户编号
*/
public static Long getLoginUserId(HttpServletRequest request) {
if (request == null) {
return null;
@@ -71,23 +41,14 @@ public class WebFrameworkUtils {
return (Long) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID);
}
/**
* 获得当前用户的类型
* 注意:该方法仅限于 web 相关的 framework 组件使用!!!
*
* @param request 请求
* @return 用户编号
*/
public static Integer getLoginUserType(HttpServletRequest request) {
if (request == null) {
return null;
}
// 1. 优先,从 Attribute 中获取
Integer userType = (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE);
if (userType != null) {
return userType;
}
// 2. 其次,基于 URL 前缀的约定
if (request.getServletPath().startsWith(properties.getAdminApi().getPrefix())) {
return UserTypeEnum.ADMIN.getValue();
}
@@ -98,13 +59,11 @@ public class WebFrameworkUtils {
}
public static Integer getLoginUserType() {
HttpServletRequest request = getRequest();
return getLoginUserType(request);
return getLoginUserType(getRequest());
}
public static Long getLoginUserId() {
HttpServletRequest request = getRequest();
return getLoginUserId(request);
return getLoginUserId(getRequest());
}
public static Integer getTerminal() {
@@ -134,24 +93,10 @@ public class WebFrameworkUtils {
return servletRequestAttributes.getRequest();
}
/**
* 判断是否为 RPC 请求
*
* @param request 请求
* @return 是否为 RPC 请求
*/
public static boolean isRpcRequest(HttpServletRequest request) {
return request.getRequestURI().startsWith(RpcConstants.RPC_API_PREFIX);
}
/**
* 判断是否为 RPC 请求
*
* 约定大于配置,只要以 Api 结尾,都认为是 RPC 接口
*
* @param className 类名
* @return 是否为 RPC 请求
*/
public static boolean isRpcRequest(String className) {
return className.endsWith("Api");
}

View File

@@ -5,7 +5,6 @@ import com.njcn.msgpush.framework.xss.core.clean.JsoupXssCleaner;
import com.njcn.msgpush.framework.xss.core.clean.XssCleaner;
import com.njcn.msgpush.framework.xss.core.filter.XssFilter;
import com.njcn.msgpush.framework.xss.core.json.XssStringJsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

View File

@@ -2,14 +2,14 @@ package com.njcn.msgpush.framework.xss.core.filter;
import com.njcn.msgpush.framework.xss.config.XssProperties;
import com.njcn.msgpush.framework.xss.core.clean.XssCleaner;
import lombok.AllArgsConstructor;
import org.springframework.util.PathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.util.PathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
/**

View File

@@ -1,9 +1,9 @@
package com.njcn.msgpush.framework.xss.core.filter;
import com.njcn.msgpush.framework.xss.core.clean.XssCleaner;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.util.LinkedHashMap;
import java.util.Map;

View File

@@ -1,12 +1,12 @@
package com.njcn.msgpush.framework.xss.core.json;
import com.njcn.msgpush.framework.common.util.servlet.ServletUtils;
import com.njcn.msgpush.framework.xss.config.XssProperties;
import com.njcn.msgpush.framework.xss.core.clean.XssCleaner;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
import com.njcn.msgpush.framework.common.util.servlet.ServletUtils;
import com.njcn.msgpush.framework.xss.config.XssProperties;
import com.njcn.msgpush.framework.xss.core.clean.XssCleaner;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

View File

@@ -5,4 +5,4 @@ com.njcn.msgpush.framework.web.config.MsgpushWebAutoConfiguration
com.njcn.msgpush.framework.apilog.config.MsgpushApiLogRpcAutoConfiguration
com.njcn.msgpush.framework.xss.config.MsgpushXssAutoConfiguration
com.njcn.msgpush.framework.banner.config.MsgpushBannerAutoConfiguration
com.njcn.msgpush.framework.encrypt.config.MsgpushApiEncryptAutoConfiguration
com.njcn.msgpush.framework.encrypt.config.MsgpushApiEncryptAutoConfiguration

View File

@@ -1,4 +1,3 @@
灿能源码 http://www.iocoder.cn
Application Version: ${msgpush.info.version}
Spring Boot Version: ${spring-boot.version}

View File

@@ -1,100 +0,0 @@
package com.njcn.msgpush.framework.desensitize.core;
import com.njcn.msgpush.framework.common.util.json.JsonUtils;
import com.njcn.msgpush.framework.desensitize.core.regex.annotation.EmailDesensitize;
import com.njcn.msgpush.framework.desensitize.core.regex.annotation.RegexDesensitize;
import com.njcn.msgpush.framework.desensitize.core.annotation.Address;
import com.njcn.msgpush.framework.desensitize.core.slider.annotation.BankCardDesensitize;
import com.njcn.msgpush.framework.desensitize.core.slider.annotation.CarLicenseDesensitize;
import com.njcn.msgpush.framework.desensitize.core.slider.annotation.ChineseNameDesensitize;
import com.njcn.msgpush.framework.desensitize.core.slider.annotation.FixedPhoneDesensitize;
import com.njcn.msgpush.framework.desensitize.core.slider.annotation.IdCardDesensitize;
import com.njcn.msgpush.framework.desensitize.core.slider.annotation.PasswordDesensitize;
import com.njcn.msgpush.framework.desensitize.core.slider.annotation.MobileDesensitize;
import com.njcn.msgpush.framework.desensitize.core.slider.annotation.SliderDesensitize;
import lombok.Data;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link DesensitizeTest} 的单元测试
*/
@ExtendWith(MockitoExtension.class)
public class DesensitizeTest {
@Test
public void test() {
// 准备参数
DesensitizeDemo desensitizeDemo = new DesensitizeDemo();
desensitizeDemo.setNickname("灿能源码");
desensitizeDemo.setBankCard("9988002866797031");
desensitizeDemo.setCarLicense("粤A66666");
desensitizeDemo.setFixedPhone("01086551122");
desensitizeDemo.setIdCard("530321199204074611");
desensitizeDemo.setPassword("123456");
desensitizeDemo.setPhoneNumber("13248765917");
desensitizeDemo.setSlider1("ABCDEFG");
desensitizeDemo.setSlider2("ABCDEFG");
desensitizeDemo.setSlider3("ABCDEFG");
desensitizeDemo.setEmail("1@email.com");
desensitizeDemo.setRegex("你好,我是灿能源码");
desensitizeDemo.setAddress("北京市海淀区上地十街10号");
desensitizeDemo.setOrigin("灿能源码");
// 调用
DesensitizeDemo d = JsonUtils.parseObject(JsonUtils.toJsonString(desensitizeDemo), DesensitizeDemo.class);
// 断言
assertNotNull(d);
assertEquals("芋***", d.getNickname());
assertEquals("998800********31", d.getBankCard());
assertEquals("粤A6***6", d.getCarLicense());
assertEquals("0108*****22", d.getFixedPhone());
assertEquals("530321**********11", d.getIdCard());
assertEquals("******", d.getPassword());
assertEquals("132****5917", d.getPhoneNumber());
assertEquals("#######", d.getSlider1());
assertEquals("ABC*EFG", d.getSlider2());
assertEquals("*******", d.getSlider3());
assertEquals("1****@email.com", d.getEmail());
assertEquals("你好,我是*", d.getRegex());
assertEquals("北京市海淀区上地十街10号*", d.getAddress());
assertEquals("灿能源码", d.getOrigin());
}
@Data
public static class DesensitizeDemo {
@ChineseNameDesensitize
private String nickname;
@BankCardDesensitize
private String bankCard;
@CarLicenseDesensitize
private String carLicense;
@FixedPhoneDesensitize
private String fixedPhone;
@IdCardDesensitize
private String idCard;
@PasswordDesensitize
private String password;
@MobileDesensitize
private String phoneNumber;
@SliderDesensitize(prefixKeep = 6, suffixKeep = 1, replacer = "#")
private String slider1;
@SliderDesensitize(prefixKeep = 3, suffixKeep = 3)
private String slider2;
@SliderDesensitize(prefixKeep = 10)
private String slider3;
@EmailDesensitize
private String email;
@RegexDesensitize(regex = "灿能源码", replacer = "*")
private String regex;
@Address
private String address;
private String origin;
}
}

View File

@@ -1,30 +0,0 @@
package com.njcn.msgpush.framework.desensitize.core.annotation;
import com.njcn.msgpush.framework.desensitize.core.DesensitizeTest;
import com.njcn.msgpush.framework.desensitize.core.base.annotation.DesensitizeBy;
import com.njcn.msgpush.framework.desensitize.core.handler.AddressHandler;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 地址
*
* 用于 {@link DesensitizeTest} 测试使用
*
* @author gaibu
*/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = AddressHandler.class)
public @interface Address {
String replacer() default "*";
}

View File

@@ -1,19 +0,0 @@
package com.njcn.msgpush.framework.desensitize.core.handler;
import com.njcn.msgpush.framework.desensitize.core.DesensitizeTest;
import com.njcn.msgpush.framework.desensitize.core.base.handler.DesensitizationHandler;
import com.njcn.msgpush.framework.desensitize.core.annotation.Address;
/**
* {@link Address} 的脱敏处理器
*
* 用于 {@link DesensitizeTest} 测试使用
*/
public class AddressHandler implements DesensitizationHandler<Address> {
@Override
public String desensitize(String origin, Address annotation) {
return origin + annotation.replacer();
}
}

View File

@@ -1,86 +0,0 @@
package com.njcn.msgpush.framework.encrypt;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.AsymmetricAlgorithm;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import org.junit.jupiter.api.Test;
import java.util.Objects;
/**
* 各种 API 加解密的测试类:不是单测,而是方便大家生成密钥、加密、解密等操作。
*
* @author hongawen
*/
@SuppressWarnings("ConstantValue")
public class ApiEncryptTest {
@Test
public void testGenerateAsymmetric() {
String asymmetricAlgorithm = AsymmetricAlgorithm.RSA.getValue();
// String asymmetricAlgorithm = "SM2";
// String asymmetricAlgorithm = SM4.ALGORITHM_NAME;
// String asymmetricAlgorithm = SymmetricAlgorithm.AES.getValue();
String requestClientKey = null;
String requestServerKey = null;
String responseClientKey = null;
String responseServerKey = null;
if (Objects.equals(asymmetricAlgorithm, AsymmetricAlgorithm.RSA.getValue())) {
// 请求的密钥
RSA requestRsa = SecureUtil.rsa();
requestClientKey = requestRsa.getPublicKeyBase64();
requestServerKey = requestRsa.getPrivateKeyBase64();
// 响应的密钥
RSA responseRsa = new RSA();
responseClientKey = responseRsa.getPrivateKeyBase64();
responseServerKey = responseRsa.getPublicKeyBase64();
} else if (Objects.equals(asymmetricAlgorithm, SymmetricAlgorithm.AES.getValue())) {
// AES 密钥可选 32、24、16 位
// 请求的密钥(前后端密钥一致)
requestClientKey = RandomUtil.randomNumbers(32);
requestServerKey = requestClientKey;
// 响应的密钥(前后端密钥一致)
responseClientKey = RandomUtil.randomNumbers(32);
responseServerKey = responseClientKey;
}
// 打印结果
System.out.println("requestClientKey = " + requestClientKey);
System.out.println("requestServerKey = " + requestServerKey);
System.out.println("responseClientKey = " + responseClientKey);
System.out.println("responseServerKey = " + responseServerKey);
}
@Test
public void testEncrypt_aes() {
String key = "52549111389893486934626385991395";
String body = "{\n" +
" \"username\": \"admin\",\n" +
" \"password\": \"admin123\",\n" +
" \"uuid\": \"3acd87a09a4f48fb9118333780e94883\",\n" +
" \"code\": \"1024\"\n" +
"}";
String encrypt = SecureUtil.aes(StrUtil.utf8Bytes(key))
.encryptBase64(body);
System.out.println("encrypt = " + encrypt);
}
@Test
public void testEncrypt_rsa() {
String key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCls2rIpnGdYnLFgz1XU13GbNQ5DloyPpvW00FPGjqn5Z6JpK+kDtVlnkhwR87iRrE5Vf2WNqRX6vzbLSgveIQY8e8oqGCb829myjf1MuI+ZzN4ghf/7tEYhZJGPI9AbfxFqBUzm+kR3/HByAI22GLT96WM26QiMK8n3tIP/yiLswIDAQAB";
String body = "{\n" +
" \"username\": \"admin\",\n" +
" \"password\": \"admin123\",\n" +
" \"uuid\": \"3acd87a09a4f48fb9118333780e94883\",\n" +
" \"code\": \"1024\"\n" +
"}";
String encrypt = SecureUtil.rsa(null, key)
.encryptBase64(body, KeyType.PublicKey);
System.out.println("encrypt = " + encrypt);
}
}