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 ...
随机推荐
- openstack (共享组件) 时间同步服务
云计算openstack共享组件——时间同步服务ntp(2) 一.标准时间讲解 地球分为东西十二个区域,共计 24 个时区 格林威治作为全球标准时间即 (GMT 时间 ),东时区以格林威治时区进行 ...
- Spring boot +Thymeleaf 搭建springweb
对接天猫精灵的时候需要有网关服务器方提供几个页面,服务器已经有了,spring boot的 纯后台的,就加了Thymeleaf jar包添加几个页面跳转 maven配置 <!-- 引入thy ...
- 容器云平台No.9~kubernetes日志收集系统EFK
EFK介绍 EFK,全称Elasticsearch Fluentd Kibana ,是kubernetes中比较常用的日志收集方案,也是官方比较推荐的方案. 通过EFK,可以把集群的所有日志收集到El ...
- JELLY技术周刊 Vol.23: Vue3 是伟大航路上的 One Piece 么?
蒲公英 · JELLY技术周刊 Vol.23 这两天大家应该都被 Vue 发布 3.0 版本的信息刷屏了,背负着很多人的期待, Vue 终于将这个船新版本推到台前,接受大众的检验,那么这个代号为 On ...
- Docker数据卷和数据卷容器
是什么 数据卷设计的目的,在于数据的永久化,他完全独立于容器的生存周期,因此,Docker不会在容器删除时删除其挂载的数据卷,也不会存在类似的垃圾收集机制对容器引用的数据卷进行处理.类似我们Redis ...
- 用 Java 做个“你画手机猜”的小游戏
本文适合有 Java 基础的人群 作者:DJL-Lanking HelloGitHub 推出的<讲解开源项目>系列.有幸邀请到了亚马逊 + Apache 的工程师:Lanking( htt ...
- 虚拟机系列 | JVM运行时数据区
本文源码:GitHub·点这里 || GitEE·点这里 一.内存与线程 1.内存结构 内存是计算机的重要部件之一,它是外存与CPU进行沟通的桥梁,计算机中所有程序的运行都在内存中进行,内存性能的强弱 ...
- pwnable.kr-mistake-witeup
阅读代码后思路: 实践: 1111111111 其异或值:0000000000 欧克,愉快的结束. 需要经常多看代码,对代码的书写习惯有了解,比如这一块17行一般人书写时都会习惯性加括号的.
- PHPExcel集成对数据导入和导出
<?php /** * Created by PhpStorm. * User: admin * Date: 2017/8/15 * Time: 9:07 */ class User exten ...
- JMETER并发压测-自定义不同请求参数
背景 虽然可以请求相同的接口做测试,但是请求参数每次都是相同的.为了模拟不同的用户,请求不同的参数,所以要自定义参数来做多线程并发压力测试. 点赞再看,关注公众号:[地藏思维]给大家分享互联网场景设计 ...