当前位置:   article > 正文

rouyi后端开发文档-幂等性(防止重复提交)_ruoyi 重复提交

ruoyi 重复提交

1.什么是重复提交

例如说,用户快速的双击了某个按钮,前端没有禁用该按钮,导致发送了两次重复的请求。

实现原理

它的实现原理非常简单,针对相同参数的方法,一段时间内,有且仅能执行一次。执行流程如下:

① 在方法执行前,根据参数对应的 Key 查询是否存在

  • 如果 存在 ,说明正在执行中,则进行报错。
  • 如果 不在 ,则计算参数对应的 Key,存储到 Redis 中,并设置过期时间,即标记正在执行中。

默认参数的 Redis Key 的计算规则由 DefaultIdempotentKeyResolver (opens new window) 实现,使用 MD5(方法名 + 方法参数),避免 Redis Key 过长。

② 方法执行完成,不会主动删除参数对应的 Key。

从本质上来说,idempotent 包提供的幂等特性,本质上也是基于 Redis 实现的分布式锁。

③ 如果方法执行时间较长,超过 Key 的过期时间,则 Redis 会自动删除对应的 Key。因此,需要大概评估下,避免方法的执行时间超过过期时间。

@Idempotent 注解

声明在方法上,表示该方法需要开启幂等性。代码如下:

  1. // UserController.java
  2. @Idempotent(timeout = 10, timeUnit = TimeUnit.SECONDS, message = "正在添加用户中,请勿重复提交")
  3. @PostMapping("/user/create")
  4. public String createUser(User user){
  5. userService.createUser(user);
  6. return "添加成功";
  7. }

再次调用接口,被幂等性拦截,执行失败。

  1. {
  2. "code": 900,
  3. "data": null,
  4. "msg": "重复请求,请稍后重试"
  5. }

实现代码:

image.png

对应的 AOP 切面是 IdempotentAspect (opens new window) :

IdempotentAspect 切面

对应的 Redis Key 的前缀是 idempotent:%s,可见 IdempotentRedisDAO (opens new window) 类,如下图所示:

IdempotentRedisDAO 存储

redis存储例子:

image.png

扩展

解析器接口:

  1. /**
  2. * 幂等 Key 解析器接口
  3. *
  4. * @author 芋道源码
  5. */
  6. public interface IdempotentKeyResolver {
  7. /**
  8. * 解析一个 Key
  9. *
  10. * @param idempotent 幂等注解
  11. * @param joinPoint AOP 切面
  12. * @return Key
  13. */
  14. String resolver(JoinPoint joinPoint, Idempotent idempotent);
  15. }

默认解析器:

  1. /**
  2. * 默认幂等 Key 解析器,使用方法名 + 方法参数,组装成一个 Key
  3. *
  4. * 为了避免 Key 过长,使用 MD5 进行“压缩”
  5. *
  6. * @author 芋道源码
  7. */
  8. public class DefaultIdempotentKeyResolver implements IdempotentKeyResolver {
  9. @Override
  10. public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
  11. String methodName = joinPoint.getSignature().toString();
  12. String argsStr = StrUtil.join(",", joinPoint.getArgs());
  13. return SecureUtil.md5(methodName + argsStr);
  14. }
  15. }

基于Spring EL表达式的解析器:

  1. /**
  2. * 基于 Spring EL 表达式,
  3. *
  4. * @author 芋道源码
  5. */
  6. public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver {
  7. private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
  8. private final ExpressionParser expressionParser = new SpelExpressionParser();
  9. @Override
  10. public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
  11. // 获得被拦截方法参数名列表
  12. Method method = getMethod(joinPoint);
  13. Object[] args = joinPoint.getArgs();
  14. String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method);
  15. // 准备 Spring EL 表达式解析的上下文
  16. StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
  17. if (ArrayUtil.isNotEmpty(parameterNames)) {
  18. for (int i = 0; i < parameterNames.length; i++) {
  19. evaluationContext.setVariable(parameterNames[i], args[i]);
  20. }
  21. }
  22. // 解析参数
  23. Expression expression = expressionParser.parseExpression(idempotent.keyArg());
  24. return expression.getValue(evaluationContext, String.class);
  25. }
  26. private static Method getMethod(JoinPoint point) {
  27. // 处理,声明在类上的情况
  28. MethodSignature signature = (MethodSignature) point.getSignature();
  29. Method method = signature.getMethod();
  30. if (!method.getDeclaringClass().isInterface()) {
  31. return method;
  32. }
  33. // 处理,声明在接口上的情况
  34. try {
  35. return point.getTarget().getClass().getDeclaredMethod(
  36. point.getSignature().getName(), method.getParameterTypes());
  37. } catch (NoSuchMethodException e) {
  38. throw new RuntimeException(e);
  39. }
  40. }
  41. }

这两个解析器有什么区别呢?

  1. DefaultIdempotentKeyResolver :
    • 默认实现 :DefaultIdempotentKeyResolver 是默认的键解析器,它是最简单的实现方式。
    • 解析方式 :它使用方法名和方法参数组合成字符串,然后通过 MD5 进行哈希处理,生成唯一的关键信息。
    • 固定逻辑 :它的解析逻辑是固定的,无法根据具体的业务需求进行自定义。它适合那些不需要复杂关键信息逻辑的幂等操作。
  2. ExpressionIdempotentKeyResolver :
    • 自定义实现 :ExpressionIdempotentKeyResolver 允许开发者使用 Spring EL 表达式来自定义关键信息的生成方式。
    • 解析方式 :它根据 idempotent.keyArg() 表达式来动态生成关键信息。开发者可以在表达式中使用方法参数、返回值或其他上下文信息。
    • 灵活性 :它非常灵活,允许根据具体需求定义复杂的关键信息生成逻辑。这对于需要根据不同的方法或业务场景生成不同关键信息的情况非常有用。

总之,DefaultIdempotentKeyResolver 是一个简单而固定的实现,适合不需要复杂关键信息生成逻辑的情况。而 ExpressionIdempotentKeyResolver 则更为灵活,允许根据具体需求自定义关键信息的生成方式,这对于需要动态生成关键信息的幂等操作非常有用。开发人员可以根据具体情况选择合适的解析器。

使用 ExpressionIdempotentKeyResolver 的例子

下面是一个使用 ExpressionIdempotentKeyResolver 的简单示例:

首先,假设有一个服务类,其中包含一个幂等性操作:

  1. @Service
  2. public class OrderService {
  3. @Idempotent(keyResolver = ExpressionIdempotentKeyResolver.class, keyArg = "#userId + '-' + #productId")
  4. public void createOrder(int userId, int productId) {
  5. // 创建订单的业务逻辑
  6. }
  7. }

在上述示例中,createOrder 方法被标记为 @Idempotent,并且使用 ExpressionIdempotentKeyResolver 作为键解析器,同时指定了一个 Spring EL 表达式 #userId + '-' + #productId 作为 keyArg

这里,我们使用 Spring EL 表达式来动态生成关键信息,以确保对于每个不同的 userId 和 productId 组合,都会生成不同的幂等性关键。这样,即使相同的用户和产品尝试多次调用 createOrder 方法,只有第一次调用会生效,后续的调用会被幂等性控制机制拦截。

rouyi后端开发文档地址:幂等性(防重复提交) | ruoyi-vue-pro 开发指南

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/550785
推荐阅读
相关标签
  

闽ICP备14008679号