今天我们来看看如何通过AOP来防止重复提交
哪些因素会引起重复提交?
开发的项目中可能会出现下面这些情况:
- 前端下单按钮重复点击导致订单创建多次
- 网速等原因造成页面卡顿,用户重复刷新提交请求
- 黑客或恶意用户使用postman等http工具重复恶意提交表单
重复提交会带来哪些问题?
重复提交带来的问题
- 会导致表单重复提交,造成数据重复或者错乱
- 核心接口的请求增加,消耗服务器负载,严重甚至会造成服务器宕机
订单的防重复提交你能想到几种方案?
核心接口需要做防重提交,你应该可以想到以下几种方案:
方式一:前端JS控制点击次数,屏蔽点击按钮无法点击 前端可以被绕过,前端有限制,后端也需要有限制
方式二:数据库或者其他存储增加唯一索引约束 需要想出满足业务需求的唯一索引约束,比如注册的手机号唯一。但是有些业务是没有唯一性限制的,且重复提交也会导致数据错乱,比如你在电商平台可以买一部手机,也可以买两部手机
方式三:服务端token令牌方式 下单前先获取令牌-存储redis,下单时一并把token提交并检验和删除-lua脚本
分布式情况下,采用Lua脚本进行操作(保障原子性)
其中方式三 是大家采用的最多的,那有没更加优雅的方式呢?
假如系统中不止一个地方,需要用到这种防重复提交,每一次都要写这种lua脚本,代码耦合性太强,这种又不属于业务逻辑,所以不推荐耦合进service中,可读性较低。
本文采用自定义注解+AOP的方式,优雅的实现防止重复提交功能。
自定义注解
Java核心知识-自定义注解(先了解下什么是自定义注解)
Annotation(注解)
从JDK 1.5开始, Java增加了对元数据(MetaData)的支持,也就是 Annotation(注解)。 注解其实就是代码里的特殊标记,它用于替代配置文件,常见的很多,有 @Override、@Deprecated等
什么是元注解
元注解是注解的注解,比如当我们需要自定义注解时会需要一些元注解(meta-annotation),如@Target和@Retention
java内置4种元注解
@Target 表示该注解用于什么地方
- ElementType.CONSTRUCTOR 用在构造器
- ElementType.FIELD 用于描述域-属性上
- ElementType.METHOD 用在方法上
- ElementType.TYPE 用在类或接口上
- ElementType.PACKAGE 用于描述包
@Retention 表示在什么级别保存该注解信息
- RetentionPolicy.SOURCE 保留到源码上
- RetentionPolicy.CLASS 保留到字节码上
- RetentionPolicy.RUNTIME 保留到虚拟机运行时(最多,可通过反射获取)
@Documented 将此注解包含在 javadoc 中
- @Inherited 是否允许子类继承父类中的注解
- @interface 用来声明一个注解,可以通过default来声明参数的默认值
自定义注解时,自动继承了java.lang.annotation.Annotation接口,可以通过反射可以获取自定义注解
AOP+自定义注解接口防重提交多场景设计
防重提交方式
- token令牌方式
- ip+类+方法方式(方法参数)
利用AOP来实现
- Aspect Oriented Program 面向切面编程, 在不改变原有逻辑上增加额外的功能
- AOP思想把功能分两个部分,分离系统中的各种关注点
好处
- 减少代码侵入,解耦
- 可以统一处理横切逻辑,方便添加和删除横切逻辑
业务流程:
代码实战防重提交自定义注解之Token令牌/参数方式
自定义注解token令牌方式
第一步 自定义注解
import java.lang.annotation.*;
/**
* 自定义防重提交
*/
@Documented
@Target(ElementType.METHOD)//可以用在方法上
@Retention(RetentionPolicy.RUNTIME)//保留到虚拟机运行时,可通过反射获取
public @interface RepeatSubmit {
/**
* 防重提交,支持两种,一个是方法参数,一个是令牌
*/
enum Type { PARAM, TOKEN