diff --git a/src/main/java/com/youlai/system/common/result/ResultCode.java b/src/main/java/com/youlai/system/common/result/ResultCode.java index 1bcbcfe9..6da7e27c 100644 --- a/src/main/java/com/youlai/system/common/result/ResultCode.java +++ b/src/main/java/com/youlai/system/common/result/ResultCode.java @@ -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", "用户不存在"), diff --git a/src/main/java/com/youlai/system/config/RedisConfig.java b/src/main/java/com/youlai/system/config/RedisConfig.java index 819ff67f..f2523932 100644 --- a/src/main/java/com/youlai/system/config/RedisConfig.java +++ b/src/main/java/com/youlai/system/config/RedisConfig.java @@ -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 序列化配置 diff --git a/src/main/java/com/youlai/system/controller/SysUserController.java b/src/main/java/com/youlai/system/controller/SysUserController.java index 812a0e54..cbb4ef40 100644 --- a/src/main/java/com/youlai/system/controller/SysUserController.java +++ b/src/main/java/com/youlai/system/controller/SysUserController.java @@ -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 ) { diff --git a/src/main/java/com/youlai/system/framework/resubmit/Resubmit.java b/src/main/java/com/youlai/system/framework/resubmit/Resubmit.java new file mode 100644 index 00000000..b5632aee --- /dev/null +++ b/src/main/java/com/youlai/system/framework/resubmit/Resubmit.java @@ -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 { + + /** + * 防重提交锁过期时间(秒) + *

+ * 默认5秒内不允许重复提交 + */ + int expire() default 5; + +} diff --git a/src/main/java/com/youlai/system/framework/resubmit/ResubmitAspect.java b/src/main/java/com/youlai/system/framework/resubmit/ResubmitAspect.java new file mode 100644 index 00000000..131bb2e4 --- /dev/null +++ b/src/main/java/com/youlai/system/framework/resubmit/ResubmitAspect.java @@ -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; + } + +}