• 接口幂等性探讨


    概念

    接口幂等性指的是同一个接口,多次发出的同一个请求,必须保证操作只执行一次。比如在极短时间内同一个订单用户点击了2次提交,这时第二次点击应视为无效点击。

    实现方案举例

    基于请求凭证,token机制

    此种方式比较常见,大致流程如下:

    1. 客户端预先向服务端申请一个唯一token作为请求凭证
    2. 客户端调用接口时,携带token
    3. 服务端获取客户端token,执行redis SETNX命令将token指定有效期写入redis(key不存在则写入成功,否则失败),若命令执行失败,则表示重复操作,直接返回错误信息给客户端。

    数据库唯一索引

    此种方式改动量小但和业务密切相关,以新增用户功能为例:

    1. 给用户表的身份证字段设置非空唯一索引。
    2. 用户首次点击时由于用户表不存在该身份证号,保存成功。
    3. 用户重复点击时由于用户表已存在该身份证号,保存失败,程序捕获唯一索引冲突异常。

    基于请求体哈希值

    此种方式原理类似于token机制, 但比token更加简单,无需修改客户端逻辑,原理如下:

    1. 通过一定策略辨别出哪些请求是重复的,可以选择给请求体等取SHA256哈希值,SHA256碰撞概率极其低。
    2. 后端为每个请求生成hash值。
    3. 若该hash值在指定时间内出现过(可通过redis实现),则返回错误信息给客户端。
    4. 若该hash值在指定时间内未出现,则指定有效期暂存该hash值(可指定过期时间存入redis),接着执行业务逻辑。

    例子:

    • 通过filter拦截请求。
    • 通过对请求体长度+ip+uri+User-Agent取hash256哈希值。
    • 通过判断sha256哈希值是否已在缓存中来辨别请求是否重复。
    • 将sha256哈希值缓存指定时间。
    • 代码如下:
    @WebFilter(filterName = "duplicateFilter", urlPatterns = "/*")
    public class DuplicateRequestFilter implements Filter {
        //缓存最近2秒内请求的哈希值
        private final TtlMap<String, Long> CONTENT_HASH = new TtlMap<>(100_000, 2_000);
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            //TODO 校验
            if (!needCheck(request) || !checkDuplicateRq(request)){
                filterChain.doFilter(servletRequest, servletResponse);
                return;
            }
            //TODO 输出错误信息
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            writeErrResult(response);
        }
    
        /** 请求是否需要校验 */
        private boolean needCheck(HttpServletRequest request){
            String method = request.getMethod();
    
            return !Arrays.asList("GET", "HEAD", "OPTIONS", "TRACE", "CONNECT").contains(method);
        }
        /**
         * @description:通过请求体长度,ip,uri等进行校验
         * @date 17:07 2022/8/31
         * @param request
         * @return true:重复/false:不重复
         **/
        private boolean checkDuplicateRq(HttpServletRequest request) {
            //获取请求uri,ip,客户端类型
            String uri = request.getRequestURI();
            String ip = request.getRemoteAddr();
            String userAgent = request.getHeader("User-Agent");
            //获取请求体长度
            String length = request.getHeader("Content-Length");
            String content = new StringBuilder(ip)
                    .append(uri)
                    .append(userAgent)
                    .append(length).toString();
    
            //取sha256哈希
            String contentHash = HexUtil.getHexString(content, HexAlgorithmEnum.SHA256);
            boolean hit = CONTENT_HASH.get(contentHash) != null;
            //刷新有限期
            CONTENT_HASH.put(contentHash, System.currentTimeMillis());
            //返回是否命中
            return hit;
        }
    
        private void writeErrResult(HttpServletResponse response ) throws IOException {
            BaseResultVo resultVo = new BaseResultVo(409, false, "请求重复");
            response.setContentType("application/json;charset=UTF-8");
            response.setCharacterEncoding("UTF-8");
    
            PrintWriter writer = response.getWriter();
            writer.write( JSON.toJSONString(resultVo) );
            writer.flush();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
  • 相关阅读:
    java计算机毕业设计高校在线办公系统源程序+mysql+系统+lw文档+远程调试
    分治-19寻找峰值,20逆序对
    Redis - 高级
    vue xterm4.x自定义请求报文
    ubuntu 20.04 SD 卡分区类型 msdos 改为 GPT 的方法
    安卓kotlin面试题 91-100
    『现学现忘』Git基础 — 26、给Git命令设置别名
    一款IM即时通讯聊天系统源码,包含app和后台源码
    【Swift 60秒】53 - Using closures as parameters when they accept parameters
    《你不可不知的人性》经典语录
  • 原文地址:https://blog.csdn.net/qq_41633199/article/details/126616566