springboot防重复提交
1、场景
网页卡顿的时候,用户点击会造成重复操作
如果前端不做防重复操作。会导致重复提交,重复下单等意外操作。而且对于系统资源来说也是一种浪费
常规的解决方法是让前端把点击后的按钮设置为不可点击,这样基本上能就能解决了。99.999999%能解决。前端这么弄过后,就没有遇到过需要后端弄的了。
为了万无一失,剩下的不能解决的就需要后端做防重复点击的操作了。
2、解决方案
2.1、前端通过js让按钮点击后失效,基本上就够了。除非别人要搞你网站,直接扒你接口,放心,你的网站没有那么有价值。
2.2、数据库增加唯一索引,不建议改数据库,虽然方便。
2.3、利用令牌防止表单重复提交
表单页面初始化时,会从后端获取一个生成一个token,这个token放在表单隐藏,当表单提交时一起提交,提交后后端使该token失效;
后端判断前端提交的token为空或者失效则表单提交失败(发送token,验证token)这里需要前后端配合
2.4、使用Spring AOP自定义切入实现,作为后端,倾向于这种方式。
一般接口提交时会有token验证,表明该请求是合法一个用户。后端可以通过 用户token+类+方法来判断是否是重复请求。
RepeatSubmitTestController 文件
@RestController
@RequestMapping(path = "/repeatSubmitTest", produces = "application/json;charset=UTF-8")
public class RepeatSubmitTestController{
@GetMapping("/listPage")
@RepeatSubmit(timeout = 20000)
@ApiOperation(value = "条件查询列表分页", notes = "条件查询列表分页")
public ResponseBean listPage(@RequestParam Map<String, Object> params, HttpServletRequest request) throws MyException {
return new ResponseBean(Constants.CODE_SUCCESS, "操作成功", "");
}
}
RepeatSubmit 文件
// @Target 表示该注解用于什么地方
// ElementType.CONSTRUCTOR 用在构造器
// ElementType.FIELD 用于描述域-属性上
// ElementType.METHOD 用在方法上
// ElementType.TYPE 用在类或接口上
// ElementType.PACKAGE 用于描述包
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE})
//@Retention 表示在什么级别保存该注解信息
// RetentionPolicy.SOURCE 保留到源码上
// RetentionPolicy.CLASS 保留到字节码上
// RetentionPolicy.RUNTIME 保留到虚拟机运行时(最多,可通过反射获取)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
/**
* 默认的间隔时间(ms),小于此时间视为重复提交
*/
int timeout() default 2000;
}
RepeatSubmitAspect 文件
// 开启日志,需要依赖lombok
@Slf4j
// 把一个类定义为切面供容器读取
@Aspect
@Component
public class RepeatSubmitAspect {
@Resource
private RedisTemplate<String, String> redisTemplate;
// 这是一个环绕通知,它会围绕被 @RepeatSubmit 注解标记的方法执行,这里的 repeatSubmit 与下面的参数对应
@Around("@annotation(repeatSubmit)")
public Object around(ProceedingJoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
// 获取用户的token验证,这里项目用的是 header 里的 Authorization 参数
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String requestToken = request.getHeader("Authorization");
// 获取注解
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
// 获取类,方法
String className = method.getDeclaringClass().getName();
String methodName = method.getName();
// 组装key:用户唯一标识+操作类+方法
String key = requestToken + "#" + className + "#" + methodName;
String keyHashCode = String.valueOf(Math.abs(key.hashCode()));
log.info("key:{},keyHashcode:{}", key, keyHashCode);
//获取超时时间
int timeOut = repeatSubmit.timeout();
log.info("超时时间{}", timeOut);
// 从缓存给中根据key获取数据
String value = redisTemplate.opsForValue().get(keyHashCode);
if (value != null) {
log.info("重复提交");
// 如果value不为空; return "请勿重复提交";
return new ResponseBean(Constants.CODE_SUCCESS, "重复提交,稍后重试", "");
} else {
log.info("首次提交");
// value为空,则加入缓存,并设置过期过期时间
redisTemplate.opsForValue().set(keyHashCode, "1", timeOut, TimeUnit.MILLISECONDS);
}
//执行Object
Object object = point.proceed();
return object;
}
}
标签:key,springboot,重复,token,提交,ElementType,String
From: https://blog.51cto.com/u_16159391/6499470