Java后端实现简单的防止重复提交校验
前言:
重复提交是指页面按钮提交时的误操作点击多次,或者用户估计进行快速点击多次按钮行为,破坏系统的稳定性,进而增加一页安全限制对系统进行保护
本次系统实现的方案:
- 单体应用
- 不对接redis
- spring-boot项目
- 使用内存缓存
代码实现
才有注解+切面的方式进行拦截校验,之所以这样做而不是用intceptor是因为这样自由度更高,因为我们基本都是对有写操作的接口进行保护的,而读接口是不用校验的
1、定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {
}
2、定义数据模型
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UrlParamValue {
private String userAddressId;
private String methodName;
private Object[] args;
private Long time;
public int hashValue() {
return Objects.hash(userAddressId, methodName, Arrays.hashCode(args));
}
}
3、定义切面,实现拦截功能
@Aspect
@Component
@Slf4j
public class NoRepeatSubmitAspect {
@Value(value = "${noRepeatSubmit.expireTime:500}")
private Long expireTime;
// 内存缓存,基于Guava Cache
private Cache<Integer, UrlParamValue> PARAM_REQUEST_CACHE = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(2, TimeUnit.SECONDS)
.build();
/**
* 横切点
*/
@Pointcut("@annotation(noRepeatSubmit)")
public void repeatPoint(NoRepeatSubmit noRepeatSubmit) {
}
/**
* 接收请求,并记录数据
*/
@Around(value = "repeatPoint(noRepeatSubmit)")
public Object doBefore(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) {
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().toShortString();
// 获取用户信息,使用自己的方式进行替换
User user = AuthUtils.instance().getUser();
// 目前根据用户、类方法、参数 hash 确定是否唯一
UrlParamValue paramValue = new UrlParamValue(user.getAddressId(), methodName, args, System.currentTimeMillis());
int hash = paramValue.hashValue();
try {
UrlParamValue cacheParam = PARAM_REQUEST_CACHE.get(hash, () -> paramValue);
// 判断是否是刚放进去的数据
if (!paramValue.equals(cacheParam)) {
// 判断间隔时间
if (paramValue.getTime() - cacheParam.getTime() < expireTime) {
log.warn("userAddressId={},method={}访问过于频繁", paramValue.getUserAddressId(), paramValue.getMethodName());
return BaseResponse.error(ResponseCode.OPERATE_REPEATEDLY);
}
}
} catch (ExecutionException e) {
log.error("用户拦截切面缓存执行异常", e);
}
PARAM_REQUEST_CACHE.put(hash, paramValue);
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
if (throwable instanceof BizException) {
return BaseResponse.error(((BizException) throwable).getErrorInfo());
}
log.error("运行业务代码出错", throwable);
return BaseResponse.error(ResponseCode.SYSTEM_ERROR);
}
}
}
4、使用
@PostMapping("challenge")
// 增加注解即可
@NoRepeatSubmit
public BaseResponse<BattleChallengeResponse> challenge(@Validated @RequestBody BattlePKRequest request) {
xxxxx
xxxxx
}
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 蛮王的记事本
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果
Steam卡片