基于 Redis 实现幂等性操作

This commit is contained in:
YunaiV 2021-02-23 00:11:21 +08:00
parent 8fa9ba8ec6
commit 1c1f1c49fa
8 changed files with 79 additions and 24 deletions

View File

@ -45,6 +45,7 @@
1. 代码生成前后端代码的生成Java、Vue、SQL支持 CRUD 下载 1. 代码生成前后端代码的生成Java、Vue、SQL支持 CRUD 下载
1. 系统接口:基于 Swagger 自动生成相关的 RESTful API 接口文档 1. 系统接口:基于 Swagger 自动生成相关的 RESTful API 接口文档
1. 数据库文档:基于 Screw 自动生成数据库文档 1. 数据库文档:基于 Screw 自动生成数据库文档
1. 幂等组件:基于 Redis 实现幂等组件,解决重复请求问题
## 在线体验 ## 在线体验

View File

@ -1,8 +1,8 @@
package cn.iocoder.dashboard.framework.idempotent.config; package cn.iocoder.dashboard.framework.idempotent.config;
import cn.iocoder.dashboard.framework.idempotent.core.aop.IdempotentAspect; 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.impl.DefaultIdempotentKeyResolver;
import cn.iocoder.dashboard.framework.idempotent.core.keyresolver.ExpressionIdempotentKeyResolver; 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.keyresolver.IdempotentKeyResolver;
import cn.iocoder.dashboard.framework.idempotent.core.redis.IdempotentRedisDAO; import cn.iocoder.dashboard.framework.idempotent.core.redis.IdempotentRedisDAO;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;

View File

@ -1,6 +1,6 @@
package cn.iocoder.dashboard.framework.idempotent.core.annotation; 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 cn.iocoder.dashboard.framework.idempotent.core.keyresolver.IdempotentKeyResolver;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;

View File

@ -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;
}
}

View File

@ -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.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SecureUtil;
import cn.iocoder.dashboard.framework.idempotent.core.annotation.Idempotent; 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.JoinPoint;
/** /**

View File

@ -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);
}
}
}

View File

@ -1,4 +1,12 @@
/** /**
* 幂等组件参考 https://github.com/it4alla/idempotent 项目实现 * 幂等组件参考 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; package cn.iocoder.dashboard.framework.idempotent;

View File

@ -5,6 +5,7 @@ import cn.iocoder.dashboard.common.pojo.CommonResult;
import cn.iocoder.dashboard.common.pojo.PageResult; import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils; 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.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.controller.config.vo.*;
import cn.iocoder.dashboard.modules.infra.convert.config.InfConfigConvert; import cn.iocoder.dashboard.modules.infra.convert.config.InfConfigConvert;
import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO; import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO;
@ -92,7 +93,7 @@ public class InfConfigController {
@PostMapping("/create") @PostMapping("/create")
// @PreAuthorize("@ss.hasPermi('infra:config:add')") // @PreAuthorize("@ss.hasPermi('infra:config:add')")
// @Log(title = "参数管理", businessType = BusinessType.INSERT) // @Log(title = "参数管理", businessType = BusinessType.INSERT)
@Idempotent(timeout = 10) @Idempotent(timeout = 60, keyResolver = ExpressionIdempotentKeyResolver.class, keyArg = "#reqVO.key")
public CommonResult<Long> createConfig(@Validated @RequestBody InfConfigCreateReqVO reqVO) { public CommonResult<Long> createConfig(@Validated @RequestBody InfConfigCreateReqVO reqVO) {
return success(configService.createConfig(reqVO)); return success(configService.createConfig(reqVO));
} }