在分布式系统中,SpringBoot 实现接口幂等性


在分布式系统中,SpringBoot 实现接口幂等性

文章插图
在分布式系统中,接口幂等性是一个非常重要的概念,它保证了在同样的条件下,同一请求的多次执行所产生的效果都是相同的 。在实际开发中,为了防止重复提交或者重复操作带来的问题,我们需要考虑如何实现接口幂等性 。
下面我将介绍如何在 SpringBoot + MySQL + MyBatisPlus + Druid 的环境下实现接口幂等性 。
  1. 什么是接口幂等性?
接口幂等性是指,对于相同的输入,接口的输出结果应该相同 。换句话说,如果接口已经处理了一个请求并返回了结果,那么在相同的输入条件下,该接口的后续请求应该返回相同的结果,而不会产生任何新的副作用 。
  1. 如何实现接口幂等性?
要实现接口幂等性,需要考虑以下几个方面:
  • 请求唯一标识:每个请求都应该有一个唯一的标识,可以是请求参数的组合或者是一个单独的参数 。
  • 幂等性校验:每次请求到达服务器时,服务器需要判断该请求是否已经被处理过,如果已经被处理过,则直接返回处理结果,否则执行请求操作,并记录请求的唯一标识,以便后续的幂等性校验 。
在 SpringBoot + MySQL + MybatisPlus + Druid 的环境下,我们可以通过以下方式实现接口幂等性:
  • 在请求参数中添加一个幂等性校验码(比如 UUID),用于唯一标识每个请求 。
  • 在请求处理前,先查询幂等性校验码是否已经存在于数据库中,如果存在则说明该请求已经被处理过,直接返回结果 。
  • 如果幂等性校验码不存在于数据库中,则执行请求操作,并将幂等性校验码插入到数据库中 。
下面是实现接口幂等性的示例代码:
在请求参数中添加一个幂等性校验码:
public class RequestDTO {private String idempotenceKey;// other request fields and methods}在 MybatisPlus 中创建对应的实体类:
@Data@TableName("idempotence_key")public class IdempotenceKey {@TableId(type = IdType.ASSIGN_UUID)private String id;private String key;private Date createTime;}在 Controller 中实现幂等性校验:
@RestControllerpublic class UserController {@Autowiredprivate UserService userService;@PostMApping("/user")public String createUser(@RequestBody RequestDTO request) {// 幂等性校验if (checkIdempotence(request.getIdempotenceKey())) {return "success";}// 执行请求操作userService.createUser(request);// 插入幂等性校验码saveIdempotence(request.getIdempotenceKey());return "success";}}在 Service 中实现幂等性校验和插入幂等性校验码:
@Servicepublic class UserService {@Autowiredprivate IdempotenceKeyMapper idempotenceKeyMapper;public void createUser(RequestDTO request) {// 创建用户// ...}private boolean checkIdempotence(String key) {IdempotenceKey idempotenceKey = idempotenceKeyMapper.selectOne(new LambdaQueryWrapper<IdempotenceKey>().eq(IdempotenceKey::getKey, key));return idempotenceKey != null;}private void saveIdempotence(String key) {IdempotenceKey idempotenceKey = new IdempotenceKey();idempotenceKey.setKey(key);idempotenceKey.setCreateTime(new Date());idempotenceKeyMapper.insert(idempotenceKey);}}这里使用了 MybatisPlus 的 LambdaQueryWrapper 进行查询,并使用自动生成的 UUID 作为幂等性校验码 。
全局实现幂等性校验可以使用AOP(面向切面编程)来实现,在方法执行前先进行幂等性校验,如果已经执行过该方法,则直接返回结果 。可以通过自定义注解来标记需要进行幂等性校验的方法 。
以下是一个简单的示例代码:
  1. 自定义注解 Idempotent:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Idempotent {long expireSeconds() default 60;}
  1. 编写 AOP 切面,用于拦截带有 @Idempotent 注解的方法:
@Aspect@Componentpublic class IdempotentAspect {@Autowiredprivate IdempotenceKeyMapper idempotenceKeyMapper;@Pointcut("@annotation(com.example.demo.annotation.Idempotent)")public void idempotentPointcut() {}@Around("idempotentPointcut()")public Object idempotentAround(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();Idempotent idempotent = method.getAnnotation(Idempotent.class);String key = getKey(point);if (StringUtils.isBlank(key)) {throw new RuntimeException("幂等性校验码不能为空");}if (checkIdempotence(key)) {throw new RuntimeException("请勿重复操作");}saveIdempotence(key, idempotent.expireSeconds());return point.proceed();}private boolean checkIdempotence(String key) {IdempotenceKey idempotenceKey = idempotenceKeyMapper.selectOne(new LambdaQueryWrapper<IdempotenceKey>().eq(IdempotenceKey::getKey, key));return idempotenceKey != null;}private void saveIdempotence(String key, long expireSeconds) {IdempotenceKey idempotenceKey = new IdempotenceKey();idempotenceKey.setKey(key);idempotenceKey.setCreateTime(new Date());idempotenceKey.setExpireTime(new Date(System.currentTimeMillis() + expireSeconds * 1000));idempotenceKeyMapper.insert(idempotenceKey);}private String getKey(ProceedingJoinPoint point) {Object[] args = point.getArgs();if (args.length == 0) {return null;}return args[0].toString();}}


推荐阅读