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是 ...
随机推荐
- MongoDB_安装、配置、连接(五)
MongoDB 是跨平台的,既可以在 Linux系统下安装,也可以在Windows 系统.MacOS系统下安装,本节主要介绍如何在 Linux 系统下安装 MongoDB. windows安装:htt ...
- quasar框架在store中使用router跳转页面报错
网上一通百度,终于在这篇博客中找到原因. https://www.cnblogs.com/remly/p/12995936.html 原因是: 在router中导出了一个工厂函数, 既然是一个函数, ...
- SpringMVC 解析(一)概览
Spring MVC是Spring提供的构建Web应用程序的框架,该框架遵循了Servlet规范,负责接收并处理Servelt容器传递的请求,并将响应写回Response.Spring MVC以Dis ...
- windows下过安全狗
最近想着把过waf相关的整理一下,本次主要以安全狗4.0为例进行演示 准备工作 安全狗官网:http://free.safedog.cn/install_desc_website.html环境:Win ...
- JNDI和JDBC的区别
最近也是遇见了JNDI这个概念,查了一下,网上的说法太官方,所以参考下一位老哥的博客总结下 JDBC 看到最多的就是,Java Database Connectivity (JDBC)是一个标准的Ja ...
- Easticsearch概述(API使用)二
Rest简介 一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件.它主要用于客户端和服务端互类的软件.基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制 Rest的操作分为以 ...
- 《剑指offer》面试题19. 正则表达式匹配
问题描述 请实现一个函数用来匹配包含'. '和'*'的正则表达式.模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次).在本题中,匹配是指字符串的所有字符匹配整个模式. ...
- 【刷题-LeetCode】210. Course Schedule II
Course Schedule II There are a total of n courses you have to take, labeled from 0 to n-1. Some cour ...
- 【记录一个问题】linux下使用opencv中的UMat,性能并未提升,反而略有下降
使用后性能略微下降,一开始怀疑是UMat拷贝的问题.运行 nvidia-smi -l 1, 发现GPU占用始终为0.说明opencl使用的是CPU版本,而不是GPU版本.明天试验opencl的GPU版 ...
- 安卓无法访问Azure服务器和微软API
Azure服务器保护机制限制移动端访问 必须使用移动app服务来转接api,才可以访问.