|
@@ -0,0 +1,133 @@
|
|
|
|
|
+package com.fs.framework.aspectj;
|
|
|
|
|
+
|
|
|
|
|
+import com.fs.app.annotation.NoRepeatSubmit;
|
|
|
|
|
+import com.fs.app.utils.JwtUtils;
|
|
|
|
|
+import com.fs.app.utils.RepeatSubmitKeyGenerator;
|
|
|
|
|
+import com.fs.common.core.domain.R;
|
|
|
|
|
+import com.fs.common.core.redis.RedisCache;
|
|
|
|
|
+import com.fs.common.exception.CustomException;
|
|
|
|
|
+import com.fs.common.utils.StringUtils;
|
|
|
|
|
+import io.jsonwebtoken.Claims;
|
|
|
|
|
+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.aspectj.lang.reflect.MethodSignature;
|
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
|
|
+import org.springframework.web.context.request.RequestContextHolder;
|
|
|
|
|
+import org.springframework.web.context.request.ServletRequestAttributes;
|
|
|
|
|
+
|
|
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
|
|
+import java.lang.reflect.Method;
|
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 防重复提交切面
|
|
|
|
|
+ */
|
|
|
|
|
+@Aspect
|
|
|
|
|
+@Component
|
|
|
|
|
+public class NoRepeatSubmitAspect {
|
|
|
|
|
+
|
|
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(NoRepeatSubmitAspect.class);
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private RedisCache redisCache;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private JwtUtils jwtUtils;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 初始化,将JwtUtils注入到KeyGenerator中
|
|
|
|
|
+ */
|
|
|
|
|
+ @PostConstruct
|
|
|
|
|
+ public void init() {
|
|
|
|
|
+ RepeatSubmitKeyGenerator.setJwtUtils(jwtUtils);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 定义切点:所有添加了@NoRepeatSubmit注解的方法
|
|
|
|
|
+ */
|
|
|
|
|
+ @Pointcut("@annotation(com.fs.app.annotation.NoRepeatSubmit)")
|
|
|
|
|
+ public void noRepeatSubmitPointcut() {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 环绕通知处理防重复提交
|
|
|
|
|
+ */
|
|
|
|
|
+ @Around("noRepeatSubmitPointcut()")
|
|
|
|
|
+ public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
|
|
|
|
+ // 获取方法上的注解
|
|
|
|
|
+ MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
|
|
|
|
+ Method method = signature.getMethod();
|
|
|
|
|
+ NoRepeatSubmit annotation = method.getAnnotation(NoRepeatSubmit.class);
|
|
|
|
|
+
|
|
|
|
|
+ // 获取过期时间
|
|
|
|
|
+ int expire = annotation.expire();
|
|
|
|
|
+ TimeUnit timeUnit = annotation.timeUnit();
|
|
|
|
|
+ String message = annotation.message();
|
|
|
|
|
+
|
|
|
|
|
+ // 生成Redis Key
|
|
|
|
|
+ String key = RepeatSubmitKeyGenerator.generateKey(joinPoint);
|
|
|
|
|
+
|
|
|
|
|
+ // 尝试设置Redis键,使用setIfAbsent(相当于setnx)
|
|
|
|
|
+ Boolean success = redisCache.setIfAbsent(key, "1", expire, timeUnit);
|
|
|
|
|
+
|
|
|
|
|
+ if (Boolean.TRUE.equals(success)) {
|
|
|
|
|
+ // 第一次请求,正常执行方法
|
|
|
|
|
+ try {
|
|
|
|
|
+ return joinPoint.proceed();
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ // 如果方法执行异常,删除key,允许重试
|
|
|
|
|
+ redisCache.deleteObject(key);
|
|
|
|
|
+ throw e;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 重复请求,记录日志并返回提示
|
|
|
|
|
+ String userId = getUserIdFromToken();
|
|
|
|
|
+ log.warn("防重复提交拦截: key={}, 用户={}, URI={}, 方法={}",
|
|
|
|
|
+ key, userId, getRequestURI(), method.getName());
|
|
|
|
|
+ return R.error(message);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从Token中获取用户ID
|
|
|
|
|
+ */
|
|
|
|
|
+ private String getUserIdFromToken() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
|
|
|
|
+ if (attributes != null) {
|
|
|
|
|
+ HttpServletRequest request = attributes.getRequest();
|
|
|
|
|
+ String token = request.getHeader("APPToken");
|
|
|
|
|
+ if (StringUtils.isNotEmpty(token)) {
|
|
|
|
|
+ Claims claims = jwtUtils.getClaimByToken(token);
|
|
|
|
|
+ if (claims != null) {
|
|
|
|
|
+ return claims.getSubject();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("获取用户ID失败", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ return "unknown";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取当前请求的URI
|
|
|
|
|
+ */
|
|
|
|
|
+ private String getRequestURI() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
|
|
|
|
+ if (attributes != null) {
|
|
|
|
|
+ return attributes.getRequest().getRequestURI();
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("获取请求URI失败", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ return "unknown";
|
|
|
|
|
+ }
|
|
|
|
|
+}
|