使用SpringAOP+Redis实现接口处理幂等
  o21g2AkC8e2r 2023年12月07日 20 0



文章目录

  • 一、思路分析
  • 二、代码实战
  • 1、搭建Springboot+AOP+Redis环境
  • 2、自定义注解
  • 3、切面类
  • 4、测试一下吧


一、思路分析

在调用后台接口时,由于用户多次点击或者说第三方重试,可能会导致幂等问题。

解决方案无非就是上一次请求没有处理完,第二次请求不会处理,或者直接提示请求频繁,让用户等待。

我们基于SpringAOP(或者拦截器)来实现接口的幂等处理,多次请求时,提示用户不要重复请求,并缓存处理结果,将处理后的结果快速返回。

流程图如下:

使用SpringAOP+Redis实现接口处理幂等_ide

二、代码实战

1、搭建Springboot+AOP+Redis环境

2、自定义注解

该注解标注在Controller层,可以根据项目需要进行参数调整,比如说可以实现按指定字段判断幂等、实现接口的限流、指定幂等的判断条件等等。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 实现幂等的注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {

}

3、切面类

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 幂等切面
 */
@Aspect
@Component
public class IdempotentAspect {

    private final StringRedisTemplate redisTemplate;

    private final String Status = "status";
    private final String Begin = "begin";
    private final String End = "end";
    private final String Data = "data";

    @Autowired
    public IdempotentAspect(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint point, Idempotent idempotent) throws Throwable {
        String identifier = generateIdentifier(point);

        // 直接设置状态为begin,设置成功,说明该请求已处理
        Boolean beginStatus = redisTemplate.opsForHash().putIfAbsent(identifier, Status, Begin);
        if(!beginStatus) {
            // 先取数据,再取状态,防止这期间数据过期
            Object dataObject = redisTemplate.opsForHash().get(identifier, Data);
            Object status = redisTemplate.opsForHash().get(identifier, Status);
            if (Begin.equals(status)) {
                // 请求在处理,抛异常退出
                throw new RuntimeException("请求处理中,不要重复请求");
            }

            if (End.equals(status)) {
                // 如果处理结束,直接将结果返回
                return JsonAide.fromJson(dataObject.toString(), getMethod(point).getReturnType());
            }
        }
        // 5分钟过期
        redisTemplate.expire(identifier, 5, TimeUnit.MINUTES);

        // 设置成功,执行流程
        Object proceed = point.proceed();
        // 将请求状态设为结束,并且缓存返回值
        // TODO 请求结束之后,可以将key删掉,在业务中进行判断是否重复请求
        redisTemplate.opsForHash().put(identifier, Status, End);
        redisTemplate.opsForHash().put(identifier, Data, JsonAide.toJson(proceed));

        return proceed;
    }

    /**
     * 获取方法
     */
    private Method getMethod(ProceedingJoinPoint point) {
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        return methodSignature .getMethod();
    }

    /**
     * 获取方法签名
     */
    private String generateIdentifier(ProceedingJoinPoint point) {
        // 获取方法参数和相关信息
        Method method = getMethod(point);
        Object[] args = point.getArgs();
        // 根据方法名和参数生成唯一标识 TODO 可以替换为使用统一流水号
        return method.getName() + ":" + Stream.of(args).filter(Objects::nonNull).map(Object::toString).collect(Collectors.joining());
    }
}

4、测试一下吧

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;

@RestController
@RequestMapping("/test")
public class TestController {

    @PostMapping("/test")
    @Idempotent
    public Object test(@RequestBody Map<String, String> req) throws InterruptedException {
        System.out.println("请求进来了");
        // 休眠10秒 ,模拟业务处理时间
        Thread.sleep(10000);
        System.out.println("请求处理结束了");
        return req;
    }
}

我们发现,同一个请求未处理完成,会抛异常,此时我们捕获这个异常即可。(或者使用拦截器实现)


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年12月07日 0

暂无评论

推荐阅读
o21g2AkC8e2r