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

自定义注解 + Redis 实现业务的幂等性

1.实现幂等性思路

实现幂等性有两种方式:

⭐ 1. 在数据库层面进行幂等性处理(数据库添加唯一约束).

例如:新增用户幂等性处理,username 字段可以添加唯一约束.

⭐ 2. 在应用程序层面进行幂等性处理.

而在应用程序方面进行幂等性处理,又有两种方式:

  • 通过 Spring AOP 方式实现幂等性判断(需要额外添加依赖).
  • 通过 Spring Boot 提供的拦截器实现幂等性判断.

例如:发表评论,同一个用户可以发表相同的评论,添加唯一约束不合适,放在程序层面处理.

2. 自定义注解 + Redis 实现业务幂等性

【实现思路】

  1. 创建自定义幂等性注解.

  2. 实现自定义幂等性注解的拦截器

    1. 创建拦截器,添加幂等性判断逻辑

    2. 定义幂等性判断的 ID(两种方式)

      1. 请求方携带唯一业务 ID

      2. 后端程序自行组织唯一业务 ID:当前用户 ID + 请求的数据(此处使用第二种)

  3. 配置拦截规则

  4. 使用自定义幂等性注解来保证业务的幂等性

2.1 自定义幂等性注解

/*** 自定义幂等性判断注解** @author helong*/
@Target(ElementType.METHOD) // 方法注解
@Retention(RetentionPolicy.RUNTIME)  // 程序运行期间有效
public @interface Idempotent {/*** 幂等性判断的时效** @return*/int time() default 60;
}

2.2 实现自定义幂等性注解的拦截器

@Component
public class IdempotentInterceptor implements HandlerInterceptor {@Resourceprivate ObjectMapper objectMapper;@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 只处理控制器方法,而不处理其他类型的请求(如静态资源)if (handler instanceof HandlerMethod) {Method method = ((HandlerMethod) handler).getMethod();// 尝试获取方法上的 Idempotent 注解Idempotent idempotent = method.getAnnotation(Idempotent.class);if (ObjectUtil.isNotNull(idempotent)) {// 生成唯一业务 IDString id = createId(request);ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();// 如果 Redis 中已存在相同的业务 ID,阻止重复提交if (ObjectUtil.isNotNull(ops.get(id))) {response.setContentType("application/json;charset=UTF-8");response.setCharacterEncoding("UTF-8");String json = "{\"code\": 500, \"msg\": \"数据正在处理,请勿重复提交!\", \"data\": null}";response.getWriter().write(json);return false;} else {// 如果 Redis 中不存在相同的业务 ID,存储这个 ID 并设置过期时间ops.set(id, Boolean.TRUE.toString(), idempotent.time(), TimeUnit.SECONDS);return true;}}}// 如果不是 HandlerMethod 实例或没有 Idempotent 注解,继续处理请求return HandlerInterceptor.super.preHandle(request, response, handler);}/*** 生成幂等性 Id -> md5(用户ID + 请求参数)** @param request*/private String createId(HttpServletRequest request) throws JsonProcessingException {Long uid = NumberUtils.LONG_ZERO;// 获取当前用户的详细信息SecurityUserDetails userDetails = SecurityUtil.getCurrentUser();if (ObjectUtil.isNotNull(userDetails)) {uid = userDetails.getUid();}// 将请求参数转换为 JSON 字符串String requestParam = objectMapper.writeValueAsString(request.getParameterMap());return SecureUtil.md5(uid + requestParam);}
}

2.3 配置拦截规则

@Configuration
public class WebConfig implements WebMvcConfigurer {/*** 注入自定义拦截器*/@Resourceprivate IdempotentInterceptor idempotentInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(idempotentInterceptor)// 拦截所有的请求.addPathPatterns("/**")// 放行静态资源.excludePathPatterns("/index.html").excludePathPatterns("/login.html").excludePathPatterns("/image/**").excludePathPatterns("/js/**").excludePathPatterns("/layui/**");}
}

此处也可以不需要放行静态资源,因为上一步的自定义幂等性注解拦截器的逻辑里,第一个 if 就相当于放行了静态资源。

2.4 使用自定义幂等性注解

@PostMapping("/add")
@Idempotent
public ResponseEntity addComment(@Validated Comment comment) {comment.setUid(SecurityUtil.getCurrentUser().getUid());boolean result = commentService.save(comment);return result ? ResponseEntity.success(Boolean.TRUE) : ResponseEntity.fail("评论失败");
}

就拿发表评论来看,添加完自定义幂等性注解后,来到前端页面尝试在 1 分钟内,使用相同的用户,发表相同的评论:

PS:Security 用户对象,获取当前登录用户的代码,请参照这篇文章:SpringSecurity + JWT 实现登录认证-CSDN博客

相关文章:

  • juicefs部署实践
  • 任意空间平面点云旋转投影至水平面—罗德里格旋转公式
  • 【简洁明了】调节大模型的prompt的方法【带案例】
  • 一款IM即时通讯聊天系统源码,包含app和后台源码
  • 【Hive SQL 每日一题】找出各个商品销售额的中位数
  • C语言 ——— 实用调试技巧(Visual Studio)
  • 业务系统核心模块资料访问性能优化实战
  • 【Rust】使用日志记录利器flexi_logger
  • 【系统架构设计师】十一、系统架构设计(层次架构风格|MVC|面向服务的架构风格|ESB)
  • 解决 Failed to get nested archive for entry BOOT-INF/lib/xxx.jar
  • 【编程语言】C++和C的异同点
  • DBA 数据库管理 表管理 数据批量处理。表头约束
  • SAC-IA粗配准算法记录
  • 景联文科技构建高质量心理学系知识图谱,助力大模型成为心理学科专家
  • AI艺术创作:掌握Midjourney和DALL-E的技巧与策略
  • (三)从jvm层面了解线程的启动和停止
  • Cumulo 的 ClojureScript 模块已经成型
  • iOS小技巧之UIImagePickerController实现头像选择
  • Java 内存分配及垃圾回收机制初探
  • JavaScript DOM 10 - 滚动
  • JavaScript/HTML5图表开发工具JavaScript Charts v3.19.6发布【附下载】
  • Laravel5.4 Queues队列学习
  • Python 使用 Tornado 框架实现 WebHook 自动部署 Git 项目
  • Shadow DOM 内部构造及如何构建独立组件
  • SpringBoot几种定时任务的实现方式
  • Vue2.x学习三:事件处理生命周期钩子
  • web标准化(下)
  • 从0搭建SpringBoot的HelloWorld -- Java版本
  • - 概述 - 《设计模式(极简c++版)》
  • 基于 Babel 的 npm 包最小化设置
  • 利用jquery编写加法运算验证码
  • 聊聊redis的数据结构的应用
  • 在Mac OS X上安装 Ruby运行环境
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • ​第20课 在Android Native开发中加入新的C++类
  • # 计算机视觉入门
  • # 睡眠3秒_床上这样睡觉的人,睡眠质量多半不好
  • $.ajax()方法详解
  • (vue)el-checkbox 实现展示区分 label 和 value(展示值与选中获取值需不同)
  • (zhuan) 一些RL的文献(及笔记)
  • (附源码)ssm经济信息门户网站 毕业设计 141634
  • (九)One-Wire总线-DS18B20
  • (南京观海微电子)——示波器使用介绍
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .
  • (转)ObjectiveC 深浅拷贝学习
  • (转)树状数组
  • ./include/caffe/util/cudnn.hpp: In function ‘const char* cudnnGetErrorString(cudnnStatus_t)’: ./incl
  • .[hudsonL@cock.li].mkp勒索加密数据库完美恢复---惜分飞
  • .DFS.
  • .mkp勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET 4.0中的泛型协变和反变
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .net framwork4.6操作MySQL报错Character set ‘utf8mb3‘ is not supported 解决方法
  • .NET gRPC 和RESTful简单对比
  • .NET Standard 支持的 .NET Framework 和 .NET Core