基于Redis的接口幂等方案设计实现-SpringBoot

方案:

  • 需要幂等的接口,前端发起请求前,先请求后端接口获取一个幂等字符串,后端生成一个字符串,并存入Redis
  • 前端发起真正的接口请求时需要在请求头带上这个字符串
  • 后端基于AOP在需要幂等的接口做拦截,从请求头拿到这个幂等字符串
  • 校验幂等字符串,校验成功则删除该幂等字符串,则放行执行真正的业务逻辑,校验失败则把该请求视为无效请求

实现

定义一个幂等注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoIdempotent {
}

需要幂等的接口加上这个注解

    @AutoIdempotent
    @PostMapping("/pay")
    public void pay(@RequestBody @Validated OrderPayParam param) {
        orderService.pay(param);
    }

定义幂等接口

public interface Idempotence {

    /**
     * 检查是否存在幂等号
     *
     * @param idempotenceId 幂等号
     * @return 是否存在
     */
    boolean check(String idempotenceId);

    /**
     * 记录幂等号
     *
     * @param idempotenceId 幂等号
     */
    void record(String idempotenceId);

    /**
     * 记录幂等号
     *
     * @param idempotenceId 幂等号
     * @param time          过期时间
     */
    void record(String idempotenceId, Integer time);

    /**
     * 删除幂等号
     *
     * @param idempotenceId 幂等号
     */
    void delete(String idempotenceId);

    /**
     * 生成幂等号
     *
     * @return uId
     */
    String generateId();

    /**
     * 从Header里面获取幂等号
     *
     * @return idempotenceId
     */
    String getHeaderIdempotenceId();
}

幂等接口实现类

@Service
public class RedisIdempotenceImpl implements Idempotence {

    private final RedisUtil redisUtil;

    public RedisIdempotenceImpl(RedisUtil redisUtil) {
        this.redisUtil = redisUtil;
    }

    @Override
    public boolean check(String idempotenceId) {
        return redisUtil.isKeyExist(idempotenceId);
    }

    @Override
    public void record(String idempotenceId) {
        redisUtil.set(idempotenceId, "1");
    }

    @Override
    public void record(String idempotenceId, Integer time) {
        redisUtil.setex(idempotenceId, "1", time);
    }

    @Override
    public void delete(String idempotenceId) {
        redisUtil.deleteKey(idempotenceId);
    }

    @Override
    public String generateId() {
        String uuid = UUID.randomUUID().toString();
        String uId = Base64Util.encode(uuid).toLowerCase();
        redisUtil.setex(uId, "1", 86400);
        return uId;
    }

    @Override
    public String getHeaderIdempotenceId() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        return request.getHeader("idempotenceId");
    }
}

定义AOP增强类


@Aspect
@Slf4j
@Component
public class IdempotenceSupportAdvice {

    private final Idempotence idempotence;

    private final ReentrantLockUtil reentrantLockUtil;

    private final String IDEMPOTENCE_LOCK_PREFIX = "IDEMPOTENCE_LOCK:";

    public IdempotenceSupportAdvice(Idempotence idempotence, ReentrantLockUtil reentrantLockUtil) {
        this.idempotence = idempotence;
        this.reentrantLockUtil = reentrantLockUtil;
    }

    /**
     * 拦截有@AutoIdempotent注解的方法
     */
    @Pointcut("@annotation(com.yamu.bns.annotation.AutoIdempotent)")
    public void idempotenceMethod() {
    }

    @AfterThrowing(value = "idempotenceMethod()()", throwing = "e")
    public void afterThrowing(Throwable e) {
        // 从HTTP header中获取幂等号idempotenceId
        String idempotenceId = idempotence.getHeaderIdempotenceId();
        if (StringUtils.isNotBlank(idempotenceId))
            idempotence.delete(idempotenceId);
    }

    /**
     * 让第一个请求拿到锁,执行接下来的  验证幂等字符串存在和删除幂等字符串,最后执行业务逻辑,其余请求全部获取锁不成功则直接返回
     *
     * @param joinPoint ProceedingJoinPoint
     * @return joinPoint.proceed()
     * @throws Throwable Throwable
     */
    @Around(value = "idempotenceMethod()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 从HTTP header中获取幂等号idempotenceId
        String idempotenceId = idempotence.getHeaderIdempotenceId();
        if (StringUtils.isEmpty(idempotenceId) ||
                !reentrantLockUtil.tryLock(IDEMPOTENCE_LOCK_PREFIX + idempotenceId, 0, 600) ||
                !idempotence.check(idempotenceId))
            // idempotenceId为空 || 加锁不成功 || 幂等号不存在则直接返回
            throw new BusinessException(BnsResultCode.SYSTEM_INVALID_REQUEST);
        // 删除幂等号
        idempotence.delete(idempotenceId);
        // 执行业务方法
        return joinPoint.proceed();
    }
}

ReentrantLockUtil是一个自定义的基于Redission的加锁工具类。这里使用一般的Redis工具类setKey也行。


在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值