feat: 新增防重提交功能

This commit is contained in:
haoxr
2023-05-10 06:46:06 +08:00
parent c94f7cecae
commit 2f62566b56
5 changed files with 127 additions and 0 deletions

View File

@@ -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", "用户不存在"),

View File

@@ -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 序列化配置

View File

@@ -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
) {

View File

@@ -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;
}

View File

@@ -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;
}
}