接口幂等性设计

news2025/1/7 21:44:34

幂等性: 对于同一个操作发起一次请求或者多次请求,得到的结果都是一样的,不会因为请求多次而出现异常现象。

场景:

  • 用户多次请求,比如重复点击页面上的按钮
  • 网络异常,右移网络原因导致在一定时间内未返回调用成功的信息,触发了框架层的重试机制
  • 页面回退都再次提交的动作
  • 程序上的重试机制--对未及时响应的请求发起重试操作

Restful 请求方式的幂等性:

  • POST : 相当于新增,不具备幂等性
  • GET : 对资源的获取。在浏览器中通过地址进行访问,每次结果都是一样的,天然幂等
  • PUT : 将一个资源替换成另一个资源。这是非计算型的更新,无论更新多少次,结果都是一样的,天然幂等
  • DELETE : 无论删除多少次,都是一样的,是天然幂等

如何避免重复提交

1.利用全局唯一ID防止重复提交 

在向数据库新增一条记录时,有时会出现错误信息“result in duplicate entry for key primary”,原因是插入了相同的ID信息。

利用数据库的主键唯一特性,可以解决重复提交问题

流程:

  1. 搭建一个生成全局唯一ID的服务,可以参考雪花算法SnowFlow进行搭建
  2. 在订单确定页面中,调用全局唯一ID服务生成订单号
  3. 提交订单时带上订单号,请求到达订单订单系统的下单接口
  4. 订单系统在创建订单信息时,订单号使用前端传过来的订单号,然后直接将订单信息插入数据库
  5. 如果订单写入成功,则是第一次提交,返回下单成功;如果报ID冲突信息,则是重复提交。

 2.利用“Token+Redis”机制防止重复提交

流程:

  1. 订单系统提供一个发放Token的接口。这个Token是一个防重令牌,即一串唯一字符串(可以使用uuid)
  2. 在“订单确认页”中调用获取Token的接口,该接口向订单确认页返回Token,同时将Token写入Redis缓存中,并依据实际业务对其设置一定的有效期
  3. 用户在“订单确认页”中点击“提交订单”按钮时,将第2步Token以参数或者请求头的形式封装进订单信息,然后请求订单系统的下单接口
  4. 下单接口在收到提交下单的请求后,首先判断在Redis中是否存在当前传入的Token
  • 如果存在,则表示这是第1次请求,会删除这个Token,继续创建订单的其他业务
  • 如果不存在,则表示这不是第1此请求,而是重复的请求,会终止后面的业务逻辑

代码实现

生成Token

package com.wxclient.controller;


import com.fasterxml.uuid.Generators;
import com.yl.entitys.RespEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/system/idempotence")
public class IdempotenceController {

    @Autowired
    private RedisTemplate redisTemplate;

    public static final String USER_TOKEN_PREFIX = "idempotence:token:";
    /**
     * 参数token , 放入redis set数据结构,防止重复,返回给前端,做接口幂等性
     */
    @GetMapping("/{userId}")
    public RespEntity idempotence(@PathVariable Integer userId) {
        // 基于时间的UUID(全球唯一)
        UUID uuid = Generators.timeBasedGenerator().generate();
        // 将token 放入redis set中 ,5分钟的过期时间
        redisTemplate.opsForValue().set(USER_TOKEN_PREFIX+userId, uuid,5, TimeUnit.MINUTES);
        log.debug("【系统日志】产生的TOKEN->{}", uuid);
        return RespEntity.okData(uuid);
    }

}

 业务检验

	 /**
     * 添加公告信息
     */
    @ApiOperation(value = "添加公告信息")
    @PostMapping("/")
    public RespEntity add(@RequestBody YlNotice notice, HttpServletRequest httpServletRequest) {
        log.debug("【系统日志】添加公告信息---》");
        // 验证幂等性的标识
        String idempotence = httpServletRequest.getHeader("idempotence");
        // 用户的JWT信息
        String jwt = httpServletRequest.getHeader("jwt");

        JWT token = JWTUtil.parseToken(jwt);
        Integer userId = (Integer) token.getPayload("id");
        // 获取用户ID
        log.debug("【系统日志】用户:{}->",userId);
        log.debug("【幂等性】idempotence:{}->",idempotence);
        //获取redis中的令牌【令牌的对比和删除必须保证原子性】
        //LUA脚本  返回0表示校验令牌失败  1表示删除成功,校验令牌成功
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Long result = (Long) redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
                Arrays.asList(USER_TOKEN_PREFIX + userId),
                idempotence);
        if (result == 1) {
            log.debug("【幂等性】OK:{}->",idempotence);
            log.debug("【系统日志】redis验证成功:{}->",idempotence);
            //令牌验证成功
            //去创建、下订单、验令牌、验价格、锁定库存...
            if (notice.getNoticeTitlet().isEmpty()){
                return new RespEntity(501, "公告标题不能为空", null);
            }
            if (notice.getNoticeContent().isEmpty()){
                return new RespEntity(501, "公告内容不能为空", null);
            }

            // 默认启用
            notice.setNoticeStates("y");
            LocalDateTime dateTime = LocalDateTime.now(); // 获取当前时间
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            notice.setNoticeTime(dateTime.format(formatter));
            noticeService.save(notice);
            return RespEntity.SUCCESS;
        } else {
            log.debug("【幂等性】ERROR:{}->",idempotence);
            log.debug("【系统日志】redis验证失败:{}->",idempotence);
            //令牌校验失败,返回失败信息
            return RespEntity.FAIL;
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/167355.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《JavaScript 核心原理解析》学习笔记 Day 1 delete 引用与值

关于引用与值&#xff1a;在 javaScript 中一个表达式的值或者说结果&#xff0c;可能是引用 / 值。所以 x x &#xff0c;是将右侧表达式x的值赋值给左侧表达式x所指的引用。注意此处的引用并非为到具体内存地址的指向&#xff0c;而是指表达式与其值的一种关联。 这一关联即…

Android 音视频——直播推流技术指南

一、推流架构 推流SDK客户端的模块主要有三个&#xff0c;推流采集端、队列控制模块、推流端。其中每个模块的主要流程如下&#xff0c;本文的主要目的就是拆分推流流程&#xff0c; 1.1 采集端 视频采集&#xff1a;通过Camera采集视频。 音频采集&#xff1a;通过麦克风采…

SSM 05 SpringBoot yaml mybatisplus

01-SpringBoot工程入门案例开发步骤SpringBoot 是 Pivotal 团队提供的全新框架&#xff0c;设计目的是简化 Spring 应用的初始搭建以及开发过程。使用了 Spring 框架后已经简化了我们的开发。而 SpringBoot 又是对 Spring 开发进行简化的&#xff0c;可想而知 SpringBoot使用的…

linux挂载新磁盘

一、查看磁盘挂载状态&#xff1a; fdisk -l df -h 二、为其中一个磁盘创建新的分区&#xff0c;参考&#xff1a; linux用fdisk创建分区,在Linux下用fdisk创建分区_weixin_39968410的博客-CSDN博客 sudo fdisk /dev/nvme0n1 1. 创建主分区&#xff1a; -----------------…

第8章 NVS

NVS Blob块存储 1. 演示app_main任务栈溢出 2. 设置app_main任务栈大小 打开menuconfig&#xff0c;输入main&#xff0c;如下图所示 默认栈大小为3584字节&#xff0c;这里改为35840字节&#xff0c;重新编译 3. Blob存储结果 #include <stdio.h> #include <st…

使用nginx搭建HTTP FLV流媒体服务器

使用nginx搭建HTTP FLV流媒体服务器 文章目录使用nginx搭建HTTP FLV流媒体服务器1 HTTP FLV简介2 HTTP FLV流媒体服务搭建3 结果验证1 HTTP FLV简介 前文已经介绍了RTSP、RTMP、HLS的流媒体协议&#xff0c;还有一种比较常见的流媒体协议HTTP FLV&#xff0c;其兼具RTMP的实时…

Kettle源码启动运行

Kettle源码运行环境如下&#xff1a; windows10 Kettle 9.3.0.2 Java JDK 11 IntelliJ IDEA 2021.2.2 (Community Edition) Maven 3.8.1&#xff08;版本不需要太高 &#xff09; 导入kettle到IDEA 可通过kettle的GIthub地址获取 kettle的克隆连接&#xff0c;或直接下载ZIP压…

python2和python3环境安装

一、背景 ​ 众所周知&#xff0c;python当前有两大主流版本&#xff0c;分别是Python2和Python3系列&#xff0c;其中Python3因为对Python2做了较大的优化&#xff0c;使得Python3不会向下兼容&#xff0c;但是工作和学习中&#xff0c;有很多项目需要Python2的环境&#xff…

SAP 物料账未分摊差异分析

今天在开发处理未分摊差异程序的时候&#xff0c;偶然在网络上看到一篇这样的文章&#xff0c;挺有意思的&#xff0c;特意转载过来&#xff0c;方便大伙学习之用&#xff0c;若有异议&#xff0c;立即撤回。 利用CKMLCP运行完物料分类账之后&#xff0c;差异科目余额通常为0&…

Golang网络聊天室案例

1.聊天室设计分析 一. 概览 实现 个网络聊天室&#xff08;群&#xff09; 功能分析&#xff1a; 上线下线聊天&#xff0c;其他人&#xff0c;自己都可以看到聊天消息查询当前聊天室用户名字 who可以修改自己名字 rename | Duke超时踢出 技术点分析&#xff1a; 1 . sock …

Notion 汉化Macwindows客户端

1、注册/登录账号&#xff1a; https://www.notion.so/zh-cn 2、下载桌面应用&#xff1a; https://www.notion.so/desktop 3、下载汉化js插件 地址&#xff1a;https://github.com/Reamd7/notion-zh_CN 点击最后一次更新的标签&#xff0c;下载【 notion-zh_CN.js 】文件 …

[激光原理与应用-64]:激光器-器件 - 光电二极管

第1章 概述光电二极管&#xff08;Photo-Diode&#xff09;和普通二极管一样&#xff0c;也是由一个PN结组成的半导体器件&#xff0c;也具有单方向导电特性。但在电路中它不是作整流元件&#xff0c;而是把光信号转换成电信号的光电传感器件。普通二极管在反向电压作用时处于截…

如何在Windows上同时搭建多个版本的golang环境——g

假如说我们不同的项目使用的go版本是不一样的&#xff0c;当我们想切换时&#xff0c;不懂的人可能就直接卸载掉现有的go环境去安装新的go环境了&#xff0c;这种方法可行但是有点呆&#xff0c;今天推荐一个好用的go版本管理工具——g,不是寄了的寄&#xff0c;就是英文字母g。…

jQuery(一):选择器、样式操作、动画效果

jQuery选择器样式操作jQuery 动画效果选择器 1.基础选择器 语法格式&#xff1a; $(“选择器”) // 里面选择器直接写 CSS 选择器即可&#xff0c;但是要加引号 常见选择器类型 例如&#xff1a; 2.样式设置 语法格式&#xff1a; $(‘div’).css(‘属性’, ‘值’) 例如&…

PHP 百度知识图谱数据处理与接入SDK

前言最近的项目在做百度知识图谱相关的&#xff0c;需要实现内容将本地数据处理->数据格式转化->数据接入图谱平台。因为百度提供的sdk为Python的sdk&#xff0c;所以我需要将Python程序转换成PHP格式。图谱生产流程介绍1.知识定义建立相应的scheme&#xff0c;确定类目以…

探究SQL SERVER 更改跟踪

介绍SQL SERVER 在2008 以上的版本提供两个用于数据库中跟踪数据更改的功能&#xff1a;变更数据捕获&#xff08;CDC &#xff09;与更改跟踪&#xff08;CT &#xff09;。这两个功能使应用程序能够确定对数据库中的用户表所做的 DML 更改&#xff08;插入、更新和删除操作 &…

Python基础(二十五):异常处理基础知识

文章目录 异常处理基础知识 一、了解异常 二、异常的写法 1、语法

HNUCM蓝桥杯Python组寒假第二次练习

文章目录1316: 选房子2158: 倍数问题2196: X星游戏1034: 近似回文词1749: 最少硬币2079: X星大学2086: 奖牌榜总结&#xff1a;1316: 选房子 1316: 选房子 [命题人 : 外部导入] 时间限制 : 1.000 sec 内存限制 : 128 MB 题目描述 栋栋和李剑已经大四了&#xff0c;想要出去找…

UDS诊断系列介绍13-31服务

本文框架1. 系列介绍1.1 31服务概述2. 31服务请求与应答2.1 31服务请求2.2 31服务正响应2.3 31服务否定响应3. Autosar系列文章快速链接1. 系列介绍 UDS&#xff08;Unified Diagnostic Services&#xff09;协议&#xff0c;即统一的诊断服务&#xff0c;是面向整车所有ECU的…

”凌寒独自开“绽放不一样的自己

目录 1&#xff1a;介绍 2&#xff1a;细节介绍 ​编辑 3&#xff1a;范例演示 4&#xff1a;平台助力 5&#xff1a;冲冲冲 1&#xff1a;介绍 每一个程序员都有一个产品的梦想&#xff0c;在独自开&#xff0c;每一位开发者为自己写代码。 毕竟谁又想错过每一个展示自…