Commit 1c1f1c49fa897c576b595cec4cf5e77d34e6ae69
1 parent
8fa9ba8e
基于 Redis 实现幂等性操作
Showing
8 changed files
with
79 additions
and
24 deletions
README.md
src/main/java/cn/iocoder/dashboard/framework/idempotent/config/IdempotentConfiguration.java
| 1 | 1 | package cn.iocoder.dashboard.framework.idempotent.config; |
| 2 | 2 | |
| 3 | 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 | 6 | import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver; |
| 7 | 7 | import cn.iocoder.dashboard.framework.idempotent.core.redis.IdempotentRedisDAO; |
| 8 | 8 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; | ... | ... |
src/main/java/cn/iocoder/dashboard/framework/idempotent/core/annotation/Idempotent.java
| 1 | 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 | 4 | import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver; |
| 5 | 5 | |
| 6 | 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 | 3 | import cn.hutool.core.util.StrUtil; |
| 4 | 4 | import cn.hutool.crypto.SecureUtil; |
| 5 | 5 | import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent; |
| 6 | +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver; | |
| 6 | 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 | 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 | 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 | 5 | import cn.iocoder.dashboard.common.pojo.PageResult; |
| 6 | 6 | import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils; |
| 7 | 7 | import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent; |
| 8 | +import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver; | |
| 8 | 9 | import cn.iocoder.dashboard.modules.infra.controller.config.vo.*; |
| 9 | 10 | import cn.iocoder.dashboard.modules.infra.convert.config.InfConfigConvert; |
| 10 | 11 | import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO; |
| ... | ... | @@ -92,7 +93,7 @@ public class InfConfigController { |
| 92 | 93 | @PostMapping("/create") |
| 93 | 94 | // @PreAuthorize("@ss.hasPermi('infra:config:add')") |
| 94 | 95 | // @Log(title = "参数管理", businessType = BusinessType.INSERT) |
| 95 | - @Idempotent(timeout = 10) | |
| 96 | + @Idempotent(timeout = 60, keyResolver = ExpressionIdempotentKeyResolver.class, keyArg = "#reqVO.key") | |
| 96 | 97 | public CommonResult<Long> createConfig(@Validated @RequestBody InfConfigCreateReqVO reqVO) { |
| 97 | 98 | return success(configService.createConfig(reqVO)); |
| 98 | 99 | } | ... | ... |