Commit 1c1f1c49fa897c576b595cec4cf5e77d34e6ae69

Authored by YunaiV
1 parent 8fa9ba8e

基于 Redis 实现幂等性操作

README.md
@@ -45,6 +45,7 @@ @@ -45,6 +45,7 @@
45 1. 代码生成:前后端代码的生成(Java、Vue、SQL),支持 CRUD 下载 45 1. 代码生成:前后端代码的生成(Java、Vue、SQL),支持 CRUD 下载
46 1. 系统接口:基于 Swagger 自动生成相关的 RESTful API 接口文档 46 1. 系统接口:基于 Swagger 自动生成相关的 RESTful API 接口文档
47 1. 数据库文档:基于 Screw 自动生成数据库文档 47 1. 数据库文档:基于 Screw 自动生成数据库文档
  48 +1. 幂等组件:基于 Redis 实现幂等组件,解决重复请求问题
48 49
49 ## 在线体验 50 ## 在线体验
50 51
src/main/java/cn/iocoder/dashboard/framework/idempotent/config/IdempotentConfiguration.java
1 package cn.iocoder.dashboard.framework.idempotent.config; 1 package cn.iocoder.dashboard.framework.idempotent.config;
2 2
3 import cn.iocoder.dashboard.framework.idempotent.core.aop.IdempotentAspect; 3 import cn.iocoder.dashboard.framework.idempotent.core.aop.IdempotentAspect;
4 -import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.DefaultIdempotentKeyResolver;  
5 -import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.ExpressionIdempotentKeyResolver; 4 +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.impl.DefaultIdempotentKeyResolver;
  5 +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver;
6 import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver; 6 import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
7 import cn.iocoder.dashboard.framework.idempotent.core.redis.IdempotentRedisDAO; 7 import cn.iocoder.dashboard.framework.idempotent.core.redis.IdempotentRedisDAO;
8 import org.springframework.boot.autoconfigure.AutoConfigureAfter; 8 import org.springframework.boot.autoconfigure.AutoConfigureAfter;
src/main/java/cn/iocoder/dashboard/framework/idempotent/core/annotation/Idempotent.java
1 package cn.iocoder.dashboard.framework.idempotent.core.annotation; 1 package cn.iocoder.dashboard.framework.idempotent.core.annotation;
2 2
3 -import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.DefaultIdempotentKeyResolver; 3 +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.impl.DefaultIdempotentKeyResolver;
4 import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver; 4 import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
5 5
6 import java.lang.annotation.ElementType; 6 import java.lang.annotation.ElementType;
src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/ExpressionIdempotentKeyResolver.java deleted
1 -package cn.iocoder.dashboard.framework.idempotent.core.keyresolver;  
2 -  
3 -import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent;  
4 -import org.aspectj.lang.JoinPoint;  
5 -  
6 -/**  
7 - * 基于 Spring EL 表达式,  
8 - *  
9 - * @author 芋道源码  
10 - */  
11 -public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver {  
12 -  
13 - @Override  
14 - public String resolver(JoinPoint joinPoint, Idempotent idempotent) {  
15 - // TODO 稍后实现  
16 - return null;  
17 - }  
18 -  
19 -}  
src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/DefaultIdempotentKeyResolver.java renamed to src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/impl/DefaultIdempotentKeyResolver.java
1 -package cn.iocoder.dashboard.framework.idempotent.core.keyresolver; 1 +package cn.iocoder.dashboard.framework.idempotent.core.keyresolver.impl;
2 2
3 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
4 import cn.hutool.crypto.SecureUtil; 4 import cn.hutool.crypto.SecureUtil;
5 import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent; 5 import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent;
  6 +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
6 import org.aspectj.lang.JoinPoint; 7 import org.aspectj.lang.JoinPoint;
7 8
8 /** 9 /**
src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/impl/ExpressionIdempotentKeyResolver.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.idempotent.core.keyresolver.impl;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent;
  5 +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
  6 +import org.aspectj.lang.JoinPoint;
  7 +import org.aspectj.lang.reflect.MethodSignature;
  8 +import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
  9 +import org.springframework.core.ParameterNameDiscoverer;
  10 +import org.springframework.expression.Expression;
  11 +import org.springframework.expression.ExpressionParser;
  12 +import org.springframework.expression.spel.standard.SpelExpressionParser;
  13 +import org.springframework.expression.spel.support.StandardEvaluationContext;
  14 +
  15 +import java.lang.reflect.Method;
  16 +
  17 +/**
  18 + * 基于 Spring EL 表达式,
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver {
  23 +
  24 + private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
  25 + private final ExpressionParser expressionParser = new SpelExpressionParser();
  26 +
  27 + @Override
  28 + public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
  29 + // 获得被拦截方法参数名列表
  30 + Method method = getMethod(joinPoint);
  31 + Object[] args = joinPoint.getArgs();
  32 + String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method);
  33 + // 准备 Spring EL 表达式解析的上下文
  34 + StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
  35 + if (ArrayUtil.isNotEmpty(parameterNames)) {
  36 + for (int i = 0; i < parameterNames.length; i++) {
  37 + evaluationContext.setVariable(parameterNames[i], args[i]);
  38 + }
  39 + }
  40 +
  41 + // 解析参数
  42 + Expression expression = expressionParser.parseExpression(idempotent.keyArg());
  43 + return expression.getValue(evaluationContext, String.class);
  44 + }
  45 +
  46 + private static Method getMethod(JoinPoint point) {
  47 + // 处理,声明在类上的情况
  48 + MethodSignature signature = (MethodSignature) point.getSignature();
  49 + Method method = signature.getMethod();
  50 + if (!method.getDeclaringClass().isInterface()) {
  51 + return method;
  52 + }
  53 +
  54 + // 处理,声明在接口上的情况
  55 + try {
  56 + return point.getTarget().getClass().getDeclaredMethod(
  57 + point.getSignature().getName(), method.getParameterTypes());
  58 + } catch (NoSuchMethodException e) {
  59 + throw new RuntimeException(e);
  60 + }
  61 + }
  62 +
  63 +}
src/main/java/cn/iocoder/dashboard/framework/idempotent/package-info.java
1 /** 1 /**
2 * 幂等组件,参考 https://github.com/it4alla/idempotent 项目实现 2 * 幂等组件,参考 https://github.com/it4alla/idempotent 项目实现
  3 + * 实现原理是,相同参数的方法,一段时间内,有且仅能执行一次。通过这样的方式,保证幂等性。
  4 + *
  5 + * 使用场景:例如说,用户快速的双击了某个按钮,前端没有禁用该按钮,导致发送了两次重复的请求。
  6 + *
  7 + * 和 it4alla/idempotent 组件的差异点,主要体现在两点:
  8 + * 1. 我们去掉了 @Idempotent 注解的 delKey 属性。原因是,本质上 delKey 为 true 时,实现的是分布式锁的能力
  9 + * 此时,我们偏向使用 Lock4j 组件。原则上,一个组件只提供一种单一的能力。
  10 + * 2. 考虑到组件的通用性,我们并未像 it4alla/idempotent 组件一样使用 Redisson RMap 结构,而是直接使用 Redis 的 String 数据格式。
3 */ 11 */
4 package cn.iocoder.dashboard.framework.idempotent; 12 package cn.iocoder.dashboard.framework.idempotent;
src/main/java/cn/iocoder/dashboard/modules/infra/controller/config/InfConfigController.java
@@ -5,6 +5,7 @@ import cn.iocoder.dashboard.common.pojo.CommonResult; @@ -5,6 +5,7 @@ import cn.iocoder.dashboard.common.pojo.CommonResult;
5 import cn.iocoder.dashboard.common.pojo.PageResult; 5 import cn.iocoder.dashboard.common.pojo.PageResult;
6 import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils; 6 import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils;
7 import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent; 7 import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent;
  8 +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver;
8 import cn.iocoder.dashboard.modules.infra.controller.config.vo.*; 9 import cn.iocoder.dashboard.modules.infra.controller.config.vo.*;
9 import cn.iocoder.dashboard.modules.infra.convert.config.InfConfigConvert; 10 import cn.iocoder.dashboard.modules.infra.convert.config.InfConfigConvert;
10 import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO; 11 import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO;
@@ -92,7 +93,7 @@ public class InfConfigController { @@ -92,7 +93,7 @@ public class InfConfigController {
92 @PostMapping("/create") 93 @PostMapping("/create")
93 // @PreAuthorize("@ss.hasPermi('infra:config:add')") 94 // @PreAuthorize("@ss.hasPermi('infra:config:add')")
94 // @Log(title = "参数管理", businessType = BusinessType.INSERT) 95 // @Log(title = "参数管理", businessType = BusinessType.INSERT)
95 - @Idempotent(timeout = 10) 96 + @Idempotent(timeout = 60, keyResolver = ExpressionIdempotentKeyResolver.class, keyArg = "#reqVO.key")
96 public CommonResult<Long> createConfig(@Validated @RequestBody InfConfigCreateReqVO reqVO) { 97 public CommonResult<Long> createConfig(@Validated @RequestBody InfConfigCreateReqVO reqVO) {
97 return success(configService.createConfig(reqVO)); 98 return success(configService.createConfig(reqVO));
98 } 99 }