接口幂-全面详解(学习总结---从入门到深化)

news2024/11/15 18:50:46

目录

接口设计与重试机制引发的问题

 什么是幂等性

 为什么会产生接口幂等性问题

 幂等性接口设计

 如何保证接口幂等性

 接口设计与重试机制引发的问题演示_项目搭建

创建项目

 选择框架

 修改SpringBoot版本

 创建用户表

代码生成

引入依赖

编写代码生成类

接口设计与重试机制引发的问题演示_业务实现

编写接口

编写接口实现类

编写控制层

接口幂等性设计_insert操作幂等性原理

 请求流程

 接口幂等性设计_insert操作幂等性实现

 添加Redis依赖

添加Redis相关配置

自定义注解

生成Token

幂等性拦截器

配置拦截器

接口幂等性设计_Update操作幂等性原理

 接口幂等性设计_Update操作幂等性实现

编写用户接口

编写接口实现类

编写Mapper接口

编写Mapper接口语句

测试update操作

 解决方案

页面隐藏版本字段

修改sql语句添加乐观锁


接口设计与重试机制引发的问题

 什么是幂等性

 

 为什么会产生接口幂等性问题

 注意: 不是所有接口都要求幂等性,要根据业务而定。

 幂等性接口设计

1、Select操作:不会对业务数据有影响,天然幂等。

select * from user where user_id = 1;

2、Delete操作:第一次已经删除,第二次也不会有影响。

delete from user where user_id = 1;

3、Update操作:更新操作传入数据版本号,通过乐观锁实现幂等性。

update user set username = 'zhangsan' where
user_id = 1
update user set age = age + 1 where user_id = 1

4、Insert操作:此时没有唯一业务单号,使用Token保证幂等。

insert into order(pkid, order_id, xx) values
(1, '20210304020226953568', ...);

 如何保证接口幂等性

 接口设计与重试机制引发的问题演示_项目搭建

创建项目

创建SpringBoot项目idempotent-demo

 选择框架

 修改SpringBoot版本

 创建用户表

DROP TABLE IF EXISTS user;
CREATE TABLE user
(
   id BIGINT(20) NOT NULL COMMENT '主键ID',
   name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
   age INT(11) NULL DEFAULT NULL COMMENT '年
龄',
  PRIMARY KEY (id)
);
-- 添加用户数据
INSERT INTO user (id, name, age) VALUES
(1, 'Jone', 18),
(2, 'Jack', 20),
(3, 'Tom', 28),
(4, 'Sandy', 21 ),
(5, 'Billie', 24);

代码生成

引入依赖

    <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plusgenerator</artifactId>
          <version>3.5.2</version>
    </dependency>
        <!-- 模板引擎 -->
    <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-enginecore</artifactId>
            <version>2.0</version>
    </dependency>

编写代码生成类

package com.itbaizhan.lock.utils;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.util.Arrays;
import java.util.List;
public class CodeGenerator {
public static void main(String[] args) {
      FastAutoGenerator.create("jdbc:mysql://192.168.66.100:3306/distribute", "root", "123456")
               .globalConfig(builder -> {
                    builder.author("itbaizhan")// 设置作者
                           .commentDate("MMdd") // 注释日期格式
                           .outputDir(System.getProperty("user.dir")+ "/src/main/java/") // 指定输出目录
                           .fileOverride(); //覆盖文件
               })
                // 包配置
               .packageConfig(builder -> {
                  builder.parent("com.itbaizhan.lock") // 包名前缀
                           .entity("entity")//实体类包名
                           .mapper("mapper")//mapper接口包名
                           .service("service"); //service包名
               })
               .strategyConfig(builder -> {
                    List<String> strings = Arrays.asList("t_order");
                    // 设置需要生成的表名
                    builder.addInclude(strings)
                            // 开始实体类配置
                           .entityBuilder()
                            // 开启lombok模型
                           .enableLombok()
                            //表名下划线转驼峰
                           .naming(NamingStrategy.underline_to_camel)
                            //列名下划线转驼峰
                           .columnNaming(NamingStrategy.underline_to_camel);
               })
               .execute();
   }
}

接口设计与重试机制引发的问题演示_业务实现

编写接口

    /**
     * 查询所有用户
     * @return
     */
    List<User> findAll();
    /**
     * 创建用户
     * @param name
     * @param age
     * @return
     */
    Integer create(String name ,Integer age);
    /**
     * 根据id查询用户
     * @param id
     * @return
     */
    User findById(Long id);
    /**
     * 更新用户
     * @param user
     * @return
     */
    Integer update(User user);

编写接口实现类

package com.itbaizhan.idempotentdemo.service.impl;
import com.itbaizhan.idempotentdemo.entity.User;
import com.itbaizhan.idempotentdemo.mapper.UserMapper;
import com.itbaizhan.idempotentdemo.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl .ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* <p>
* 服务实现类
* </p>
*
* @author itbaizhan
* @since 06-04
*/
@Service
public class UserServiceImpl extends
ServiceImpl<UserMapper, User> implements IUserService {
    /**
     * 查询全部用户
     * @return
     */
    @Override
    public List<User> findAll() {
        return baseMapper.selectList(null);
     }
    /**
     * 创建用户
     * @param name
     * @param age
     * @return
     */
    @Override
    public Integer create(String name, Integer age) {
        User user = new User();
        user.setName(name);
        user.setAge(age);
        return baseMapper.insert(user);
   }
    /**
     * 根据用户id查询用户
     * @param id
     * @return
     */
    @Override
    public User findById(Long id) {
        return baseMapper.selectById(id);
   }
    /**
     * 更新用户
     * @param user
     * @return
*/
    @Override
    public Integer update(User user) {
        return baseMapper.updateById(user);
   }
}

编写控制层

/**
     * 跳转首页
     * @return
     */
    @GetMapping("/index")
    public ModelAndView  list(){
        ModelAndView modelAndView = new ModelAndView();
        List<User> all = iUserService.findAll();
        modelAndView.setViewName("index");
        modelAndView.addObject("users",all);
        return modelAndView;
   }
    /**
     * 创建用户
     * @return
     */
    @ApiIdempotentAnn
    @PostMapping("/create")
    public String  create(String name,Integer age){
       Integer integer = iUserService.create(name, age);
        if (integer == 1){
            return "redirect:/user/index";
       }
        return "addUser";
   }
    /**
     * 根据用户id查询用户
     * @param id 用户id
     * @return
     */
    @GetMapping("/getByUserId")
    public ModelAndView  getByUserId(Long id){
        User user = iUserService.findById(id);
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("user",user);
        modelAndView.setViewName("update");
        return modelAndView;
   }
    /**
     * 更新
     * @return
     */
    @PostMapping("/update")
    public String  update(User user){
        Integer update = iUserService.update(user);
      if (update == 1){
            return "redirect:/user/index";
       }
        return "update";
   }

接口幂等性设计_insert操作幂等性原理

 请求流程

 流程:

为需要保证幂等性的每一次请求创建一个唯一标识token, 先获 取token, 并将此token存入redis, 请求接口时, 将此token放到 header或者作为请求参数请求接口, 后端接口判断redis中是否 存在此token,如果存在, 正常处理业务逻辑, 并从redis中删除此 token, 那么, 如果是重复请求, 由于token已被删除, 则不能通过 校验, 返回重复提交如果不存在, 说明参数不合法或者是重复请 求, 返回提示即可。

 接口幂等性设计_insert操作幂等性实现

 添加Redis依赖

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

添加Redis相关配置

spring.redis.host=localhost
spring.redis.port=6379

自定义注解

即添加了该注解的接口要实现幂等性验证。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiIdempotentAnn {
  boolean value() default true;
}

生成Token

    /**
     * 跳转注册页面
     * @return
     */
    @GetMapping("/toAddUser")
    public ModelAndView  adduser(){
        ModelAndView modelAndView = new ModelAndView();
        // 生成Token
        String s = UUID.randomUUID().toString();
        // 保存redis
        stringRedisTemplate.opsForValue().set(s,Thread.currentThread().getId()+"");
        modelAndView.setViewName("addUser");
        modelAndView.addObject("token",s);
        return modelAndView;
   }

幂等性拦截器

package com.itbaizhan.idempotentdemo.config;
import com.itbaizhan.idempotentdemo.ApiIdempotentAnn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Method;
@Component
public class ApiIdempotentInceptor implements
HandlerInterceptor {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    /**
     *表示在所有请求之前完成的拦截,一般使用居多
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
       }
        final HandlerMethod handlerMethod = (HandlerMethod) handler;
        // 获取方法
        final Method method = handlerMethod.getMethod();
        // 判断有没有添加需要幂等性注解
        boolean methodAnn = method.isAnnotationPresent(ApiIdempotentAnn.class);
        // 判断是否开启幂等性严重
        if (methodAnn && method.getAnnotation(ApiIdempotentAnn.class).value()) {
            // 需要实现接口幂等性
            boolean result = checkToken(request);
        if (result) {
                return true;
           } else {
               response.setContentType("application/json;charset=utf-8");
                PrintWriter writer = response.getWriter();
                writer.print("重复调用");
                writer.close();
                response.flushBuffer();
                return false;
           }
       }
        return false;
   }
    private boolean checkToken(HttpServletRequest request) {
        String token = request.getParameter("token");
        if (null == token || "".equals(token))
          {
            // 没有token,说明重复调用或者
            return false;
       }
        // 返回是否删除成功
        return stringRedisTemplate.delete(token);
   }
}

配置拦截器

WebMvcConfigurer配置类其实是 Spring 内部的一种配置方式,可以 自定义一些Handler,Interceptor,ViewResolver, MessageConverter等等的东西对springmvc框架进行配置。

 

package com.itbaizhan.idempotentdemo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private ApiIdempotentInceptor apiIdempotentInceptor;
    @Override
  public void addInterceptors(InterceptorRegistry registry) {
        List<String> list=new ArrayList<String>();
        list.add("/user/toAddUser");
        list.add("/user/index");
        registry.addInterceptor(apiIdempotentInceptor).excludePathPatterns(list);
   }
}

接口幂等性设计_Update操作幂等性原理

 既然悲观锁有性能问题,为了提升接口性能,我们可以使用乐观 锁。需要在表中增加一个 timestamp 或者 version 字段,这里以 version 字段 为例。

--在更新数据之前先查询一下数据:
select id,name,age,version from user id=123;

 如果数据存在,假设查到的 version 等于 1 ,再使用 id 和 version 字段作为查询条件更新数据:

update user set age=age+1,version=version+1
where id=123 and version=1;

更新数据的同时 version+1 ,然后判断本次 update 操作的影响行数, 如果大于0,则说明本次更新成功,如果等于0,则说明本次更 新没有让数据变更。

 

 具体步骤:

1 先根据id查询用户信息,包含version字段

2 根据id和version字段值作为where条件的参数,更新用户信息,同时version+1

3 判断操作影响行数,如果影响1行,则说明是一次请求,可以做其他数据操作。

4 如果影响0行,说明是重复请求,则直接返回成功。

 接口幂等性设计_Update操作幂等性实现

编写用户接口

    /**
     * 更新用户
     * @param id 用户id
     * @return
     */
    Integer updateAge(Long id);

编写接口实现类

    /**
     * 更新年纪
     * @param id
     * @return
     */
    @Override
    public Integer updateAge(Long id) {
        return baseMapper.updateAge(id);
   }

编写Mapper接口

public interface UserMapper extends BaseMapper<User> {
    Integer updateAge(@Param("id") Long id);
}

编写Mapper接口语句

<mapper namespace="com.itbaizhan.idempotentdemo.mapper.UserMapper">
    <update id="updateAge" parameterType="long">
       update user set age = age + 1 where id =   #{id}
    </update>
</mapper>

测试update操作

 更新Jone数据。

多次点击更新按钮,出现多次更新操作。

 

 解决方案

数据库表添加versino字段

 实体类添加version字段

    /**
     * 版本
     *
     */
    private Integer version;

页面隐藏版本字段

<form method="post" action="/user/update" >
        <input name="id" th:value="${user.id}" type="text" hidden>
        <label>用户名:</label> 
        <input name="name" th:value="${user.name}" type="text">
        <label>年龄:</label> <input name="age" th:value="${user.age}" type="number">
        <input name="version" hidden th:value="${user.version}" >
        <input type="submit" value="更新">
</form>

修改sql语句添加乐观锁

<mapper namespace="com.itbaizhan.idempotentdemo.mapper.UserMapper">
    <update id="updateAge"  >
        update user set age = age + 1,version =
        version + 1 where id = #{id} and version = # {version}
    </update>
</mapper>

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

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

相关文章

Scaling Up Your Kernels to 31x31: Revisiting Large Kernel Design in CNNs笔记

论文地址&#xff1a;https://arxiv.org/pdf/2203.06717.pdf 代码地址&#xff1a;https://github.com/MegEngine/RepLKNet 目录 论文地址&#xff1a;https://arxiv.org/pdf/2203.06717.pdf 代码地址&#xff1a;https://github.com/MegEngine/RepLKNet Abstract 1. Introd…

MySQL架构MMM

官网地址&#xff1a; Multi-Master Replication Manager for MySQL [MMM for MySQL Wiki] 简介 MMM&#xff08;Master-Master replication manager for MySQL&#xff09;是一套支持双主故障切换和双主日常管理的脚本程序。MMM使用Perl语言开发&#xff0c;主要用来监控和…

Kafka - 14 Kafka分区的分配策略及再平衡 | Range | RoundRobin | Sticky | CooperativeSticky

文章目录1. 分区的分配以及再平衡2. Range 分区分配以及再平衡3. RoundRobin 分区分配以及再平衡4. Sticky 分区分配以及再平衡1. 分区的分配以及再平衡 一个consumer group中有多个consumer组成&#xff0c;一个 topic有多个partition组成&#xff0c;现在的问题是&#xff0…

[Windows驱动开发] BlackBone介绍

▒ 目录 ▒&#x1f6eb; 导读需求开发环境1️⃣ 名词解释2️⃣ 介绍3️⃣ 库包含内容4️⃣ 编译问题总结常见问题fatal error LNK1104非类型模板参数中的 "auto" &#x1f4d6; 参考资料&#x1f6eb; 导读 需求 作为Windows开发人员&#xff0c;经常遇到枚举进程、…

dojo中的类

使用arcgis api for js 4.*进行地图的web前端开发&#xff0c;就不得不与dojo打交道。dojo是一个框架&#xff0c;自成体系&#xff0c;比如它对类的支持&#xff0c;有自己的一套。众所周知&#xff0c;js不是面向对象语言&#xff0c;没有类这一说&#xff0c;都是用函数来模…

人工智能轨道交通行业周刊-第25期(2022.11.28-12.4)

本期关键词&#xff1a;液体安检仪、智慧车站、大机作业、动车打温、实时人体姿态估计、图像压缩 1 整理涉及公众号名单 1.1 行业类 RT轨道交通中关村轨道交通产业服务平台人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟V…

Java项目:SSM医院挂号预约管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目分为管理员与医生两种角色&#xff1b; 管理员角色包含以下功能&#xff1a; 管理员登录,添加科室,科室增删改查,医生管理,查看预约信息,…

Redis03:Redis基础知识以及数据类型

Redis基础知识以及数据类型基础知识Redis-key基本数据类型String(字符串)三种特殊的数据类型geospatialhyperloglogbitmaps基础知识 redis默认有16个数据库&#xff0c;默认使用的时第0个&#xff0c;可以使用select进行切换数据库 清除当前数据库 清除全部数据库的内容 Red…

Cmake升级与软链接

记录cmake升级版本的记录&#xff0c;主要参考文章&#xff1a;Unbuntu安装Ros后Cmake变成3.10_向日葵骑士Faraday的博客-CSDN博客 cmake升级、更新&#xff08;ubuntu18.04&#xff09;_Doctor_Wu_的博客-CSDN博客_cmake升级 设计安装ros后cmake版本出现问题&#xff0c;且卸…

[附源码]Python计算机毕业设计Django基于Java的员工管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Windows 文件共享功能使用方法,局域网多台电脑之间传送文件

设想一下&#xff0c;家里或者公司有多台电脑&#xff0c;连接同一个Wifi&#xff0c;也就是处于同一个局域网中。 在不能使用微信、网盘的文件传输功能的情况下&#xff0c;这多台电脑之间&#xff0c;就只能用U盘传送数据吗&#xff1f; 不。Windows系统中已经提供了文件共享…

ZMQ之克隆模式的可靠性

克隆服务器的可靠性 克隆模型1至5相对比较简单&#xff0c;下面我们会探讨一个非常复杂的模型。可以发现&#xff0c;为了构建可靠的消息队列&#xff0c;我们需要花费非常多的精力。所以我们经常会问&#xff1a;有必要这么做吗&#xff1f;如果说你能够接受可靠性不够高的、或…

Docker三大核心概念(镜像、容器和仓库)与虚拟化

目录 1. Docker是什么 2. Docker与虚拟化 3. Docker虚拟化的好处 4. Docker核心概念 4.1.镜像 4.2.容器 4.3.仓库 5. CentOS7 安装docker(在线方式) 5.1.内核版本信息检查 5.2 卸载可能存在的旧版本 5.3 安装必要的系统工具 5.4 添加docker-ce安装源 5.5 更新yum缓存 5.…

web前端期末大作业:个人网站设计——响应式个人小站网站HTML+CSS+JavaScript

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

STC 51单片机54——气压水压计HX710B 串口显示均值滤波+滑窗滤波

//气压模块为红色模块&#xff0c;传感器型号未知&#xff0c;其信号放大器型号为HX710B // STC15W408AS 11.0592MHz 波特率9600&#xff0c;串口输出大气压强值 // STC15W408AS没有定时器1&#xff0c;所以用定时器2做波特率发生器 // 采用电脑USB供电会有很大的干扰&#xff…

Unity工具 - 工具聚合页(UEWindow)

随着项目工程的推进&#xff0c;开发者们会根据工作内容的需要在Unity内开发众多的工具。随着工具的增多&#xff0c;Unity 的Menu菜单也会逐渐臃肿&#xff0c;过于分散&#xff0c;工具代码也难以查找。在此问题的基础上&#xff0c;开发了工具聚合页(UEWindow) 这一功能来管…

R绘图案例|基于分面的面积图

简介 最近参加一个统计建模的比赛。模型建模后&#xff0c;需要展示不同模型的性能指标&#xff0c;数据如下所示&#xff1a; 其中&#xff0c;第 1 列是不同样本&#xff0c;共376条。第 2-4 列是随机森林得到的结果&#xff0c;第 5-7 列是XGBoost的结果。一共使用了三种评…

数字验证学习笔记——UVM学习3 核心基类

一、核心基类 UVM世界中的类最初都是从一个uvm_void根类&#xff08;root class&#xff09;继承来的&#xff0c;而实际上这个类并没有成员变量和方法。 uvm_void只是一个虚类&#xff08;virtual class&#xff09;&#xff0c;还在等待将来继承于它的子类去开垦。在继承与u…

适合新手的Pytorch的中文文档

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

牛客练习赛106

牛客练习赛106 C D 脑筋急转弯的构造题 E 染色法判断二分图 结论&#xff0c;这个图是二分图说明不存在奇环 设左边是x&#xff0c;右边是y 则有xyn,xyn, xyn,且x∗y>边数n∗(n−1)/2−mx*y>边数n*(n-1)/2-m x∗y>边数n∗(n−1)/2−m 也就是说左式最大是n∗n/4(xn/…