I. Introduction
In some cases, due to connection speed users a mistake (continuous two clicks the submit button), the page Caton and other reasons, there may be duplication of data submission form database holds more than cause duplicate data.
For the above problems can be solved to the front end, click the Save button again it can not judge how long, of course, if there is intelligent front-end users to bypass the authentication backend should go to intercept processing, the following small series will be based on SpringBoot 2.1.8.RELEASE
the environment by AOP切面
+ 自定义校验注解
+ Redis缓存
to solve this problem.
Two, Spring Boot check Form to repeat the operation
1, pom.xml
the introduction of the required dependencies
<!-- ================== 校验表单重复提交所需依赖 ===================== -->
<!-- AOP依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2, application.yml
the configuration is introduced Redis
spring:
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
timeout: 6000
# Redis服务器连接密码(默认为空)
# password:
jedis:
pool:
max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 10 # 连接池中的最大空闲连接
min-idle: 5 # 连接池中的最小空闲连接
3, custom annotation @NoRepeatSubmit
// 作用到方法上
@Target(ElementType.METHOD)
// 运行时有效
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {
/**
* 默认时间3秒
*/
int time() default 3 * 1000;
}
4, AOP intercept processing
NOTE: redis here stored key
values specific service by an individual flexible play, here is an example of
ex: single user can log in the case where a combination of token + url请求路径
a plurality of users can simultaneously log, it can be coupled withip地址
@Slf4j
@Aspect
@Component
public class NoRepeatSubmitAop {
@Autowired
RedisUtil redisUtil;
/**
* <p> 【环绕通知】 用于拦截指定方法,判断用户表单保存操作是否属于重复提交 <p>
*
* 定义切入点表达式: execution(public * (…))
* 表达式解释: execution:主体 public:可省略 *:标识方法的任意返回值 任意包+类+方法(…) 任意参数
*
* com.zhengqing.demo.modules.*.api : 标识AOP所切服务的包名,即需要进行横切的业务类
* .*Controller : 标识类名,*即所有类
* .*(..) : 标识任何方法名,括号表示参数,两个点表示任何参数类型
*
* @param pjp:切入点对象
* @param noRepeatSubmit:自定义的注解对象
* @return: java.lang.Object
*/
@Around("execution(* com.zhengqing.demo.modules.*.api.*Controller.*(..)) && @annotation(noRepeatSubmit)")
public Object doAround(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) {
try {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
// 拿到ip地址、请求路径、token
String ip = IpUtils.getIpAdrress(request);
String url = request.getRequestURL().toString();
String token = request.getHeader(Constants.REQUEST_HEADERS_TOKEN);
// 现在时间
long now = System.currentTimeMillis();
// 自定义key值方式
String key = "REQUEST_FORM_" + ip;
if (redisUtil.hasKey(key)) {
// 上次表单提交时间
long lastTime = Long.parseLong(redisUtil.get(key));
// 如果现在距离上次提交时间小于设置的默认时间 则 判断为重复提交 否则 正常提交 -> 进入业务处理
if ((now - lastTime) > noRepeatSubmit.time()) {
// 非重复提交操作 - 重新记录操作时间
redisUtil.set(key, String.valueOf(now));
// 进入处理业务
ApiResult result = (ApiResult) pjp.proceed();
return result;
} else {
return ApiResult.fail("请勿重复提交!");
}
} else {
// 这里是第一次操作
redisUtil.set(key, String.valueOf(now));
ApiResult result = (ApiResult) pjp.proceed();
return result;
}
} catch (Throwable e) {
log.error("校验表单重复提交时异常: {}", e.getMessage());
return ApiResult.fail("校验表单重复提交时异常!");
}
}
}
5, which uses the Redis Tools
Because too many, here is not directly attached to it, and refer to the end of the case as given demo source code
Third, the test
Plus parity custom annotations on the method to be verified @NoRepeatSubmit
to
@RestController
public class IndexController extends BaseController {
@NoRepeatSubmit
@GetMapping(value = "/index", produces = "application/json;charset=utf-8")
public ApiResult index() {
return ApiResult.ok("Hello World ~ ");
}
}
Here repeat visits this index
api request to submit the form to simulate tests
First visit http://127.0.0.1:8080/index
repeatedly refresh this request, the prompt请勿重复提交!
IV Summary
Realization of ideas
- Firstly,
AOP切面
before entering a method拦截
for checking logical processing Form to repeat - By
Redis
thekey-value键值对
logic judgment data storage needs [ex: key storing user submits the form to request path api, value stored commit time] 逻辑处理
:
The first data into the appropriate redis submitted to the
extraction time of the last commit redis from the cache when the current and saved again submitted to the operation time to make a judgment,
if the current operating time since the last operation time in the 'determination we set submitted to repeat the time (3 sec) "was repeated submitted directly returned repeatedly submit precautionary statements, or other processing,
otherwise normal submission, the traffic entering the processing method ...
supplement
If strict compliance api Restful风格
i.e., @PostMapping
a form submission, annotations can not customize the way to judge the path needs to be checked repeatedly submit, intercepts the request path directly after aop section, by reflecting on the method to get the annotation if there @PostMapping
if there is submission form api, ie verification process, that is, if there is no other @GetMapping
, @PutMapping
, @DeleteMapping
operation ...