refactor: 防重提交代码优化

This commit is contained in:
Ray.Hao
2025-01-09 22:48:35 +08:00
parent 8fa140482b
commit c632478395
3 changed files with 48 additions and 29 deletions

View File

@@ -6,10 +6,9 @@ import java.lang.annotation.*;
/** /**
* 防止重复提交注解 * 防止重复提交注解
* <p> * <p>
* 该注解用于方法上,防止在指定时间内的重复提交。 * 该注解用于方法上,防止在指定时间内的重复提交。 默认时间为5秒。
* 默认时间为5秒。
* *
* @author haoxr * @author Ray.Hao
* @since 2.3.0 * @since 2.3.0
*/ */
@Target(ElementType.METHOD) @Target(ElementType.METHOD)

View File

@@ -115,7 +115,7 @@ public enum ResultCode implements IResultCode, Serializable {
USER_OPERATION_PLEASE_WAIT("A0503", "用户操作请等待"), USER_OPERATION_PLEASE_WAIT("A0503", "用户操作请等待"),
WEBSOCKET_CONNECTION_EXCEPTION("A0504", "WebSocket 连接异常"), WEBSOCKET_CONNECTION_EXCEPTION("A0504", "WebSocket 连接异常"),
WEBSOCKET_CONNECTION_DISCONNECTED("A0505", "WebSocket 连接断开"), WEBSOCKET_CONNECTION_DISCONNECTED("A0505", "WebSocket 连接断开"),
USER_DUPLICATE_REQUEST("A0506", "用户重复请求"), USER_DUPLICATE_REQUEST("A0506", "请求过于频繁,请稍后再试。"),
/** 二级宏观错误码 */ /** 二级宏观错误码 */
USER_RESOURCE_EXCEPTION("A0600", "用户资源异常"), USER_RESOURCE_EXCEPTION("A0600", "用户资源异常"),

View File

@@ -25,15 +25,15 @@ import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* 处理重复提交切面 * 重复提交切面
* *
* @author haoxr * @author Ray.Hao
* @since 2.3.0 * @since 2.3.0
*/ */
@Aspect @Aspect
@Component @Component
@Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class RepeatSubmitAspect { public class RepeatSubmitAspect {
private final RedissonClient redissonClient; private final RedissonClient redissonClient;
@@ -42,41 +42,61 @@ public class RepeatSubmitAspect {
* 防重复提交切点 * 防重复提交切点
*/ */
@Pointcut("@annotation(repeatSubmit)") @Pointcut("@annotation(repeatSubmit)")
public void preventDuplicateSubmitPointCut(RepeatSubmit repeatSubmit) { public void repeatSubmitPointCut(RepeatSubmit repeatSubmit) {
log.info("定义防重复提交切点"); log.debug("定义防重复提交切点,注解:{}", repeatSubmit);
} }
@Around("preventDuplicateSubmitPointCut(repeatSubmit)")
public Object doAround(ProceedingJoinPoint pjp, RepeatSubmit repeatSubmit) throws Throwable {
String resubmitLockKey = generateResubmitLockKey(); /**
if (resubmitLockKey != null) { * 环绕通知:处理防重复提交逻辑
*/
@Around("repeatSubmitPointCut(repeatSubmit)")
public Object handleRepeatSubmit(ProceedingJoinPoint pjp, RepeatSubmit repeatSubmit) throws Throwable {
String lockKey = buildLockKey();
if (lockKey == null) {
log.warn("无法生成防重复提交锁的 key跳过防重复提交逻辑");
return pjp.proceed();
}
int expire = repeatSubmit.expire(); // 防重提交锁过期时间 int expire = repeatSubmit.expire(); // 防重提交锁过期时间
RLock lock = redissonClient.getLock(resubmitLockKey); RLock lock = redissonClient.getLock(lockKey);
boolean lockResult = lock.tryLock(0, expire, TimeUnit.SECONDS); // 获取锁失败,直接返回 false
if (!lockResult) { boolean locked = lock.tryLock(0, expire, TimeUnit.SECONDS);
throw new BusinessException(ResultCode.USER_DUPLICATE_REQUEST); // 抛出重复提交提示信息 log.info("获取防重复提交锁key{},是否成功:{}", lockKey, locked);
} if (!locked) {
log.warn("重复提交请求,锁 key{}", lockKey);
throw new BusinessException(ResultCode.USER_DUPLICATE_REQUEST);
} }
return pjp.proceed(); return pjp.proceed();
} }
/** /**
* 获取重复提交锁的 key * 生成防重复提交锁的 key
*
* @return 锁的 key如果无法生成则返回 null
*/ */
private String generateResubmitLockKey() { private String buildLockKey() {
String resubmitLockKey = null;
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader(HttpHeaders.AUTHORIZATION); String token = request.getHeader(HttpHeaders.AUTHORIZATION);
if (StrUtil.isNotBlank(token) && token.startsWith(SecurityConstants.JWT_TOKEN_PREFIX)) {
token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length()); if (StrUtil.isBlank(token) || !token.startsWith(SecurityConstants.JWT_TOKEN_PREFIX)) {
// 从 JWT Token 中获取 jti log.warn("请求头中未找到有效的 JWT Token");
String jti = (String) JWTUtil.parseToken(token).getPayload(RegisteredPayload.JWT_ID); return null;
resubmitLockKey = RedisConstants.RESUBMIT_LOCK_PREFIX + jti + ":" + request.getMethod() + "-" + request.getRequestURI();
} }
return resubmitLockKey;
// 解析 JWT Token 获取 jti
token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());
String jti = (String) JWTUtil.parseToken(token).getPayload(RegisteredPayload.JWT_ID);
if (StrUtil.isBlank(jti)) {
log.warn("JWT Token 中未找到 jti");
return null;
}
// 生成锁的 key前缀 + jti + 请求方法 + 请求路径
return RedisConstants.RESUBMIT_LOCK_PREFIX + jti + ":" + request.getMethod() + "-" + request.getRequestURI();
} }
} }