feat: 新增防重提交功能
This commit is contained in:
@@ -18,6 +18,8 @@ public enum ResultCode implements IResultCode, Serializable {
|
||||
SUCCESS("00000", "一切ok"),
|
||||
|
||||
USER_ERROR("A0001", "用户端错误"),
|
||||
REPEAT_SUBMIT_ERROR("A0002", "您的请求已提交,请不要重复提交或等待片刻再尝试。"),
|
||||
|
||||
USER_LOGIN_ERROR("A0200", "用户登录异常"),
|
||||
|
||||
USER_NOT_EXIST("A0201", "用户不存在"),
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.youlai.system.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisPassword;
|
||||
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
@@ -15,6 +18,21 @@ import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
@AutoConfigureBefore(RedisAutoConfiguration.class)
|
||||
public class RedisConfig {
|
||||
|
||||
@Value("${spring.data.redis.host}")
|
||||
private String redisHost;
|
||||
|
||||
@Value("${spring.data.redis.port}")
|
||||
private Integer redisPort;
|
||||
|
||||
@Value("${spring.data.redis.password}")
|
||||
private String redisPassword;
|
||||
|
||||
@Bean
|
||||
public LettuceConnectionFactory redisConnectionFactory() {
|
||||
RedisStandaloneConfiguration redisConfiguration = new RedisStandaloneConfiguration(redisHost, redisPort);
|
||||
redisConfiguration.setPassword(RedisPassword.of(redisPassword));
|
||||
return new LettuceConnectionFactory(redisConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* RedisTemplate 序列化配置
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.youlai.system.common.constant.ExcelConstants;
|
||||
import com.youlai.system.common.result.PageResult;
|
||||
import com.youlai.system.common.result.Result;
|
||||
import com.youlai.system.common.util.ExcelUtils;
|
||||
import com.youlai.system.framework.resubmit.Resubmit;
|
||||
import com.youlai.system.listener.UserImportListener;
|
||||
import com.youlai.system.pojo.vo.UserImportVO;
|
||||
import com.youlai.system.pojo.form.UserForm;
|
||||
@@ -64,6 +65,7 @@ public class SysUserController {
|
||||
@Operation(summary = "新增用户", security = {@SecurityRequirement(name = "Authorization")})
|
||||
@PostMapping
|
||||
@PreAuthorize("@pms.hasPermission('sys:user:add')")
|
||||
@Resubmit
|
||||
public Result saveUser(
|
||||
@RequestBody @Valid UserForm userForm
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.youlai.system.framework.resubmit;
|
||||
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 防重复提交注解
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2023/5/9
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface Resubmit {
|
||||
|
||||
/**
|
||||
* 防重提交锁过期时间(秒)
|
||||
* <p>
|
||||
* 默认5秒内不允许重复提交
|
||||
*/
|
||||
int expire() default 5;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.youlai.system.framework.resubmit;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.system.common.exception.BusinessException;
|
||||
import com.youlai.system.common.result.ResultCode;
|
||||
import com.youlai.system.common.util.RequestUtils;
|
||||
import com.youlai.system.framework.security.JwtTokenManager;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 防重复提交切面
|
||||
*
|
||||
* @author : haoxr
|
||||
* @since : 2023/05/09
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ResubmitAspect {
|
||||
|
||||
private final RedissonClient redissonClient;
|
||||
|
||||
private final JwtTokenManager jwtTokenManager;
|
||||
|
||||
private static final String RESUBMIT_LOCK_PREFIX = "LOCK:RESUBMIT:";
|
||||
|
||||
/**
|
||||
* 防重复提交切点
|
||||
*/
|
||||
@Pointcut("@annotation(resubmit)")
|
||||
public void preventDuplicateSubmitPointCut(Resubmit resubmit) {
|
||||
log.info("定义防重复提交切点");
|
||||
}
|
||||
|
||||
@Around("preventDuplicateSubmitPointCut(resubmit)")
|
||||
public Object doAround(ProceedingJoinPoint pjp, Resubmit resubmit) throws Throwable {
|
||||
|
||||
String resubmitLockKey = generateResubmitLockKey();
|
||||
if (resubmitLockKey != null) {
|
||||
int expire = resubmit.expire(); // 防重提交锁过期时间
|
||||
RLock lock = redissonClient.getLock(resubmitLockKey);
|
||||
boolean lockResult = lock.tryLock(0, expire, TimeUnit.SECONDS); // 获取锁失败,直接返回 false
|
||||
if (!lockResult) {
|
||||
throw new BusinessException(ResultCode.REPEAT_SUBMIT_ERROR); // 抛出重复提交提示信息
|
||||
}
|
||||
}
|
||||
Object result = pjp.proceed();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取防重提交锁的 key
|
||||
*/
|
||||
private String generateResubmitLockKey() {
|
||||
String resubmitLockKey = null;
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
String jwt = RequestUtils.resolveToken(request);
|
||||
if (StrUtil.isNotBlank(jwt)) {
|
||||
String jti = (String) jwtTokenManager.getTokenClaims(jwt).get("jti");
|
||||
resubmitLockKey = RESUBMIT_LOCK_PREFIX + jti + ":" + request.getMethod() + "-" + request.getRequestURI() + "-";
|
||||
}
|
||||
return resubmitLockKey;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user