使用切面实现前端重复提交(防抖)
- 代码结构
- 定义注解
- 请求锁切面处理器
- 入参对象
- 使用注解
代码结构
原理:
1、前端提交保存操作;
2、后端通过注解指定重复提交的关键字段进行识别,可以有多个;
3、拼接关键字段,缓存到redis中,设置到期时间(默认3秒);
4、命中缓存则进行防抖处理,否则进行正常业务。
定义注解
/**
* @description 请求防抖锁,用于防止前端重复提交导致的错误
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RequestLock {
/**redis 锁前缀 */
String prefix() default "";
/**redis 锁过期时间, 默认3秒 */
int expire() default 3;
/** redis 锁过期时间单位,默认单位为秒 */
TimeUnit timeUnit() default TimeUnit.SECONDS;
/** redis key分隔符, 分隔符 */
String delimiter() default ":";
/** 防抖关键标识,使用spring el 表达式<br>
* 例子:<br>
* 第一个参数(对象)中的一个属性(电话号码) : params[0].phone<br>
* 第一个参数(Map)中的键值对(电话号码) : params[0]['phone']<br>
*/
String[] keys();
}
请求锁切面处理器
/**
* @description 请求锁切面处理器
*/
@Aspect
@Configuration
public class RequestLockAspect {
@Autowired
StringRedisTemplate stringRedisTemplate;
/** 切入点声明 */
@Pointcut("execution(public * * (..)) && @annotation(zzc.learn.springboot.demo.repeatsubmit.annotation.RequestLock)")
public void pointcut() {
// do nothing
}
/** 定义切面:环绕通知 */
@Around("pointcut()")
public Object interceptor(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
RequestLock requestLock = method.getAnnotation(RequestLock.class);
if (StringUtils.isEmpty(requestLock.prefix())) {
// throw new RuntimeException("重复提交前缀不能为空");
return "重复提交前缀不能为空";
}
//获取自定义key
final String lockKey = _getLockKey(joinPoint);
final Boolean success = stringRedisTemplate.execute(
(RedisCallback<Boolean>) connection -> connection.set(lockKey.getBytes(), new byte[0], Expiration.from(requestLock.expire(), requestLock.timeUnit())
, RedisStringCommands.SetOption.SET_IF_ABSENT));
if (!success) {
// throw new RuntimeException("您的操作太快了,请稍后重试");
return "您的操作太快了,请稍后重试";
}
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
// throw new RuntimeException("系统异常");
return "系统异常";
}
}
/** 获取防抖提交对应的rediss的key */
private String _getLockKey(ProceedingJoinPoint joinPoint) {
//获取连接点的方法签名对象
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//Method对象
Method method = methodSignature.getMethod();
//获取Method对象上的注解对象
RequestLock requestLock = method.getAnnotation(RequestLock.class);
//获取方法参数
List<Object> spelValueist = _getSpelValueList(requestLock, joinPoint.getArgs());
StringBuilder sb = new StringBuilder();
for (Object object : spelValueist) {
sb.append(requestLock.delimiter()).append(object);
}
//返回指定前缀的key
return requestLock.prefix() + sb;
}
/** 根据spel表达式读取对应参数的值 */
private List<Object> _getSpelValueList(RequestLock requestLock, final Object[] args) {
List<Object> spelValueist = new ArrayList<>();
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("params", args);
String[] spelArr = requestLock.keys();
for (int i = 0; i < spelArr.length; i++) {
String expressionString = spelArr[i];
if(StringUtils.isBlank(expressionString)) {
continue;
}
Expression expression = parser.parseExpression(expressionString);
spelValueist.add(expression.getValue(context));
}
return spelValueist;
}
}
入参对象
@Data
public class User {
private String name;
private Integer age;
private String phone;
}
使用注解
@Api(value = "重复提交测试", tags = {"重复提交测试"})
@RestController
@RequestMapping("/repeatsubmit/user")
public class UserController {
@ApiOperation(value = "增加用户(不限制重复提交)", notes = "增加用户1")
@PostMapping("/addUser1")
public String addUser1(@RequestBody User user) {
System.out.println("不做任何处理" + user);
return "添加成功";
}
@ApiOperation(value = "增加对象类型用户(限制重复提交)", notes = "增加用户2")
@PostMapping("/addUser2")
@RequestLock(prefix = "addUser", keys= {"#params[0].name","#params[0].phone"})
public String addUser2(@RequestBody User user, @RequestParam String name) {
System.out.println("防重提交" + user);
return "添加成功";
}
@ApiOperation(value = "增加Map类型用户(限制重复提交)", notes = "增加用户3")
@PostMapping("/addUser3")
@RequestLock(prefix = "addUser", keys = {"#params[0]['phone']"})
public String addUser3(@RequestBody Map<String, Object> paramMap, @RequestParam String name) {
System.out.println("防重提交" + paramMap);
return "添加成功";
}
}