xm
2024-06-14 722af26bc6fec32bb289b1df51a9016a4935610f
提交 | 用户 | 时间
722af2 1 package com.dl.framework.aspectj;
X 2
3 import cn.dev33.satoken.SaManager;
4 import cn.hutool.core.util.ObjectUtil;
5 import cn.hutool.crypto.SecureUtil;
6 import com.dl.common.annotation.RepeatSubmit;
7 import com.dl.common.constant.CacheConstants;
8 import com.dl.common.core.domain.R;
9 import com.dl.common.exception.ServiceException;
10 import com.dl.common.utils.JsonUtils;
11 import com.dl.common.utils.MessageUtils;
12 import com.dl.common.utils.ServletUtils;
13 import com.dl.common.utils.StringUtils;
14 import com.dl.common.utils.redis.RedisUtils;
15 import lombok.RequiredArgsConstructor;
16 import lombok.extern.slf4j.Slf4j;
17 import org.aspectj.lang.JoinPoint;
18 import org.aspectj.lang.annotation.AfterReturning;
19 import org.aspectj.lang.annotation.AfterThrowing;
20 import org.aspectj.lang.annotation.Aspect;
21 import org.aspectj.lang.annotation.Before;
22 import org.springframework.stereotype.Component;
23 import org.springframework.validation.BindingResult;
24 import org.springframework.web.multipart.MultipartFile;
25
26 import javax.servlet.http.HttpServletRequest;
27 import javax.servlet.http.HttpServletResponse;
28 import java.time.Duration;
29 import java.util.Collection;
30 import java.util.Map;
31
32 /**
33  * 防止重复提交(参考美团GTIS防重系统)
34  *
35  * @author Lion Li
36  */
37 @Slf4j
38 @RequiredArgsConstructor
39 @Aspect
40 @Component
41 public class RepeatSubmitAspect {
42
43     private static final ThreadLocal<String> KEY_CACHE = new ThreadLocal<>();
44
45     @Before("@annotation(repeatSubmit)")
46     public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
47         // 如果注解不为0 则使用注解数值
48         long interval = 0;
49         if (repeatSubmit.interval() > 0) {
50             interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval());
51         }
52         if (interval < 1000) {
53             throw new ServiceException("重复提交间隔时间不能小于'1'秒");
54         }
55         HttpServletRequest request = ServletUtils.getRequest();
56         String nowParams = argsArrayToString(point.getArgs());
57
58         // 请求地址(作为存放cache的key值)
59         String url = request.getRequestURI();
60
61         // 唯一值(没有消息头则使用请求地址)
62         String submitKey = StringUtils.trimToEmpty(request.getHeader(SaManager.getConfig().getTokenName()));
63
64         submitKey = SecureUtil.md5(submitKey + ":" + nowParams);
65         // 唯一标识(指定key + url + 消息头)
66         String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey;
67         String key = RedisUtils.getCacheObject(cacheRepeatKey);
68         if (key == null) {
69             RedisUtils.setCacheObject(cacheRepeatKey, "", Duration.ofMillis(interval));
70             KEY_CACHE.set(cacheRepeatKey);
71         } else {
72             String message = repeatSubmit.message();
73             if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
74                 message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1));
75             }
76             throw new ServiceException(message);
77         }
78     }
79
80     /**
81      * 处理完请求后执行
82      *
83      * @param joinPoint 切点
84      */
85     @AfterReturning(pointcut = "@annotation(repeatSubmit)", returning = "jsonResult")
86     public void doAfterReturning(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Object jsonResult) {
87         if (jsonResult instanceof R) {
88             try {
89                 R<?> r = (R<?>) jsonResult;
90                 // 成功则不删除redis数据 保证在有效时间内无法重复提交
91                 if (r.getCode() == R.SUCCESS) {
92                     return;
93                 }
94                 RedisUtils.deleteObject(KEY_CACHE.get());
95             } finally {
96                 KEY_CACHE.remove();
97             }
98         }
99     }
100
101     /**
102      * 拦截异常操作
103      *
104      * @param joinPoint 切点
105      * @param e         异常
106      */
107     @AfterThrowing(value = "@annotation(repeatSubmit)", throwing = "e")
108     public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) {
109         RedisUtils.deleteObject(KEY_CACHE.get());
110         KEY_CACHE.remove();
111     }
112
113     /**
114      * 参数拼装
115      */
116     private String argsArrayToString(Object[] paramsArray) {
117         StringBuilder params = new StringBuilder();
118         if (paramsArray != null && paramsArray.length > 0) {
119             for (Object o : paramsArray) {
120                 if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
121                     try {
122                         params.append(JsonUtils.toJsonString(o)).append(" ");
123                     } catch (Exception e) {
124                         e.printStackTrace();
125                     }
126                 }
127             }
128         }
129         return params.toString().trim();
130     }
131
132     /**
133      * 判断是否需要过滤的对象。
134      *
135      * @param o 对象信息。
136      * @return 如果是需要过滤的对象,则返回true;否则返回false。
137      */
138     @SuppressWarnings("rawtypes")
139     public boolean isFilterObject(final Object o) {
140         Class<?> clazz = o.getClass();
141         if (clazz.isArray()) {
142             return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
143         } else if (Collection.class.isAssignableFrom(clazz)) {
144             Collection collection = (Collection) o;
145             for (Object value : collection) {
146                 return value instanceof MultipartFile;
147             }
148         } else if (Map.class.isAssignableFrom(clazz)) {
149             Map map = (Map) o;
150             for (Object value : map.entrySet()) {
151                 Map.Entry entry = (Map.Entry) value;
152                 return entry.getValue() instanceof MultipartFile;
153             }
154         }
155         return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
156             || o instanceof BindingResult;
157     }
158
159 }