一.什么是 幂等性

在编程中,幂等性的特点就是其任意多次执行的效果和一次执行的效果所产生的影响是一样的。

二.Token+Redis的实现思路

1.数据提交前要向服务的申请 token(用户登录时可以获取),token 放到 redis 或 jvm 内存,token 有效时间;
2. 提交后后台校验 token,同时删除 token,生成新的 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实现接口幂等性的更多相关文章

  1. 基于Redis&MySQL接口幂等性设计

    基于Redis&MySQL接口幂等性设计 欲把相思说似谁,浅情人不知. 1.幂等 幂等性即多次调用接口或方法不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致. 2.幂等使用场景 前 ...

  2. springboot + redis + 注解 + 拦截器 实现接口幂等性校验

    一.概念 幂等性, 通俗的说就是一个接口, 多次发起同一个请求, 必须保证操作只能执行一次 比如: 订单接口, 不能多次创建订单 支付接口, 重复支付同一笔订单只能扣一次钱 支付宝回调接口, 可能会多 ...

  3. Springboot + redis + 注解 + 拦截器来实现接口幂等性校验

    Springboot + redis + 注解 + 拦截器来实现接口幂等性校验   1. SpringBoot 整合篇 2. 手写一套迷你版HTTP服务器 3. 记住:永远不要在MySQL中使用UTF ...

  4. asp.net core mvc基于Redis实现分布式锁,C# WebApi接口防止高并发重复请求,分布式锁的接口幂等性实现

    使用背景:在使用app或者pc网页时,可能由于网络原因,api接口可能被前端调用一个接口重复2次的情况,但是请求内容是一样的.这样在同一个短暂的时间内,就会有两个相同请求,而程序只希望处理第一个请求, ...

  5. API接口幂等性框架设计

    表单重复提价问题 rpc远程调用时候 发生网络延迟  可能有重试机制 MQ消费者幂等(保证唯一)一样 解决方案: token 令牌 保证唯一的并且是临时的  过一段时间失效 分布式: redis+to ...

  6. 防盗链&CSRF&API接口幂等性设计

    防盗链技术 CSRF(模拟请求) 分析防止伪造Token请求攻击 互联网API接口幂等性设计 忘记密码漏洞分析 1.Http请求防盗链 什么是防盗链 比如A网站有一张图片,被B网站直接通过img标签属 ...

  7. 分布式之分布式事务、分布式锁、接口幂等性、分布式session

    一.分布式session session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存在,然后每次发请求过来都带上一个特殊的 jsessionid cookie,就根据这个东西 ...

  8. Java生鲜电商平台-生鲜电商高并发下的接口幂等性实现与代码讲解

    Java生鲜电商平台-生鲜电商高并发下的接口幂等性实现与代码讲解 说明:Java生鲜电商平台-生鲜电商高并发下的接口幂等性实现与代码讲解,实际系统中有很多操作,是不管做多少次,都应该产生一样的效果或返 ...

  9. 一天一道Java面试题----第十二天(如何实现接口幂等性)

    这里是参考B站上的大佬做的面试题笔记.大家也可以去看视频讲解!!! 文章目录 1.如何实现接口幂等性 1.如何实现接口幂等性 唯一id.每次操作,都根据操作和内容生成唯一的id,在执行之前先判断id是 ...

随机推荐

  1. Drools创建Maven工程

    1.说明 本文介绍创建Drools的Maven工程的方法, 仅使用Eclipse开发工具, 不使用Drools的相关插件, 先创建一个Maven工程, 然后引入Drools的相关依赖即可, 最后再写一 ...

  2. 『无为则无心』Python函数 — 31、命名空间(namespace)

    目录 1.什么是命名空间 2.三种命名空间 3.命名空间查找顺序 4.命名空间的生命周期 5.如何获取当前的命名空间 1.什么是命名空间 命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名 ...

  3. shc命令

    今天在公司看到业务系统有一个query.viewtx 等等命令.虽然不知道是什么语言写的,但是里边内容是看不到的. 如果是编译型语言这样的结果 我并不奇怪.但是如果我们写了一个shell脚本 如果加密 ...

  4. 官网下载mysql的方法

    mysql官网    http://www.mysql.com/ 方法一:    (1)登陆官网 (2)把页面拉到最底部,点击Downloads(GA) 下边的MySQL Community Serv ...

  5. 自带排序 Array.sort()

    public static void main(String[] args) { int[] aa = {1, 5, 9, 7, 3, 1, 6, 3, 47}; Arrays.sort(aa); p ...

  6. Word合并多文档

    图片如果损坏,点击链接: https://www.toutiao.com/i6489785099528176142/ 很多时候,我们需要将两个或者多个文档的内容,放到一起,而最直接的办法就是将多个文档 ...

  7. IE8和IE9下textarea滚动选中的问题

    在IE8和IE9下如果textarea设置了样式overflow-y:auto;就不可以滚动选中了,应该样式写成overflow:auto;有了纵向滚动实际上就不会出现横向滚动的情况,也没有必要ove ...

  8. Easticsearch概述(ES、Lucene、Solr)一

    ES是在Lucene的基础上实现的 1.Lucene全文检索 lucene是一个全文搜索框架,而不是应用产品.因此它并不像http://www.baidu.com/或goolge Destop 那么拿 ...

  9. 机器学习&恶意代码静态检测

    目录 分析工具 方法概述 二进制灰度图 字节(熵)直方图 字符串信息 ELF结构信息 源码分析与OPcode FCG references: 分析工具 readelf elfparser ninja ...

  10. 《剑指offer》面试题28. 对称的二叉树

    问题描述 请实现一个函数,用来判断一棵二叉树是不是对称的.如果一棵二叉树和它的镜像一样,那么它是对称的. 例如,二叉树 [1,2,2,3,4,4,3] 是对称的.     1    / \   2   ...