@Pointcut("@annotation(repeatSubmit)")
public void pointCutNoRepeatSubmit(RepeatSubmit repeatSubmit){}
@Around("pointCutNoRepeatSubmit(repeatSubmit)")
public Object around(ProceedingJoinPoint joinPoint,RepeatSubmit repeatSubmit) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
//用于记录成功或者失败
boolean res =false;
//防重提交类型
String type = repeatSubmit.limitType().name();
if (type.equalsIgnoreCase(RepeatSubmit.Type.PARAM.name())){
//方式一:参数形式的防重提交 todo
}else{
//方式二:令牌形式防重提交
String requestToken = request.getHeader("request-token");
if (StringUtils.isBlank(requestToken)){
throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
}
String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY,accountNo,requestToken);
res = redisTemplate.delete(key);
}
if (!res){
throw new BizException(BizCodeEnum.ORDER_CONFIRM_REPEAT);
}
Object obj = joinPoint.proceed();
return obj;
}
这段代码使用了Spring AOP(面向切面编程)来实现一个防止重复提交的功能。以下是对代码的详细逐行解释:
-
定义切点:
@Pointcut("@annotation(repeatSubmit)") public void pointCutNoRepeatSubmit(RepeatSubmit repeatSubmit){}
@Pointcut
: 定义了一个切点,用于匹配所有使用@RepeatSubmit
注解的方法。pointCutNoRepeatSubmit
: 是切点的名称,它是一个方法签名,该方法接收一个RepeatSubmit
注解作为参数。
-
环绕通知:
@Around("pointCutNoRepeatSubmit(repeatSubmit)") public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable {
@Around
: 表示这是一个环绕通知,它会在目标方法执行前后以及抛出异常时执行。pointCutNoRepeatSubmit(repeatSubmit)
: 指定了环绕通知绑定的切点表达式,这里使用了上面定义的切点。around
: 是环绕通知的方法名,它接收两个参数:joinPoint
和repeatSubmit
。
-
获取请求对象:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
- 从Spring的请求上下文中获取当前的HTTP请求对象。
-
获取账户编号:
long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
- 从自定义的拦截器
LoginInterceptor
的线程局部变量中获取账户编号。
- 从自定义的拦截器
-
定义结果变量:
boolean res = false;
- 定义一个布尔变量
res
,用于记录操作是否成功。
- 定义一个布尔变量
-
获取防重提交类型:
String type = repeatSubmit.limitType().name();
- 从
RepeatSubmit
注解中获取limitType
属性的值,并将其转换为字符串。
- 从
-
根据类型处理:
if (type.equalsIgnoreCase(RepeatSubmit.Type.PARAM.name())) { // 方式一:参数形式的防重提交 } else { // 方式二:令牌形式防重提交
- 根据
limitType
的值,选择不同的防重提交方式。
- 根据
-
令牌形式防重提交:
String requestToken = request.getHeader("request-token"); if (StringUtils.isBlank(requestToken)) { throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL); } String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, requestToken); res = redisTemplate.delete(key);
- 从HTTP请求头中获取
request-token
。 - 如果请求头中没有
request-token
,则抛出业务异常。 - 使用账户编号和
request-token
构建Redis中的key。 - 尝试从Redis中删除对应的key,如果删除成功,则
res
设置为true
。
- 从HTTP请求头中获取
-
检查操作结果:
if (!res) { throw new BizException(BizCodeEnum.ORDER_CONFIRM_REPEAT); }
- 如果
res
为false
,表示防重提交操作失败,抛出业务异常。
- 如果
-
执行原方法:
Object obj = joinPoint.proceed();
- 使用
joinPoint.proceed()
执行原方法,即使用环绕通知的目标方法。
- 使用
-
返回结果:
return obj;
- 返回原方法的执行结果。
这段代码实现了一个基于AOP的防重提交机制,通过检查HTTP请求头中的令牌或方法参数来防止重复提交。如果检测到重复提交,将抛出异常;否则,允许执行原方法并返回结果。