From 1c1f1c49fa897c576b595cec4cf5e77d34e6ae69 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 23 Feb 2021 00:11:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E4=BA=8E=20Redis=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E5=B9=82=E7=AD=89=E6=80=A7=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + .../config/IdempotentConfiguration.java | 4 +- .../core/annotation/Idempotent.java | 2 +- .../ExpressionIdempotentKeyResolver.java | 19 ------ .../DefaultIdempotentKeyResolver.java | 3 +- .../impl/ExpressionIdempotentKeyResolver.java | 63 +++++++++++++++++++ .../framework/idempotent/package-info.java | 8 +++ .../config/InfConfigController.java | 3 +- 8 files changed, 79 insertions(+), 24 deletions(-) delete mode 100644 src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/ExpressionIdempotentKeyResolver.java rename src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/{ => impl}/DefaultIdempotentKeyResolver.java (88%) create mode 100644 src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/impl/ExpressionIdempotentKeyResolver.java diff --git a/README.md b/README.md index cebe6ef37..652c3856b 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ 1. 代码生成:前后端代码的生成(Java、Vue、SQL),支持 CRUD 下载 1. 系统接口:基于 Swagger 自动生成相关的 RESTful API 接口文档 1. 数据库文档:基于 Screw 自动生成数据库文档 +1. 幂等组件:基于 Redis 实现幂等组件,解决重复请求问题 ## 在线体验 diff --git a/src/main/java/cn/iocoder/dashboard/framework/idempotent/config/IdempotentConfiguration.java b/src/main/java/cn/iocoder/dashboard/framework/idempotent/config/IdempotentConfiguration.java index c73e723f9..7730ec81e 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/idempotent/config/IdempotentConfiguration.java +++ b/src/main/java/cn/iocoder/dashboard/framework/idempotent/config/IdempotentConfiguration.java @@ -1,8 +1,8 @@ package cn.iocoder.dashboard.framework.idempotent.config; import cn.iocoder.dashboard.framework.idempotent.core.aop.IdempotentAspect; -import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.DefaultIdempotentKeyResolver; -import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.ExpressionIdempotentKeyResolver; +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.impl.DefaultIdempotentKeyResolver; +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver; import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver; import cn.iocoder.dashboard.framework.idempotent.core.redis.IdempotentRedisDAO; import org.springframework.boot.autoconfigure.AutoConfigureAfter; diff --git a/src/main/java/cn/iocoder/dashboard/framework/idempotent/core/annotation/Idempotent.java b/src/main/java/cn/iocoder/dashboard/framework/idempotent/core/annotation/Idempotent.java index 1b1a22352..f5981d07f 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/idempotent/core/annotation/Idempotent.java +++ b/src/main/java/cn/iocoder/dashboard/framework/idempotent/core/annotation/Idempotent.java @@ -1,6 +1,6 @@ package cn.iocoder.dashboard.framework.idempotent.core.annotation; -import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.DefaultIdempotentKeyResolver; +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.impl.DefaultIdempotentKeyResolver; import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver; import java.lang.annotation.ElementType; diff --git a/src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/ExpressionIdempotentKeyResolver.java b/src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/ExpressionIdempotentKeyResolver.java deleted file mode 100644 index c5d1588af..000000000 --- a/src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/ExpressionIdempotentKeyResolver.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.iocoder.dashboard.framework.idempotent.core.keyresolver; - -import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent; -import org.aspectj.lang.JoinPoint; - -/** - * 基于 Spring EL 表达式, - * - * @author 芋道源码 - */ -public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver { - - @Override - public String resolver(JoinPoint joinPoint, Idempotent idempotent) { - // TODO 稍后实现 - return null; - } - -} diff --git a/src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/DefaultIdempotentKeyResolver.java b/src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/impl/DefaultIdempotentKeyResolver.java similarity index 88% rename from src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/DefaultIdempotentKeyResolver.java rename to src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/impl/DefaultIdempotentKeyResolver.java index bcd4bbe91..900959b94 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/DefaultIdempotentKeyResolver.java +++ b/src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/impl/DefaultIdempotentKeyResolver.java @@ -1,8 +1,9 @@ -package cn.iocoder.dashboard.framework.idempotent.core.keyresolver; +package cn.iocoder.dashboard.framework.idempotent.core.keyresolver.impl; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent; +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver; import org.aspectj.lang.JoinPoint; /** diff --git a/src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/impl/ExpressionIdempotentKeyResolver.java b/src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/impl/ExpressionIdempotentKeyResolver.java new file mode 100644 index 000000000..f67e52571 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/idempotent/core/keyresolver/impl/ExpressionIdempotentKeyResolver.java @@ -0,0 +1,63 @@ +package cn.iocoder.dashboard.framework.idempotent.core.keyresolver.impl; + +import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent; +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import java.lang.reflect.Method; + +/** + * 基于 Spring EL 表达式, + * + * @author 芋道源码 + */ +public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver { + + private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); + private final ExpressionParser expressionParser = new SpelExpressionParser(); + + @Override + public String resolver(JoinPoint joinPoint, Idempotent idempotent) { + // 获得被拦截方法参数名列表 + Method method = getMethod(joinPoint); + Object[] args = joinPoint.getArgs(); + String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method); + // 准备 Spring EL 表达式解析的上下文 + StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); + if (ArrayUtil.isNotEmpty(parameterNames)) { + for (int i = 0; i < parameterNames.length; i++) { + evaluationContext.setVariable(parameterNames[i], args[i]); + } + } + + // 解析参数 + Expression expression = expressionParser.parseExpression(idempotent.keyArg()); + return expression.getValue(evaluationContext, String.class); + } + + private static Method getMethod(JoinPoint point) { + // 处理,声明在类上的情况 + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + if (!method.getDeclaringClass().isInterface()) { + return method; + } + + // 处理,声明在接口上的情况 + try { + return point.getTarget().getClass().getDeclaredMethod( + point.getSignature().getName(), method.getParameterTypes()); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/idempotent/package-info.java b/src/main/java/cn/iocoder/dashboard/framework/idempotent/package-info.java index 7e4594180..b8b54ba31 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/idempotent/package-info.java +++ b/src/main/java/cn/iocoder/dashboard/framework/idempotent/package-info.java @@ -1,4 +1,12 @@ /** * 幂等组件,参考 https://github.com/it4alla/idempotent 项目实现 + * 实现原理是,相同参数的方法,一段时间内,有且仅能执行一次。通过这样的方式,保证幂等性。 + * + * 使用场景:例如说,用户快速的双击了某个按钮,前端没有禁用该按钮,导致发送了两次重复的请求。 + * + * 和 it4alla/idempotent 组件的差异点,主要体现在两点: + * 1. 我们去掉了 @Idempotent 注解的 delKey 属性。原因是,本质上 delKey 为 true 时,实现的是分布式锁的能力 + * 此时,我们偏向使用 Lock4j 组件。原则上,一个组件只提供一种单一的能力。 + * 2. 考虑到组件的通用性,我们并未像 it4alla/idempotent 组件一样使用 Redisson RMap 结构,而是直接使用 Redis 的 String 数据格式。 */ package cn.iocoder.dashboard.framework.idempotent; diff --git a/src/main/java/cn/iocoder/dashboard/modules/infra/controller/config/InfConfigController.java b/src/main/java/cn/iocoder/dashboard/modules/infra/controller/config/InfConfigController.java index 04614cafa..05549b3c2 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/infra/controller/config/InfConfigController.java +++ b/src/main/java/cn/iocoder/dashboard/modules/infra/controller/config/InfConfigController.java @@ -5,6 +5,7 @@ import cn.iocoder.dashboard.common.pojo.CommonResult; import cn.iocoder.dashboard.common.pojo.PageResult; import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils; import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent; +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver; import cn.iocoder.dashboard.modules.infra.controller.config.vo.*; import cn.iocoder.dashboard.modules.infra.convert.config.InfConfigConvert; import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO; @@ -92,7 +93,7 @@ public class InfConfigController { @PostMapping("/create") // @PreAuthorize("@ss.hasPermi('infra:config:add')") // @Log(title = "参数管理", businessType = BusinessType.INSERT) - @Idempotent(timeout = 10) + @Idempotent(timeout = 60, keyResolver = ExpressionIdempotentKeyResolver.class, keyArg = "#reqVO.key") public CommonResult createConfig(@Validated @RequestBody InfConfigCreateReqVO reqVO) { return success(configService.createConfig(reqVO)); }