当前位置: 首页 > news >正文

关于我在一个优惠券系统中rocketMQ消息幂等性自定义注解的处理

幂等性注解的定义

我定义一个注解 @MQIdempotent,用于加在需要幂等控制的方法上:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MQIdempotent {/*** key 前缀*/String keyPrefix();/*** 唯一 key 的 SpEL 表达式*/String key();/*** 过期时间(秒)*/long expireTime() default 3600;
}

解释:

  • keyPrefix:幂等性校验的 Redis Key 前缀

  • key:通过 SpEL 表达式 从方法参数中动态解析唯一 Key

  • expireTime:幂等 Key 的过期时间,默认 1 小时

定义aop切面

@Aspect
@Component
@RequiredArgsConstructor
public class IdempotentAspect {private final StringRedisTemplate redisTemplate;@Around("@annotation(MQidempotent)")public Object around(ProceedingJoinPoint joinPoint, MQIdempotent idempotent) throws Throwable {Method method = getMethod(joinPoint);// 解析 SpEL 表达式,得到唯一 keyString key = SpELUtil.parseKey(idempotent.keyPrefix(),idempotent.key(),method,joinPoint.getArgs());// 执行 Lua 脚本,确保原子性 setnx + expireString script = """if redis.call('setnx', KEYS[1], 1) == 1 thenredis.call('expire', KEYS[1], ARGV[1])return 1elsereturn 0end""";DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);Long result = redisTemplate.execute(redisScript, Collections.singletonList(key),String.valueOf(idempotent.expireSeconds()));if (result == null || result == 0) {throw new RuntimeException("请勿重复提交");}return joinPoint.proceed();}private Method getMethod(ProceedingJoinPoint joinPoint) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();return signature.getMethod();}
}

其中的apEL的解析工具类是这样的

public class SpELUtil {private static final SpelExpressionParser PARSER = new SpelExpressionParser();private static final DefaultParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer();/*** 解析注解中的 key*/public static String parseKey(String keyPrefix, String keySpEL, Method method, Object[] args) {// 获取方法参数名String[] paramNames = NAME_DISCOVERER.getParameterNames(method);if (paramNames == null) {throw new IllegalArgumentException("无法获取方法参数名");}// 构造 SpEL 上下文EvaluationContext context = new StandardEvaluationContext();for (int i = 0; i < paramNames.length; i++) {context.setVariable(paramNames[i], args[i]);}// 解析表达式Expression expression = PARSER.parseExpression(keySpEL);Object value = expression.getValue(context);return keyPrefix + value;}
}

示例

@Idempotent(keyPrefix = "order:", key = "#request.userId")
public void createOrder(OrderRequest request) {// 下单逻辑
}

调用时,如果同一个 userId 短时间内多次请求,就会被拦截,避免重复下单。

http://www.dtcms.com/a/354940.html

相关文章:

  • 使用reCAPTCHA提升WordPress网站安全性
  • 驱动开发系列67 - NVIDIA 开源GPU驱动open-gpu-kernel-modules分析-驱动初始化
  • Java自定义程序使用Ollama实现本地ai调用
  • Java-反射机制
  • Java 多线程环境下的全局变量缓存实践指南
  • PyTorch 张量核心知识点
  • 【物联网】什么是 Arduino Nano 33 IoT?
  • 基于springboot的二手车交易系统
  • WEEX唯客上线C2C交易平台:打造安全便捷的用户交易体验
  • FISCO-BCOS-Python 模板
  • 上海控安:GB 44495-2024《汽车整车信息安全技术要求》标准解读和测试方案
  • 动手学深度学习(pytorch版):第七章节—现代卷积神经网络(6)残差网络(ResNet)
  • Ubuntu 使用百度云的bypy上传和下载数据
  • ArcGIS+Fragstats:土地利用统计分析、景观格局指数计算与地图制图
  • 终极实战 - 全链路排查一次“502 Bad Gateway”
  • Linux并发与竞争
  • 达梦数据库-重做日志文件(三)-自动化迁移脚本和检查 磁盘 I/O 性能建议
  • 详细介绍Linux 内存管理 匿名页面和page cache页面有什么区别?
  • Mybatis 与 Springboot 集成过程详解
  • vue有哪些优缺点
  • 前端实现Linux查询平台:打造高效运维工作流
  • 从图卷积网络(GCN)到简化图卷积网络(SGC)的对话
  • RAG系统深度优化全攻略:从理论到实践的高性能实现
  • 【C语言16天强化训练】从基础入门到进阶:Day 14
  • NVFP4量化技术深度解析:4位精度下实现2.3倍推理加速
  • 内网对抗-红日靶场4通关详解
  • 财务数据报销画像技术实现:从数据采集到智能决策的全流程解析
  • 2025docker快速部署Nginx UI可视化管理平台
  • Unity3d使用SerialPortUtilityPro读取串口数据
  • Linux(一) | 初识Linux与目录管理基础命令掌握