API幂等设计

article/2025/9/25 10:11:28

API幂等就是在新增或更新数据时,如果多次发起同一个请求,只能产生一个结果。如:同一个订单多次提交,只能在数据库产生一个订单数据。我了解的基于redis实现幂等的有两种方式:基于token和基于请求。

基于token认证

参考大神:

https://blog.csdn.net/id5555/article/details/105575435

  • 客户端获取服务端token, 服务端产生token之后将token放入redis中;
  • 客户端将获取的token放入请求头或请求参数中,发起提交请求;
  • 服务器端检验请求的token,如果没有就报错;如果有就查询redis中有无对应的token,如果没有就重复请求,如果有就删除token,放行请求;
  • 服务器重复请求时,由于之前的token被删除,请求被拦截,从而实现幂等
    在这里插入图片描述

这里介绍使用自定义注解+拦截器的方式实现幂等校验。Redis相关的配置见springboot集成redis

首先定义注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoIdempotent {
}

接着定义token接口:

import javax.servlet.http.HttpServletRequest;public interface ITokenService {/*** 创建token* @return token*/public  String createToken();/*** 检验token* @param request* @return true/false*/public boolean checkToken(HttpServletRequest request) throws Exception;
}

定义实现类:

import com.xxx.xlt.utils.constant.ApiResult;
import com.xxx.xlt.utils.constant.RedisConstant;
import com.xxx.xlt.utils.exception.CommonException;
import com.xxx.xlt.utils.redis.RedisUtil;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest;
import java.util.UUID;/*** Token service*/
@Service
public class TokenService implements ITokenService {/*** 创建token** @return token*/@Overridepublic String createToken() {String token = UUID.randomUUID().toString().replace("-", "");try {String key = RedisConstant.PREFIX + RedisConstant.API_TOKEN;RedisUtil.set(key, token, 30L);if (!StringUtils.isEmpty(token)) {return token;}} catch (Exception ex) {ex.printStackTrace();}return null;}/*** 检验token** @param request http request* @return true/false*/@Overridepublic boolean checkToken(HttpServletRequest request) throws Exception {String token = request.getHeader(RedisConstant.API_TOKEN);if (StringUtils.isEmpty(token)) {// header中不存在tokentoken = request.getParameter(RedisConstant.API_TOKEN);if (StringUtils.isEmpty(token)) {// parameter中也不存在tokenthrow new CommonException(ApiResult.BAD_ARGUMENT);}}String key = RedisConstant.PREFIX + RedisConstant.API_TOKEN;if (!RedisUtil.hasKey(key)) {throw new CommonException(ApiResult.REPETITIVE_REQUEST);}if(RedisUtil.get(key).equals(token)) {RedisUtil.del(token);} else {throw new CommonException(ApiResult.API_TOKEN_ERROR);}return true;}
}

定义认证拦截器

import com.xxx.xlt.utils.constant.ApiResult;
import com.xxx.xlt.utils.exception.CommonException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;@Slf4j
public class AuthInterceptor extends HandlerInterceptorAdapter {@Autowiredprivate ITokenService tokenService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (!(handler instanceof HandlerMethod)) {return true;}HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();//被ApiIdempotent标记的扫描AutoIdempotent methodAnnotation = method.getAnnotation(AutoIdempotent.class);if (methodAnnotation != null) {try {return tokenService.checkToken(request); // 幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示} catch (Exception ex) {throw new CommonException(ApiResult.REPETITIVE_REQUEST);}}return true;}
}

Web Mvc配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;@Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {@Beanpublic AuthInterceptor authInterceptor() {return new AuthInterceptor();}/*** 拦截器配置** @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(authInterceptor());
//                .addPathPatterns("/ksb/**")
//                .excludePathPatterns("/ksb/auth/**", "/api/common/**", "/error", "/api/*");super.addInterceptors(registry);}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");super.addResourceHandlers(registry);}
}

测试类

import com.xxx.xlt.constant.Constant;
import com.xxx.xlt.model.BasicResponse;
import com.xxx.xlt.utils.idempotent.AutoIdempotent;
import com.xxx.xlt.utils.idempotent.ITokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 测试接口幂等*/
@RestController
@RequestMapping("/idempotent")
public class BusinessController {@Autowiredprivate ITokenService tokenService;@GetMapping("/get/token")public Object  getToken(){String token = tokenService.createToken();return  new BasicResponse(token,"200", Constant.Status.SUCCESS);}@AutoIdempotent@GetMapping("/test")public Object testIdempotence() {String token = "接口幂等性测试成功";return new BasicResponse(token,"200", Constant.Status.SUCCESS);}
}

基于请求参数

参考大神:https://blog.csdn.net/hanchao5272/article/details/92073405

这种方式采用注解+切面的方式实现,比前面的方式简单一些,不需要客户端提前获取token,然后再发起业务请求;它根据入参的情况产生幂等的key, 并将key存入redis中一段时间,标识该请求已经发生了,后面再发情同样参数的请求,会校验Redis中是否存在该幂等key, 如果存在则拦截该请求;如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jk3EbmtA-1610467009378)(D:%5C%E6%88%91%E7%9A%84%E6%96%87%E6%A1%A3%5Ctypora%E6%96%87%E6%A1%A3%5Cpictures%5CAPI%E5%B9%82%E7%AD%89%E8%AE%BE%E8%AE%A1%5Cimage-20210112231132923.png)]

定义注解@Idempotent:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Idempotent {/*** 幂等名称,作为redis缓存Key的一部分。*/String value();/*** 幂等过期时间,即:在此时间段内,对API进行幂等处理。*/long expireMillis();
}

定义切面处理类IdempotentAspect.java

@Aspect
@Component
@ConditionalOnClass(RedisTemplate.class)
public class IdempotentAspect {private static Logger logger = LoggerFactory.getLogger(IdempotentAspect.class);/*** 根据实际路径进行调整*/@Pointcut("@annotation(com.xxx.xlt.utils.idempotent.method2.Idempotent)")public void executeIdempotent() {}@Around("executeIdempotent()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {//获取方法Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();//获取幂等注解Idempotent idempotent = method.getAnnotation(Idempotent.class);//根据 key前缀 + @Idempotent.value() + 方法签名 + 参数 构建缓存键值//确保幂等处理的操作对象是:同样的 @Idempotent.value() + 方法签名 + 参数String key = String.format("idempotent_%s", idempotent.value() + "_" + KeyUtil.generate(method, joinPoint.getArgs()));//存入redisif (RedisUtil.hasKey(key)&&RedisUtil.get(key).equals(key)) {throw new IdempotentException("Repetitive request for "+key);}RedisUtil.set(key, key, idempotent.expireMillis());return joinPoint.proceed();}
}

工具类KeyUtil.java

public class KeyUtil {private static final Logger LOGGER = LoggerFactory.getLogger(KeyUtil.class);/*** 根据{方法名 + 参数列表}和md5转换生成key*/public static String generate(Method method, Object... args) {StringBuilder sb = new StringBuilder(method.toString());for (Object arg : args) {sb.append(toString(arg));}return DigestUtils.md5DigestAsHex(sb.toString().getBytes());}private static String toString(Object object) {if (object == null) {return "null";}if (object instanceof Number) {return object.toString();}//调用json工具类转换成Stringreturn JsonUtil.toJson(object);}
}/*** Json格式化工具** @author Alex*/
class JsonUtil {private static final Logger LOGGER = LoggerFactory.getLogger(JsonUtil.class);private static final ObjectMapper MAPPER = new ObjectMapper();static {MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).setSerializationInclusion(JsonInclude.Include.NON_NULL);}/*** Java Object Maps To Json*/public static String toJson(Object obj) {String result;if (obj == null || obj instanceof String) {return (String) obj;}try {result = MAPPER.writeValueAsString(obj);} catch (Exception e) {LOGGER.error("Java Object Maps To Json Error !");throw new RuntimeException("Java Object Maps To Json Error !", e);}return result;}
}

使用示例:

    @Override@Idempotent(value="OrderService.addNewOrderHead",expireMillis=100L)public CommonResponse<OrderHead> addNewOrderHead(OrderHead orderHead) {CommonResponse<OrderHead> response = new CommonResponse<>();if (StringUtils.isEmpty(orderHead.getOrderDate())) {throw new CommonException("orderDate is empty.");}if (StringUtils.isEmpty(orderHead.getOrderNo())) {throw new CommonException("orderNo is empty.");}Long orderId = SnowflakeIdGenerator.generateId();orderHead.setOrderHeadId(orderId);orderHeadMapper.insertOrderHead(orderHead);response.setData(Collections.singletonList(orderHead));return response;}

http://chatgpt.dhexx.cn/article/kpkCeCsX.shtml

相关文章

Mysql实现幂等_过幂等性问题

一、什么是幂等? 幂等性:多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致。 二、使用幂等的场景 1、前端重复提交 用户注册,用户创建商品等操作,前端都会提交一些数据给后台服务,后台需要根据用户提交的数据在数据库中创建记录。如果用…

分布式锁实现业务幂等

1、前言 现如今很多系统都会基于分布式或微服务思想完成对系统的架构设计。那么在这一个系统中&#xff0c;就会存在若干个微服务&#xff0c;而且服务间也会产生相互通信调用。那么既然产生了服务调用&#xff0c;就必然会存在服务调用延迟或失败的问题。当出现这种问题&…

幂等性组件

根据上篇文章改进&#xff0c;并做成springbootstarter&#xff0c;依赖于redisson&#xff0c;如果redission已配置&#xff0c;则该组件可开箱即用 接口防重复提交_King_Kwin的博客-CSDN博客_接口防重复提交 本篇已做了较大的改动&#xff0c;可查看代码 idempotent: 幂等…

java幂等控制_JAVA幂等性实现

什么是幂等&#xff1a; 贴一张百度百科的图&#xff1a; 简单来说幂等保证了只要调用接口成功,外部多次调用对系统的影响是一致的&#xff0c;也就是一个请求多次重试的问题。 需要考虑幂等的场景&#xff1a; 客户端存在多次提交或者超时重试的情况&#xff1b; 分布式架构中…

幂等问题

点击↑上方↑蓝色“编了个程”关注我~ 每周至少一篇原创文章 这是本公众号的第 34 篇原创文章 最近比较懒&#xff0c;好几周没写文章了。还是没能坚持每周更新&#xff0c;愧对关注我的读者和自己的flag。。。 不过还好调整了过来&#xff0c;还是会继续坚持周更的。毕竟学习是…

常见的幂等性解决方案

背景 我们实际系统中有很多操作&#xff0c;是不管做多少次&#xff0c;都应该产生一样的效果或返回一样的结果。 get请求一般没有幂等性需求、delete请求一般也没有幂等性需求&#xff0c;post、update视情况而定例如&#xff1a; 前端重复提交选中的数据&#xff0c;应该后…

彻底理解接口幂等性

目录 背景 1. 幂等性的概念 2.什么情况需要处理接口幂等性问题&#xff1f; 2.1 select 天然自带幂等性。 2.2 insert 当我们重复插入数据的时候&#xff0c;会出现什么情况 &#xff1f; 2.3 delete 是否具有幂等性&#xff1f; 2.4 update 猜一猜是否具有天热幂等性&a…

【实战】聊聊幂等设计

前言 大家好,今天我们一起来聊聊幂等设计。 什么是幂等为什么需要幂等接口超时,如何处理呢?如何设计幂等?实现幂等的8种方案HTTP的幂等1. 什么是幂等? 幂等是一个数学与计算机科学概念。 在数学中,幂等用函数表达式就是:f(x) = f(f(x))。比如求绝对值的函数,就是幂等…

幂等性 详解

目录 一、幂等概念 1、幂等的数学概念 2. 幂等的业务概念 二、幂等概述 三、幂等场景 四、解决方案 1、token redis机制 2、乐观锁机制 3、唯一主键机制 4、去重表机制 5、门票机制 一、幂等概念 1、幂等的数学概念 如果在一元运算中&#xff0c;x 为某集合中的任…

幂等 (idempotence) 的概念

幂等 (idempotence) 的概念 幂等的数学概念 幂等是源于一种数学概念。其主要有两个定义 如果在一元运算中&#xff0c;x 为某集合中的任意数&#xff0c;如果满足 f(x) f(f(x)) &#xff0c;那么该 f 运算具有幂等性&#xff0c;比如绝对值运算 abs(a) abs(abs(a)) 就是幂…

偏度

偏度公式如下&#xff1a; 现在想解决如何从图像上解决为正为负的问题&#xff0c;如图所示&#xff1a; 个人理解&#xff1a;偏度中的偏是针对变量相对于中心点&#xff08;期望值&#xff09;距离的一种描述&#xff1b;如果厚尾的话&#xff0c;就说明有很多点距离中心点比…

偏度(skewness)

偏度 偏度&#xff08;skewness&#xff09;&#xff0c;是统计数据分布偏斜方向和程度的度量&#xff0c;是统计数据分布非对称程度的数字特征。定义上偏度是样本的三阶标准化矩。 偏度定义中包括正态分布&#xff08;偏度0&#xff09; 算术平均值 中位数 众数&#xff0c;…

策略梯度

Policy Gradient Methods for Reinforcement Learning with Function Approximation(PG) 在强化学习的算法中存在两种算法&#xff0c;一个是基于价值函数的算法&#xff0c;另一个是基于策略梯度的算法。为什么要提出策略梯度算法呢&#xff1f; 基于策略的学习可能会具有更好…

推荐系统中的偏差

推荐系统消偏 推荐系统中的偏差IPW ——逆概率加权DICE ——区分兴趣和偏差建模因果推断 推荐系统中的偏差 预估问题 我们一般会注重两种误差&#xff0c;偏差和方差&#xff0c; 方差与模型泛化能力有关&#xff1a;通常关注模型的复杂度与是否过拟合&#xff1b;偏差则表现为…

特征偏度和异常值处理

&#xff08;一&#xff09;机器学习基础 - 偏度、正态化以及 Box-Cox 变换 https://my.oschina.net/mathinside/blog/4942126 对于数据挖掘、机器学习中的很多算法&#xff0c;往往会假设变量服从正态分布。例如&#xff0c;在许多统计技术中&#xff0c;假定误差是正态分布…

推荐系统去偏(Debiased Recommendation)研究进展概述

©作者 | 张景森 学校 | 中国人民大学信息学院硕士 文章来源 | RUC AI Box 引言 推荐系统作为解决信息过载的一种重要手段&#xff0c;已经在不同的应用场景下取得了不错的效果。近些年来关于推荐系统的研究主要集中在如何设计更好的模型来适应用户行为数据&#xff0c;进而…

【综述】推荐系统偏差问题 去偏最新研究进展(Bias and Debias in Recommender System)

文章目录 1. 推荐系统的反馈回路1.1 User -> Data1.2 Data -> Model1.3 Model -> User 2. 推荐系统中的Bias2.1 数据偏差(data bias)2.1.1 选择偏差(Selection Bias)2.1.2 曝光偏差(Exposure Bias)2.1.3 一致性偏差(Conformity Bias)2.1.4 位置偏差(Position Bias) 2.…

数据偏度介绍和处理方法

偏度&#xff08;skewness&#xff09;是用来衡量概率分布或数据集中不对称程度的统计量。它描述了数据分布的尾部&#xff08;tail&#xff09;在平均值的哪一侧更重或更长。偏度可以帮助我们了解数据的偏斜性质&#xff0c;即数据相对于平均值的分布情况。 有时&#xff0c;正…

【期权系列】基于偏度指数的择时分析

【期权衍生指标系列】基于偏度指数的择时分析 本篇文章是基于研究报告的复现作品&#xff0c;旨在记录个人的学习过程和复现过程中的一些思路。 感谢中信期货研究员前辈的宝贵思路。 一、偏度指数 1.偏度指数简介 偏度是描述数据分布形态的统计量&#xff0c;其描述的是统…

对于偏度的理解

偏度公式如下&#xff1a; 现在想解决如何从图像上解决为正为负的问题&#xff0c;如图所示&#xff1a;&#xff1f; 个人理解&#xff1a;偏度中的偏是针对变量相对于中心点&#xff08;期望值&#xff09;距离的一种描述&#xff1b;如果厚尾的话&#xff0c;就说明有很多点…