SpringBoot + Redis + 注解 + 拦截器 实现接口幂等性校验

作者:微信小助手

发布时间:2020-04-29T11:42:33

(给ImportNew加星标,提高Java技能)

转自:简书  作者:wangzaiplus

https://www.jianshu.com/p/6189275403ed

一、概念


幂等性, 通俗的说就是一个接口, 多次发起同一个请求, 必须保证操作只能执行一次
比如:

  • 订单接口, 不能多次创建订单


  • 支付接口, 重复支付同一笔订单只能扣一次钱


  • 支付宝回调接口, 可能会多次回调, 必须处理重复回调


  • 普通表单提交接口, 因为网络超时等原因多次点击提交, 只能成功一次
    等等


二、常见解决方案


  1. 唯一索引 -- 防止新增脏数据


  2. token机制 -- 防止页面重复提交


  3. 悲观锁 -- 获取数据的时候加锁(锁表或锁行)


  4. 乐观锁 -- 基于版本号version实现, 在更新数据那一刻校验数据


  5. 分布式锁 -- redis(jedis、redisson)或zookeeper实现


  6. 状态机 -- 状态变更, 更新数据时判断状态


三、本文实现


本文采用第2种方式实现, 即通过redis + token机制实现接口幂等性校验


四、实现思路


为需要保证幂等性的每一次请求创建一个唯一标识token, 先获取token, 并将此token存入redis, 请求接口时, 将此token放到header或者作为请求参数请求接口, 后端接口判断redis中是否存在此token:

  • 如果存在, 正常处理业务逻辑, 并从redis中删除此token, 那么, 如果是重复请求, 由于token已被删除, 则不能通过校验, 返回请勿重复操作提示


  • 如果不存在, 说明参数不合法或者是重复请求, 返回提示即可


五、项目简介


  • springboot


  • redis


  • @ApiIdempotent注解 + 拦截器对请求进行拦截


  • @ControllerAdvice全局异常处理


  • 压测工具: jmeter

说明:

  • 本文重点介绍幂等性核心实现, 关于springboot如何集成redis、ServerResponse、ResponseCode等细枝末节不在本文讨论范围之内, 有兴趣的小伙伴可以查看我的Github项目: https://github.com/wangzaiplus/springboot/tree/wxw


六、代码实现


1.pom

<!-- Redis-Jedis --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.0</version></dependency><!--lombok 本文用到@Slf4j注解, 也可不引用, 自定义log即可--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.10</version></dependency>

2.JedisUtil

package com.wangzaiplus.test.util;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool;@Component@Slf4jpublic class JedisUtil {    @Autowired    private JedisPool jedisPool;    private Jedis getJedis() {        return jedisPool.getResource();    }    /**     * 设值     *     * @param key     * @param value     * @return     */    public String set(String key, String value) {        Jedis jedis = null;        try {            jedis = getJedis();            return jedis.set(key, value);        }