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 Web程序设计笔记 • 【目录】
章节 内容 实践练习 Java Web程序设计作业目录(作业笔记) 第1章 Java Web程序设计笔记 • [第1章 Web应用程序] 第2章 Java Web程序设计笔记 • [第2章 JSP基础 ...
- MySQL入门安装,及环境配置,初始化教程
一.MySQL安装(win64) 免费的社区版下载地址:https://dev.mysql.com/downloads/mysql 接着会跳到这个页面 下载完后,我们将 zip 包解压到相应的目录,这 ...
- windows下的pycharm下载与安装(不包括激活)
Pycharm的下载与安装 首先兄弟们我们依旧在浏览器输入pycharm的网址链接 PyCharm: the Python IDE for Professional Developers by Jet ...
- Selenium_界面的刷新、后退、前进操作(4)
import time from selenium import webdriver driver = webdriver.Chrome() driver.maximize_window() driv ...
- Java的jar包构建成docker镜像并运行
结构如下 把jar和Dockerfile放到一个文件,不在一个文件下会报错文件找不到 创建一个构建文件 buildimage.sh vi /home/hanby/buildimage.sh echo ...
- Arrays.sort实现原理
Collections.sort方法底层就是调用的array.sort方法 比较器的方式 TimSort static void sort(Object[] a, int lo, int hi, Ob ...
- vue爬坑之路(插件安装)
npm install vue-table-with-tree-grid --save import ZkTable from 'vue-table-with-tree-grid' Vue.use(Z ...
- vue实现PC端分辨率适配
lib-flexible + px2rem Loader lib-flexible 阿里伸缩布局方案 px2rem-loader:px转rem: 依赖 首先需要安装 vue-cli 脚手架,这里我安装 ...
- 51 Nod 1134 最长递增子序列 (动态规划基础)
原题链接:1134 最长递增子序列 题目分析:长度为 的数列 有多达 个子序列,但我们应用动态规划法仍可以很高效地求出最长递增子序列().这里介绍两种方法. 先考虑用下列变量设计动态规划的算法. ...
- TC (Teamcenter) 许可证解决方案
是否因为经常遇到下列许可问题而苦恼? 没有可用于Teamcenter 并发模块"catia_integration"的Flexlm许可证: Teamcenter Visualiza ...