mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 18:28:43 +08:00 
			
		
		
		
	基于 Redis 实现幂等性操作
This commit is contained in:
		| @@ -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 实现幂等组件,解决重复请求问题 | ||||||
|  |  | ||||||
| ## 在线体验 | ## 在线体验 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -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; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @@ -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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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)); | ||||||
|     } |     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV