Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into develop

This commit is contained in:
YunaiV
2024-09-02 12:27:03 +08:00
70 changed files with 1554 additions and 268 deletions

View File

@ -7,6 +7,8 @@ import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import com.baomidou.mybatisplus.extension.incrementer.*;
import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal;
import com.baomidou.mybatisplus.extension.parser.cache.JdkSerialCaffeineJsqlParseCache;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.annotations.Mapper;
@ -16,6 +18,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.ConfigurableEnvironment;
import java.util.concurrent.TimeUnit;
/**
* MyBaits 配置类
*
@ -26,6 +30,14 @@ import org.springframework.core.env.ConfigurableEnvironment;
lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试
public class YudaoMybatisAutoConfiguration {
static {
// 动态 SQL 智能优化支持本地缓存加速解析,更完善的租户复杂 XML 动态 SQL 支持,静态注入缓存
JsqlParserGlobal.setJsqlParseCache(new JdkSerialCaffeineJsqlParseCache(
(cache) -> cache.maximumSize(1024)
.expireAfterWrite(5, TimeUnit.SECONDS))
);
}
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
@ -34,7 +46,7 @@ public class YudaoMybatisAutoConfiguration {
}
@Bean
public MetaObjectHandler defaultMetaObjectHandler(){
public MetaObjectHandler defaultMetaObjectHandler() {
return new DefaultDBFieldHandler(); // 自动填充参数类
}

View File

@ -69,7 +69,7 @@ public class ApiSignatureAspect {
// 3. 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2
String nonce = request.getHeader(signature.nonce());
signatureRedisDAO.setNonce(nonce, signature.timeout() * 2, signature.timeUnit());
signatureRedisDAO.setNonce(appId, nonce, signature.timeout() * 2, signature.timeUnit());
return true;
}
@ -113,7 +113,7 @@ public class ApiSignatureAspect {
}
// 3. 检查 nonce 是否存在,有且仅能使用一次
return signatureRedisDAO.getNonce(nonce) == null;
return signatureRedisDAO.getNonce(appId, nonce) == null;
}
/**
@ -165,5 +165,4 @@ public class ApiSignatureAspect {
return sortedMap;
}
}
}

View File

@ -22,7 +22,7 @@ public class ApiSignatureRedisDAO {
* VALUE 格式String
* 过期时间:不固定
*/
private static final String SIGNATURE_NONCE = "api_signature_nonce:%s";
private static final String SIGNATURE_NONCE = "api_signature_nonce:%s:%s";
/**
* 签名密钥
@ -36,16 +36,16 @@ public class ApiSignatureRedisDAO {
// ========== 验签随机数 ==========
public String getNonce(String nonce) {
return stringRedisTemplate.opsForValue().get(formatNonceKey(nonce));
public String getNonce(String appId, String nonce) {
return stringRedisTemplate.opsForValue().get(formatNonceKey(appId, nonce));
}
public void setNonce(String nonce, int time, TimeUnit timeUnit) {
stringRedisTemplate.opsForValue().set(formatNonceKey(nonce), "", time, timeUnit);
public void setNonce(String appId, String nonce, int time, TimeUnit timeUnit) {
stringRedisTemplate.opsForValue().set(formatNonceKey(appId, nonce), "", time, timeUnit);
}
private static String formatNonceKey(String key) {
return String.format(SIGNATURE_NONCE, key);
private static String formatNonceKey(String appId, String nonce) {
return String.format(SIGNATURE_NONCE, appId, nonce);
}
// ========== 签名密钥 ==========

View File

@ -69,7 +69,7 @@ public class ApiSignatureTest {
// 断言结果
assertTrue(result);
// 断言调用
verify(signatureRedisDAO).setNonce(eq(nonce), eq(120), eq(TimeUnit.SECONDS));
verify(signatureRedisDAO).setNonce(eq(appId), eq(nonce), eq(120), eq(TimeUnit.SECONDS));
}
}
}

View File

@ -11,6 +11,7 @@ import com.mzt.logapi.service.ILogRecordService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import java.util.List;
@ -28,19 +29,24 @@ public class LogRecordServiceImpl implements ILogRecordService {
private OperateLogApi operateLogApi;
@Override
@Async
public void record(LogRecord logRecord) {
// 1. 补全通用字段
OperateLogCreateReqDTO reqDTO = new OperateLogCreateReqDTO();
reqDTO.setTraceId(TracerUtils.getTraceId());
// 补充用户信息
fillUserFields(reqDTO);
// 补全模块信息
fillModuleFields(reqDTO, logRecord);
// 补全请求信息
fillRequestFields(reqDTO);
try {
reqDTO.setTraceId(TracerUtils.getTraceId());
// 补充用户信息
fillUserFields(reqDTO);
// 补全模块信息
fillModuleFields(reqDTO, logRecord);
// 补全请求信息
fillRequestFields(reqDTO);
// 2. 异步记录日志
operateLogApi.createOperateLog(reqDTO);
// 2. 异步记录日志
operateLogApi.createOperateLog(reqDTO);
} catch (Throwable ex) {
// 由于 @Async 异步调用,这里打印下日志,更容易跟进
log.error("[record][url({}) log({}) 发生异常]", reqDTO.getRequestUrl(), reqDTO, ex);
}
}
private static void fillUserFields(OperateLogCreateReqDTO reqDTO) {

View File

@ -4,6 +4,7 @@ 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 cn.hutool.extra.servlet.JakartaServletUtil;
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
@ -14,13 +15,14 @@ import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.ValidationException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
@ -38,7 +40,12 @@ import java.time.LocalDateTime;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.FORBIDDEN;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.METHOD_NOT_ALLOWED;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_FOUND;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
/**
* 全局异常处理器,将 Exception 翻译成 CommonResult + 对应的异常编号
@ -88,7 +95,7 @@ public class GlobalExceptionHandler {
return validationException((ValidationException) ex);
}
if (ex instanceof NoHandlerFoundException) {
return noHandlerFoundExceptionHandler(request, (NoHandlerFoundException) ex);
return noHandlerFoundExceptionHandler((NoHandlerFoundException) ex);
}
if (ex instanceof NoResourceFoundException) {
return noResourceFoundExceptionHandler(request, (NoResourceFoundException) ex);
@ -123,7 +130,7 @@ public class GlobalExceptionHandler {
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public CommonResult<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
log.warn("[missingServletRequestParameterExceptionHandler]", ex);
log.warn("[methodArgumentTypeMismatchExceptionHandler]", ex);
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", ex.getMessage()));
}
@ -149,6 +156,22 @@ public class GlobalExceptionHandler {
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
}
/**
* 处理 SpringMVC 请求参数类型错误
*
* 例如说,接口上设置了 @RequestBody实体中 xx 属性类型为 Integer结果传递 xx 参数类型为 String
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public CommonResult<?> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) {
log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex);
if(ex.getCause() instanceof InvalidFormatException) {
InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause();
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue()));
}else {
return defaultExceptionHandler(ServletUtils.getRequest(), ex);
}
}
/**
* 处理 Validator 校验不通过产生的异常
*/
@ -177,7 +200,7 @@ public class GlobalExceptionHandler {
* 2. spring.mvc.static-path-pattern 为 /statics/**
*/
@ExceptionHandler(NoHandlerFoundException.class)
public CommonResult<?> noHandlerFoundExceptionHandler(HttpServletRequest req, NoHandlerFoundException ex) {
public CommonResult<?> noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
log.warn("[noHandlerFoundExceptionHandler]", ex);
return CommonResult.error(NOT_FOUND.getCode(), String.format("请求地址不存在:%s", ex.getRequestURL()));
}
@ -253,7 +276,7 @@ public class GlobalExceptionHandler {
// 情况二:处理异常
log.error("[defaultExceptionHandler]", ex);
// 插入异常日志
this.createExceptionLog(req, ex);
createExceptionLog(req, ex);
// 返回 ERROR CommonResult
return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
}
@ -279,7 +302,7 @@ public class GlobalExceptionHandler {
errorLog.setExceptionName(e.getClass().getName());
errorLog.setExceptionMessage(ExceptionUtil.getMessage(e));
errorLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));
errorLog.setExceptionStackTrace(ExceptionUtils.getStackTrace(e));
errorLog.setExceptionStackTrace(ExceptionUtil.stacktraceToString(e));
StackTraceElement[] stackTraceElements = e.getStackTrace();
Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空");
StackTraceElement stackTraceElement = stackTraceElements[0];
@ -292,12 +315,12 @@ public class GlobalExceptionHandler {
errorLog.setApplicationName(applicationName);
errorLog.setRequestUrl(request.getRequestURI());
Map<String, Object> requestParams = MapUtil.<String, Object>builder()
.put("query", ServletUtils.getParamMap(request))
.put("body", ServletUtils.getBody(request)).build();
.put("query", JakartaServletUtil.getParamMap(request))
.put("body", JakartaServletUtil.getBody(request)).build();
errorLog.setRequestParams(JsonUtils.toJsonString(requestParams));
errorLog.setRequestMethod(request.getMethod());
errorLog.setUserAgent(ServletUtils.getUserAgent(request));
errorLog.setUserIp(ServletUtils.getClientIP(request));
errorLog.setUserIp(JakartaServletUtil.getClientIP(request));
errorLog.setExceptionTime(LocalDateTime.now());
}
@ -314,51 +337,51 @@ public class GlobalExceptionHandler {
}
// 1. 数据报表
if (message.contains("report_")) {
log.error("[报表模块 yudao-module-report - 表结构未导入][参考 https://doc.iocoder.cn/report/ 开启]");
log.error("[报表模块 yudao-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[报表模块 yudao-module-report - 表结构未导入][参考 https://doc.iocoder.cn/report/ 开启]");
"[报表模块 yudao-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]");
}
// 2. 工作流
if (message.contains("bpm_")) {
log.error("[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://doc.iocoder.cn/bpm/ 开启]");
log.error("[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://doc.iocoder.cn/bpm/ 开启]");
"[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]");
}
// 3. 微信公众号
if (message.contains("mp_")) {
log.error("[微信公众号 yudao-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]");
log.error("[微信公众号 yudao-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[微信公众号 yudao-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]");
"[微信公众号 yudao-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
}
// 4. 商城系统
if (StrUtil.containsAny(message, "product_", "promotion_", "trade_")) {
log.error("[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
log.error("[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
"[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
}
// 5. ERP 系统
if (message.contains("erp_")) {
log.error("[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://doc.iocoder.cn/erp/build/ 开启]");
log.error("[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://doc.iocoder.cn/erp/build/ 开启]");
"[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
}
// 6. CRM 系统
if (message.contains("crm_")) {
log.error("[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://doc.iocoder.cn/crm/build/ 开启]");
log.error("[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://doc.iocoder.cn/crm/build/ 开启]");
"[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
}
// 7. 支付平台
if (message.contains("pay_")) {
log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[支付模块 yudao-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
"[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
}
// 8. AI 大模型
if (message.contains("ai_")) {
log.error("[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://doc.iocoder.cn/ai/build/ 开启]");
log.error("[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://doc.iocoder.cn/ai/build/ 开启]");
"[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
}
return null;
}