Are you sure you want to delete this task? Once this task is deleted, it cannot be recovered.
janney 71e2901413 | 11 months ago | |
---|---|---|
.github/ISSUE_TEMPLATE | 11 months ago | |
src | 11 months ago | |
CODE_OF_CONDUCT.md | 11 months ago | |
CONTRIBUTING.md | 11 months ago | |
LICENSE | 11 months ago | |
README.md | 11 months ago | |
pom.xml | 11 months ago |
是一款专门为SpringBoot
项目设计的幂等组件
本文以下的讨论,都是假设我们数据库没有做唯一约束和乐观锁的场景下的分析。关于防重和幂等判断的讨论,欢迎留言讨论
下面这段逻辑,在正常情况下是没有问题的,①也算进行了幂等校验,先判断状态在进行处理。但是当用户重复提交导致并发问题,两次请求都执行到了④步骤,而因为④并没有用乐观锁处理,就会导致幂等性问题。两次提交都对数据状态进行的修改。
所以我们得出结论凡是通过根据查询状态来做防重或者幂等校验的,理论情况下都会因为并发问题而被击穿。 所以如果数据库表设计允许的情况下,建议设置唯一约束,用来做幂等或者防重。(sql尽量加上乐观锁操作)
利用数据库唯一索引做防重处理,当第一次插入是没有问题的,第二次在进行插入会因为唯一索引报错。从而达到拦截的目的。
如何防止重复提交, 为每次请求生成请求唯一键,服务端对每个唯一键进行生命周期管控。规定时间内只允许一次请求,非第一次请求都属于重复提交。但是前后端改造大,后端要给出单独生成token令牌接口,前端要在每次调用时候先获取token令牌。
基于Web方法,从参数中寻找可以作为唯一键,进行控制。改造难度低,仅需要服务端改造,前端无感知。
本文是基于方案 3提供的解决方案,具体实现是利用 Redis进行实现。
原理非常简单,我们为每次请求声明一个防重的时间范围,范围内的重复请求都会当做重复提交被拦截。核心原理就是这么简单。
基于控制时间两种防重策略
每次请求设置当前请求的控制时间,控制时间内请求均会被拦截。
仅仅为第一次请求生成一个控制时间,控制时间内相同的请求会被拦截,控制时间过期后,以此类推。
class Example{
@Repeat
@GetMapping("/tt")
public String getUser(@TomatoToken String name) {
System.out.println(System.currentTimeMillis() + ":" + name);
String s = System.currentTimeMillis() + ":" + name;
return s;
}
@Repeat
@PostMapping(value = "/post", consumes = MediaType.APPLICATION_JSON_VALUE)
public String postUserName(@TomatoToken("userName") @RequestBody UserRequest userRequest) {
System.out.println(System.currentTimeMillis() + ":" + userRequest.getUserName());
String s = System.currentTimeMillis() + ":" + userRequest.getUserName();
return s;
}
@Repeat(throwable = NullPointerException.class, message = "禁止重复提交")
@PostMapping(value = "/form")
public String postUserName(@TomatoToken("userName") HttpServletRequest userRequest) {
System.out.println(System.currentTimeMillis() + ":" + userRequest.getParameter("userName"));
String s = System.currentTimeMillis() + ":" + userRequest.getParameter("userName");
return s;
}
@PostMapping("/el/phoneNo")
@Repeat
public String elName(@TomatoToken("phone.phoneNo") @RequestBody User user) {
System.out.println(user);
// 当前TOKEN:17601234466
return "当前TOKEN:" + StaticContext.getToken();
}
}
User模型结构
{
"name":"Tomato",
"age": 27,
"phone":{
"phoneNo": "17601234466"
}
}
class Example{
@Autowired
private Idempotent idempotent;
public VoidResponse addWhite(String name) {
idempotent.idempotent(name,1000,()->new RuntimeException("重复提交"))
return VoidResponse.SUCCESS();
}
}
@Test
public void testExample() {
// 表达式解析器
User liuxin = new User("liuxin", 23,new Phone("123213321"));
// 执行toString方法
System.out.println(ExpressionUtils.getElValue("toString()", liuxin));
System.out.println(ExpressionUtils.getThisElValue("${name}", liuxin));
// 支持从根元素获取数据
System.out.println(ExpressionUtils.getThisElValue("S_AO:${name}", liuxin));
System.out.println(ExpressionUtils.getThisElValue("${name + '_后缀'}", liuxin));
// 支持从变量元素获取数据,根元素 = c #是变量,$是模板占位符
System.out.println(ExpressionUtils.getThisElValue("${#c.name}, ${#c.age}", liuxin));
// 获取toString方法
System.out.println(ExpressionUtils.getThisElValue("${#c.toString()}", liuxin));
// 获取值并处理
System.out.println(ExpressionUtils.getThisElValue("${#c.age +'-'+ #c.age}", liuxin));
// 获取值,并通过方法计算
System.out.println(ExpressionUtils.getThisElValue("${T(Integer).parseInt(#c.age + 1)}", liuxin));
// 计算哈希值
System.out.println(ExpressionUtils.getThisElValue("${T(com.github.tomato.support.DefaultTokenProviderSupportTest).hash(#c.age + 1)}", liuxin));
System.out.println(ExpressionUtils.getThisElValue("${T(com.github.tomato.support.DefaultTokenProviderSupportTest).json(#c)}", liuxin));
}
Maven
<dependency>
<groupId>com.github.lxchinesszz</groupId>
<artifactId>tomato-spring-boot-starter</artifactId>
<version>1.0.1.RELEASE</version>
</dependency>
Gradle
implementation 'com.github.lxchinesszz:tomato-spring-boot-starter:1.0.1.RELEASE'
1.0.1-RELEASE
bug
1.0.2-RELEASE
Repeat
新增拦截策略StaticContext
上下文,支持用户获取令牌值及令牌初始化参数1.0.3-RELEASE
在此感谢@ruansheng与@lianbitangjin关于以下问题的发现,以及给出的建议。
1.0.4-RELEASE
1.0.5-RELEASE
在此感谢@mostcool提出的宝贵建议
1.0.6-RELEASE
1.0.7-RELEASE
再此感谢foot80@163.com提供信息
1.0.8-RELEASE
1.0.9-RELEASE
1.0.10-RELEASE
Dear OpenI User
Thank you for your continuous support to the Openl Qizhi Community AI Collaboration Platform. In order to protect your usage rights and ensure network security, we updated the Openl Qizhi Community AI Collaboration Platform Usage Agreement in January 2024. The updated agreement specifies that users are prohibited from using intranet penetration tools. After you click "Agree and continue", you can continue to use our services. Thank you for your cooperation and understanding.
For more agreement content, please refer to the《Openl Qizhi Community AI Collaboration Platform Usage Agreement》