Token+Redis实现接口幂等性
一.什么是 幂等性
在编程中,幂等性的特点就是其任意多次执行的效果和一次执行的效果所产生的影响是一样的。
二.Token+Redis的实现思路
三.具体实现
1.首先导入Redis的pom依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
2.设置切面
package com.apps.idempotent.aspect; import com.apps.bcodemsg.MsgResponse;
import com.apps.redis.service.RedisService;
import org.apache.tomcat.util.http.fileupload.servlet.ServletRequestContext;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; @Order(2)
@Component
@Aspect
public class IdempotentAspect { @Autowired
private RedisService redisService; @Pointcut("@annotation(com.apps.annotation.Token)")
public void tokenIdempotent(){} @Around(value = "tokenIdempotent()")
public MsgResponse before(ProceedingJoinPoint jp) {
System.out.println("================================IdempotentAspect=============================="); MsgResponse response = new MsgResponse(); ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest(); String token = request.getHeader("token");
System.out.println("=====================接收到的参数token:"+token);
if(StringUtils.isEmpty(token)){
response.fail("key.err");
response.setMsg("请勿重复点击!");
return response;
}
boolean contains = redisService.contains(token);
if(!contains){
response.fail("key.err");
response.setMsg("请勿重复点击!");
return response;
}
Object stringToken = redisService.get(token);
if(stringToken!=null){
boolean flag = redisService.del(token);
if(!flag){
response.fail("key.err");
response.setMsg("请勿重复点击!");
return response;
}
System.out.println("==========拦截成功,成功删除redis中的token,避免重复提交。");
} try {
response = (MsgResponse) jp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
return response;
}
}
注意:
1.因为之前有一个日志切面,如果不使用@Order注解标明切面执行顺序就会报错,当然如果没有其他切面就不需要添加这个@Order注解
2.其中MsgResponse类是一个回复类
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
// package com.apps.bcodemsg; import com.apps.asysfinal.SysFinal;
import com.apps.msgconfig.MyProperties;
import com.github.pagehelper.Page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List; @ApiModel(
value = "数据模型",
description = "数据模型"
)
public class MsgResponse extends SysFinal {
@ApiModelProperty(
example = "状态码,成功200,失败400"
)
private int code;
@ApiModelProperty(
example = "错误和成功信息"
)
private String msg;
@ApiModelProperty(
example = "交互提示"
)
private String msgText;
@ApiModelProperty(
example = "每页行数"
)
private Integer pageNum;
@ApiModelProperty(
example = "当前页数"
)
private Integer pageSize;
@ApiModelProperty(
example = "总行数"
)
private Long pageTotal;
@ApiModelProperty(
example = "总页数"
)
private Integer pages;
@ApiModelProperty(
example = "开始行"
)
private Integer startRow;
@ApiModelProperty(
example = "结束行"
)
private Integer endRow;
@ApiModelProperty(
example = "返回数据"
)
private Object data; public MsgResponse() {
} public void success(Object obj, String key) {
this.setCode(200);
this.setMsg("业务处理成功!");
this.setMsgText(MyProperties.getPropertiesText(key));
this.setData(obj);
} public void success(String key) {
this.setCode(200);
this.setMsg("业务处理成功!");
this.setMsgText(MyProperties.getPropertiesText(key));
} public void fail(Object obj, String key) {
this.setCode(400);
this.setMsg("业务处理失败!");
this.setMsgText(MyProperties.getPropertiesText(key));
this.setData(obj);
} public void fail(String key) {
this.setCode(400);
this.setMsg("业务处理失败!");
this.setMsgText(MyProperties.getPropertiesText(key));
} public MsgResponse add(Object value) {
this.setData(value);
return this;
} public int getCode() {
return this.code;
} public void setCode(int code) {
this.code = code;
} public String getMsg() {
return this.msg;
} public void setMsg(String msg) {
this.msg = msg;
} public Object getData() {
return this.data;
} public void setData(Object data) {
if (data instanceof Page) {
Page page = (Page)data;
this.setPageNum(page.getPageNum());
this.setPageSize(page.getPageSize());
this.setPageTotal(page.getTotal());
this.setPages(page.getPages());
this.setStartRow(page.getStartRow());
this.setEndRow(page.getEndRow());
} this.data = data;
} public Object returnJson() {
return this;
} public String getMsgText() {
return this.msgText;
} public void setMsgText(String msgText) {
this.msgText = msgText;
} public Integer getPageNum() {
return this.pageNum;
} public void setPageNum(Integer pageNum) {
this.pageNum = pageNum;
} public Integer getPageSize() {
return this.pageSize;
} public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
} public Long getPageTotal() {
return this.pageTotal;
} public void setPageTotal(Long pageTotal) {
this.pageTotal = pageTotal;
} public Integer getPages() {
return this.pages;
} public void setPages(Integer pages) {
this.pages = pages;
} public Integer getStartRow() {
return this.startRow;
} public void setStartRow(Integer startRow) {
this.startRow = startRow;
} public Integer getEndRow() {
return this.endRow;
} public void setEndRow(Integer endRow) {
this.endRow = endRow;
} public String toString() {
if (!(this.data instanceof List)) {
return "MsgResponse{code=" + this.code + ", msg='" + this.msg + '\'' + ", msgText='" + this.msgText + '\'' + ", pageNum=" + this.pageNum + ", pageSize=" + this.pageSize + ", pageTotal=" + this.pageTotal + ", pages=" + this.pages + ", startRow=" + this.startRow + ", endRow=" + this.endRow + ", data=" + this.data + '}';
} else {
List list = (List)this.data;
StringBuffer stringBuffer = new StringBuffer(); for(int i = 0; i < list.size(); ++i) {
stringBuffer.append(list.get(i));
} return "MsgResponse{code=" + this.code + ", msg='" + this.msg + '\'' + ", msgText='" + this.msgText + '\'' + ", pageNum=" + this.pageNum + ", pageSize=" + this.pageSize + ", pageTotal=" + this.pageTotal + ", pages=" + this.pages + ", startRow=" + this.startRow + ", endRow=" + this.endRow + ", data=" + stringBuffer + '}';
}
}
}
3.添加相关的业务代码和控制器
package com.apps.controller.Idempotent; import com.apps.annotation.Token;
import com.apps.bcodemsg.MsgResponse;
import com.apps.redis.service.RedisService;
import com.apps.service.Idempotent.IdempotentService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import java.math.BigDecimal; @Controller
@RequestMapping("/idempotent")
@Api(value = "IdempotentController",tags = {"幂等测试controller"})
public class IdempotentController { @Autowired
private IdempotentService idempotentService;
@Autowired
private RedisService redisService; @RequestMapping(value = "/create/token",method = RequestMethod.POST)
@ResponseBody
@ApiOperation("创建token")
public MsgResponse createToken(){
MsgResponse response = idempotentService.createToken();
return response;
} @RequestMapping(value = "/balance",method = RequestMethod.POST)
@ResponseBody
@ApiOperation("进行业务操作")
@Token
public MsgResponse subTract( BigDecimal count){
MsgResponse response = idempotentService.subtract(count);
return response;
} @RequestMapping(value = "/get/token",method = RequestMethod.POST)
@ApiOperation("判断token是否还有效")
public void getToken(String key){
boolean contains = redisService.contains(key);
System.out.println("==========redis是否存在:"+contains);
} @RequestMapping(value = "/del/token",method = RequestMethod.POST)
@ApiOperation("删除token")
public void delToken(String key){
boolean contains = redisService.contains(key);
System.out.println("==========redis是否存在:"+contains);
boolean del = redisService.del(key);
if(del){
System.out.println("===========key删除成功!");
}else{
System.out.println("==============key删除失败");
}
} }
package com.apps.service.Idempotent.impl; import com.apps.bcodemsg.MsgResponse;
import com.apps.redis.service.RedisService;
import com.apps.service.Idempotent.IdempotentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import java.math.BigDecimal;
import java.util.UUID; @Service
public class IdempotentImpl implements IdempotentService { private static BigDecimal account = new BigDecimal(10000); @Autowired
private RedisService redisService; @Override
public MsgResponse createToken() { MsgResponse msgResponse = new MsgResponse(); try{
String token = UUID.randomUUID().toString();
System.out.println("============token: "+token);
boolean isSuccess = redisService.set(token, token, 10*60*1000);
if(isSuccess){
System.out.println("============token成功添加到Redis中。");
msgResponse.success("key.msg");
msgResponse.setData(token);
msgResponse.setMsg("创建token成功!");
}else{
System.out.println("=============token添加到Redis中失败!");
msgResponse.success("key.err");
msgResponse.setMsg("创建token失败!");
}
}catch(Exception ex){
System.out.println("发生异常的接口:createToken()");
ex.printStackTrace();
}finally {
return msgResponse;
}
} @Override
public MsgResponse subtract(BigDecimal count) { MsgResponse response = new MsgResponse(); try{
System.out.println("===============当前数量:"+account);
BigDecimal result = account.subtract(count);
account = result;
if(account.setScale(2).compareTo(BigDecimal.ZERO)<=0){
response.fail("key.err");
response.setMsg("余额已经小于0,无法继续操作!");
return response;
}
System.out.println("=================扣除成功,你很棒棒哒啊!");
}catch(Exception ex){
System.out.println("异常接口为:subtract(Integer count)");
ex.printStackTrace();
}finally {
return response;
}
}
}
四.使用Jmeter进行测试
会看到只有一个请求能够成功处理业务,其余请求无法修改数据。
Token+Redis实现接口幂等性的更多相关文章
- 基于Redis&MySQL接口幂等性设计
基于Redis&MySQL接口幂等性设计 欲把相思说似谁,浅情人不知. 1.幂等 幂等性即多次调用接口或方法不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致. 2.幂等使用场景 前 ...
- springboot + redis + 注解 + 拦截器 实现接口幂等性校验
一.概念 幂等性, 通俗的说就是一个接口, 多次发起同一个请求, 必须保证操作只能执行一次 比如: 订单接口, 不能多次创建订单 支付接口, 重复支付同一笔订单只能扣一次钱 支付宝回调接口, 可能会多 ...
- Springboot + redis + 注解 + 拦截器来实现接口幂等性校验
Springboot + redis + 注解 + 拦截器来实现接口幂等性校验 1. SpringBoot 整合篇 2. 手写一套迷你版HTTP服务器 3. 记住:永远不要在MySQL中使用UTF ...
- asp.net core mvc基于Redis实现分布式锁,C# WebApi接口防止高并发重复请求,分布式锁的接口幂等性实现
使用背景:在使用app或者pc网页时,可能由于网络原因,api接口可能被前端调用一个接口重复2次的情况,但是请求内容是一样的.这样在同一个短暂的时间内,就会有两个相同请求,而程序只希望处理第一个请求, ...
- API接口幂等性框架设计
表单重复提价问题 rpc远程调用时候 发生网络延迟 可能有重试机制 MQ消费者幂等(保证唯一)一样 解决方案: token 令牌 保证唯一的并且是临时的 过一段时间失效 分布式: redis+to ...
- 防盗链&CSRF&API接口幂等性设计
防盗链技术 CSRF(模拟请求) 分析防止伪造Token请求攻击 互联网API接口幂等性设计 忘记密码漏洞分析 1.Http请求防盗链 什么是防盗链 比如A网站有一张图片,被B网站直接通过img标签属 ...
- 分布式之分布式事务、分布式锁、接口幂等性、分布式session
一.分布式session session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存在,然后每次发请求过来都带上一个特殊的 jsessionid cookie,就根据这个东西 ...
- Java生鲜电商平台-生鲜电商高并发下的接口幂等性实现与代码讲解
Java生鲜电商平台-生鲜电商高并发下的接口幂等性实现与代码讲解 说明:Java生鲜电商平台-生鲜电商高并发下的接口幂等性实现与代码讲解,实际系统中有很多操作,是不管做多少次,都应该产生一样的效果或返 ...
- 一天一道Java面试题----第十二天(如何实现接口幂等性)
这里是参考B站上的大佬做的面试题笔记.大家也可以去看视频讲解!!! 文章目录 1.如何实现接口幂等性 1.如何实现接口幂等性 唯一id.每次操作,都根据操作和内容生成唯一的id,在执行之前先判断id是 ...
随机推荐
- 编写Java程序,演练静态内部类应用
返回本章节 返回作业目录 需求说明: 创建一个Person类,在该类中定义一个Home静态内部类,并在这个Home类中定义一个显示Home相关信息的方法. 在Person类中设置一个Home类型属性对 ...
- ActiveMQ基础教程(三):C#连接使用ActiveMQ消息队列
接上一篇:ActiveMQ基础教程(二):安装与配置(单机与集群) 安装部署好集群环境:192.168.209.133:61616,192.168.209.134:61616,192.168.209. ...
- [GDOI2021 Day2T1] 宝石
题目大意 \(n\)个点的树, 树上每一个点有一个宝石\(w_i\), 给出一个固定的数字不重复的序列\(p_i\)和一些询问\(u_i, v_i\), 对于每一个询问求出\(u_i\)到\(v_i\ ...
- Pytest_Hook钩子函数总结(14)
前言 pytest 的钩子函数有很多,通过钩子函数的学习可以了解到pytest在执行用例的每个阶段做什么事情,也方便后续对pytest二次开发学习.详细文档可以查看pytest官方文档https:// ...
- Linux上天之路(十四)之Linux数据处理
主要内容 数据检索 数据排序 数据去重 重定向 1. 数据检索 常和管道协作的命令 – grep grep:用于搜索模式参数指定的内容,并将匹配的行输出到屏幕或者重定向文件中,常和管道协作的命令 – ...
- 正则验证&模态框
在日常生活中,凡是需要表单验证的都会用到正则验证.下面拿一个简单的带有模态框的正则验证的小demo看一下 <style> /* 遮罩层 */ .ma ...
- PPT2010制作清明上河图动画
原文: https://www.toutiao.com/i6492312556915393038/ 新建一个空白幻灯片 选择"插入"选项卡,"图像"功能组,&q ...
- HDU 1754 I Hate It(线段数-单点更新)
原题链接:I Hate It 题目分析:这个是线段树的基本操作--更新和查询,属于基础题,也就相当于一个模板吧,这里就不再多说了. 代码如下:(PS:特别注意--输入输出用 不然会超时) #incl ...
- 马哈鱼血缘分析工具部署介绍--win 10
马哈鱼血缘分析工具部署介绍--win 10 随着大数据技术的发展与普及,数据治理和数据质量变得越来越重要,数据血缘分析在业界悄然兴起并得到了广泛流行,马哈鱼是国内少有的一款专业且易用的血缘分析工具.本 ...
- dubbo泛化引发的生产故障之dubbo隐藏的坑
dubbo泛化引发的生产故障之dubbo隐藏的坑 上个月公司zk集群发生了一次故障,然后要求所有项目组自检有无使用Dubbo编程式/泛化调用,强制使用@Reference生成Consumer.具体原因 ...