spring boot:用redis+lua实现表单接口的幂等性(spring boot 2.2.0)
一,什么是幂等性?
1,幂等:
幂等操作:不管执行多少次,所产生的影响都和一次执行的影响相同。
幂等函数或幂等方法:可以使用相同的参数重复执行,并能获得相同的结果的函数/方法。
这些函数/方法不用担心重复执行会对系统造成改变。
2,幂等操作的一些例子:
前端重复提交相同的数据,后台只产生对应这个数据的一个相同的反应结果
发送验证码短信:应该只发一次,相同的验证短信不能多次发送。
生成订单:一个业务请求只能创建一个订单,不能重复创建相同的订单
用户付款:只能扣用户一次钱,不能重复扣费
3,实现幂等操作的一些方法:
unique索引
悲观锁
乐观锁
token机制
...
说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest
对应的源码可以访问这里获取: https://github.com/liuhongdi/
说明:作者:刘宏缔 邮箱: 371125307@qq.com
二,关于演示代码的说明:
1,项目的原理:
我们这里的演示的是表单提交时要避免重复提交相同的内容,
前端在用户点击提交按钮后,需要在后端返回结果之前,禁止用户再次点击提交按钮,
假如有请求绕过了前端的控制,直接向后端发送重复的相同请求,
后端如何避免?
在用户打开表单时,后端会生成一个token字符串,保存在redis后,传递给表单,
当表单提交时,这个字符串会再次提交到后端接口,
后端接口需要判断这个字符串是否在redis中存在?
如果不存在不允许提交,如果存在删除时能成功删除,允许提交,
删除时报错:表示已被其他进程删除,也不能允许提交.
2,项目在github的地址:
https://github.com/liuhongdi/idempotent
3,代码结构截图:
三,lua代码的说明:
checkidem.lua
local current = redis.call('GET', KEYS[1])
if current == false then
--redis.log(redis.LOG_NOTICE,KEYS[1]..' is nil ')
return '-1'
end
local isdel = redis.call('DEL', KEYS[1])
if isdel == 1 then
--redis.log(redis.LOG_NOTICE,' del '..KEYS[1]..' success')
return '1';
else
--redis.log(redis.LOG_NOTICE,'del '..KEYS[1]..' failed')
return '0';
end
如果当前token在redis中不存在,返回 -1
如果token存在,删除成功,返回1
删除失败,返回0
说明:为什么使用lua脚本?
redis上的lua脚本的执行是原子性的,不存在多个线程的并发问题,
使用lua脚本能保证不会出现重复的提交
四,java代码的说明:
1,RedisLuaUtil
@Service
public class RedisLuaUtil {
@Resource
private StringRedisTemplate stringRedisTemplate; /*
run a lua script
luaFileName: lua file name,no path
keyList: list for redis key
return 0: delete fail
-1: no this key
1: delete success
*/
public String runLuaScript(String luaFileName,List<String> keyList) {
//System.out.println("redis script begin");
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/"+luaFileName)));
redisScript.setResultType(String.class); String argsone = "none";
//System.out.println("execute begin");
String result = stringRedisTemplate.execute(redisScript, keyList,argsone);
System.out.println("lua result:"+result); return result;
}
}
说明:
DefaultRedisScript:负责封装lua脚本
luaFileName: lua文件名
keyList: redis中的key列表,我们只需要传递token即可
stringRedisTemplate:负责执行脚本
argsone:值参数,我们传一个空字串即可
2,TokenServiceImpl.java中对redisLuaUtil类的调用
@Override
public void checkToken(HttpServletRequest request) { String token = request.getHeader(TOKEN_NAME); if (StringUtils.isBlank(token)) {// header中不存在token
token = request.getParameter(TOKEN_NAME);
if (StringUtils.isBlank(token)) {// parameter中也不存在token
//System.out.println("-----no token");
throw new ServiceException(ResponseCode.ILLEGAL_ARGUMENT.getMsg());
}
} //System.out.println("runlua begin");
List<String> keyList = new ArrayList();
keyList.add(token);
String res = redisLuaUtil.runLuaScript("checkidem.lua",keyList); if (res.equals("1")) {
ServerResponseUtil.success("success");
} else {
throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getMsg());
}
}
五,测试幂等性的检测是否生效?
提交表单前,先得到token
访问:
http://127.0.0.1:8080/order/gettoken
返回:
{"status":0,"msg":"8IgWPMtotKyzO13pnxCS9pc4","data":null}
用ab测试表单:
#-c:指定请求的并发数量
#-n:指定请求的总数量
[root@localhost etc]# ab -c 10 -n 10 http://127.0.0.1:8080/order/addorder?form_token=8IgWPMtotKyzO13pnxCS9pc4
查看代码中system.out.println的打印输出:
lua result:1
lua result:-1
lua result:-1
lua result:-1
lua result:-1
lua result:-1
lua result:-1
lua result:-1
lua result:-1
lua result:-1
可以看到只有一个是提交成功,其他的请求均给出了报错
六,查看spring boot的版本:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.0.RELEASE)
spring boot:用redis+lua实现表单接口的幂等性(spring boot 2.2.0)的更多相关文章
- Spring Security默认的用户登录表单 页面源代码
Spring Security默认的用户登录表单 页面源代码 <html><head><title>Login Page</title></hea ...
- spring boot:用redis+lua限制短信验证码的发送频率(spring boot 2.3.2)
一,为什么要限制短信验证码的发送频率? 1,短信验证码每条短信都有成本制约, 肯定不能被刷接口的乱发 而且接口被刷会影响到用户的体验, 影响服务端的正常访问, 所以既使有图形验证码等的保护, 我们仍然 ...
- Spring Boot 2 + Thymeleaf:服务器端表单验证
表单验证分为前端验证和服务器端验证.服务器端验证方面,Java提供了主要用于数据验证的JSR 303规范,而Hibernate Validator实现了JSR 303规范.项目依赖加入spring-b ...
- spring boot学习(7) SpringBoot 之表单验证
第一节:SpringBoot 之表单验证@Valid 是spring-data-jpa的功能: 下面是添加学生的信息例子,要求姓名不能为空,年龄大于18岁. 贴下代码吧: Student实体: ...
- 【Spring学习笔记-MVC-11--】Spring MVC之表单标签
一.使用方法 1.要使用Spring MVC提供的表单标签,首先需要在视图页面添加: <%@ taglib prefix="form" uri="http://ww ...
- spring-security-4 (5)spring security Java配置实现自定义表单认证与授权
前面三篇讲解了spring security的搭建以及简单的表单认证与授权原理.本篇将实现我们自定义的表单登录与认证. 本篇不会再讲项目的搭建过程,因为跟第二节的搭建如出一辙.本篇也不会将项目中所有 ...
- SpringBoot集成Spring Security(4)——自定义表单登录
通过前面三篇文章,你应该大致了解了 Spring Security 的流程.你应该发现了,真正的 login 请求是由 Spring Security 帮我们处理的,那么我们如何实现自定义表单登录呢, ...
- Spring MVC(十四)--SpringMVC验证表单
在Spring MVC中提供了验证器可以进行服务端校验,所有的验证都必须先注册校验器,不过校验器也是Spring MVC自动加载的,在使用Spring MVC校验器之前首先要下载相关的jar包,下面是 ...
- spring mvc Controller与jquery Form表单提交代码demo
1.JSP表单 <% String basePath = request.getScheme() + "://" + request.getServerName() +&qu ...
随机推荐
- python中绝对值的表达式
abs(x) print(abs(23)) #23print(abs(-45)) #45
- oracle之二物化视图
物化视图 18.1.物化视图作用 1) 物化视图起源于数据仓库,早期的考虑是用于预先计算并保存表连接或聚集等耗时较多的操作的结果,这样,在执行查询时,就可以避免在基表上进行这些耗时的操作,从而快速的得 ...
- oracle之同义词
同义词 从字面上理解就是别名的意思,和视图的功能类似.就是一种映射关系. 14.1 私有同义词; 一般是普通用户自己建立的同义词,创建者需要create synonym 权限. sys:SQL> ...
- python 3 for与while嵌套
- 在 Flutter 中使用 TensorFlow Lite 插件实现文字分类
如果您希望能有一种简单.高效且灵活的方式把 TensorFlow 模型集成到 Flutter 应用里,那请您一定不要错过我们今天介绍的这个全新插件 tflite_flutter.这个插件的开发者是 G ...
- 测试软件—禅道BUG管理工具
入禅 目录 入禅 1.禅道的基本使用 1.禅道的基本使用 admin(管理员) 部门:创建部门(需求部门,开发部门,测试部门,项目部门,产品部门) 组织:创建用户(产品经理,项目经理,开发人员,测试人 ...
- 第一次软件工程与UML作业
这个作业属于哪个课程 https://url.cn/IMQa18Jo 这个作业要求在哪里 https://edu.cnblogs.com/campus/fzzcxy/2018SE1/homework/ ...
- outh2
之前做天猫精灵对接,就碰到了outh鉴权,当时实现好之后没有细细缕,今天看了一个博主的介绍,贴一下 转载自http://www.ruanyifeng.com/blog/2014/05/oauth_2_ ...
- python基础入门语法和变量类型(二)
列表 列表是 Python 中使用最频繁的数据类型,它可以完成大多数集合类的数据结构实现,可以包含不同类型的元素,包括数字.字符串,甚至列表(也就是所谓的嵌套). 和字符串一样,可以通过索引值或者切片 ...
- Flutter学习三之搭建一个简单的项目框架
上一篇文章介绍了Dart的语法的基本使用,从这篇文章开始,开发一个基于玩Android网站的app.使用的他们开放的api来获取网站数据. 根据网站的结构,我们app最外层框架需要添加一个底部导航栏, ...