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是 ...
随机推荐
- 浅谈JVM内存分配与垃圾回收
大家好,我是微尘,最近又去翻了周志明老师的<深入理解Java虚拟机>这本书.已经看了很多遍了,每次都感觉似乎看懂了,但没过多久就忘了.这次翻了第三章的垃圾收集器与内存分配策略,感觉有了新的 ...
- 《手把手教你》系列技巧篇(五十四)-java+ selenium自动化测试-上传文件-中篇(详细教程)
1.简介 在实际工作中,我们进行web自动化的时候,文件上传是很常见的操作,例如上传用户头像,上传身份证信息等.所以宏哥打算按上传文件的分类对其进行一下讲解和分享. 2.为什么selenium没有提供 ...
- unittest_测试报告(6)
用例执行完成后,执行结果默认是输出在屏幕上,其实我们可以把结果输出到一个文件中,形成测试报告. unittest自带的测试报告是文本形式的,如下代码: import unittest if __nam ...
- ConfigParser_读取配置文件信息
ConfigParse简介 ConfigParser 在python中是用来解析配置文件的内置模块,直接导入使用 import configparser 使用该模块可以对配置文件进行增.读.改.删操作 ...
- [网络编程] 自己构建一个cgi.FieldStorage()的对象
问题描述: 通常cgi.FieldStorage()返回一个类似于Python字典的对象. 在cgi框架中必须通过浏览器发送表单过来才能接受消息 那么我该怎么进行本地调试呢? 或者说在没有搭建好一整套 ...
- zabbix监控图形中文乱码的解决方法
问题描述: 最近搭建了一套zabbix,当我把语言切换到中文的时候,发现监控的图形界面中一些中文参数乱码,但是图形界面在英文环境下完全没有乱码问题.如下图(中文界面): 解决方法: 解决方法有两种,方 ...
- Java CAS 原理详解
1. 背景 在JDK 5之前Java语言是靠 synchronized 关键字保证同步的,这会导致有锁.锁机制存在以下问题: 在多线程竞争下,加锁.释放锁会导致比较多的上下文切换和调度延时,引起性能问 ...
- JavaScript的执行过程(深入执行上下文、GO、AO、VO和VE等概念)
JavaScript的执行过程 前言 编写一段JavaScript代码,它是如何执行的呢?简单来说,JS引擎在执行JavaScript代码的过程中需要先解析再执行.那么在解析阶段JS引擎又会进行哪些操 ...
- 使用.NET 6开发TodoList应用(27)——实现API的Swagger文档化
系列导航及源代码 使用.NET 6开发TodoList应用文章索引 需求 在日常开发中,我们需要给前端提供文档化的API接口定义,甚至需要模拟架设一个fake服务用来调试接口字段.或者对于后端开发人员 ...
- UDP代码编写、操作系统发展史、多道技术、进程理论与代码层面创建、进程join方法与进程对象方法
昨日内容回顾 socket基本使用 # 内置的模块 import socket s = socket.socket() # 默认是TCP协议 也可以切换为UDP协议 s.bind((ip,port)) ...