提交 | 用户 | 时间
|
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 |
} |