后端接口防重提交的解决方案

场景

在常规的web系统中,涉及到写操作的接口请求调用时,前端往往有做防止重复提交的验证,但是从系统的完整性上说,仅仅只在前端做校验实际上是有缺陷的,恶意调用接口者仍可通过其他工具重复调用接口,如果只是涉及读数据的接口可能还没什么问题,一旦重复调用涉及写的接口,则可能会造成无法预估的数据问题。

解决方案

如果只是单机服务,可以直接使用单机线程锁,在接口调用完成之前锁住接口,获取锁失败则快速返回,防止用户重复提交。考虑到系统的扩展性,使用基于redis的分布式锁,使用的spring的切面做前置增强获取锁,后置增强释放锁。

流程图

avatar

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.smart.security.aop;

import com.smart.jedis.JedisTemplate;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
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.context.annotation.Configuration;


/**
* @author jungle
*/
@Aspect
@Configuration
public class WebAspect {

/**
* redis操作类
*/
@Autowired
private JedisTemplate jedisTemplate;

/**
* 基于注解的切面,可自定义到方法或类上,只读接口无需加锁浪费性能
*/
@Pointcut("@annotation(com.smart.annotation.AntiRepeat)")
public void antiRepeat() {
}


/**
* 获取锁
* 1.获取当前用户的唯一识别编码和当前URL
* 2.使用编码+url作为key去redis中获取锁
* 3.获取锁失败直接返回通用消息,为防止网络原因阻塞,需要加超时快速返回
* 4.拿锁成功则进入业务代码
* @param joinPoint
*/
@Before("antiRepeat()")
public void lock(JoinPoint joinPoint) {

}

/**
* 释放锁
* 1.业务代码执行完毕,请求redis释放锁,同样需要设置超时时间
* 2.为防止释放锁时因网络问题导致的死锁,获取锁时需要设置自动过期时间,值从配置文件中读取
*/
@After("antiRepeat()")
public void unlock(){

}
}

总结

使用基于redis的分布式锁,可以解决后端接口重复调用导致的数据问题,锁的过期时间可以自由配置,同时能在集群的条件下使用,是一个在中等并发下比较完善的方案。