|
@@ -0,0 +1,117 @@
|
|
|
+package shop.alien.config.filter;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+import org.springframework.util.DigestUtils;
|
|
|
+import org.springframework.web.method.HandlerMethod;
|
|
|
+import org.springframework.web.servlet.HandlerInterceptor;
|
|
|
+import shop.alien.config.redis.BaseRedisService;
|
|
|
+import shop.alien.entity.result.R;
|
|
|
+import shop.alien.util.common.JwtUtil;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.PrintWriter;
|
|
|
+import java.lang.reflect.Method;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 防止重复提交拦截器
|
|
|
+ *
|
|
|
+ * @author lingma
|
|
|
+ * @version 1.0
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Component
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class NoRepeatSubmitInterceptor implements HandlerInterceptor {
|
|
|
+
|
|
|
+ private final BaseRedisService baseRedisService;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
|
|
+ if (handler instanceof HandlerMethod) {
|
|
|
+ HandlerMethod handlerMethod = (HandlerMethod) handler;
|
|
|
+ Method method = handlerMethod.getMethod();
|
|
|
+ NoRepeatSubmit noRepeatSubmit = method.getAnnotation(NoRepeatSubmit.class);
|
|
|
+
|
|
|
+ if (noRepeatSubmit != null) {
|
|
|
+ String key = generateKey(request, method);
|
|
|
+ if (!lock(key, noRepeatSubmit)) {
|
|
|
+ // 重复提交
|
|
|
+ handleRepeatSubmit(response, noRepeatSubmit.message());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成锁的键值
|
|
|
+ * key 值组成: repeat_submit:方法所在类名:方法名:用户ID(如果已登录)或IP地址:请求URI的MD5摘
|
|
|
+ * @param request 请求
|
|
|
+ * @param method 方法
|
|
|
+ * @return 键值
|
|
|
+ */
|
|
|
+ private String generateKey(HttpServletRequest request, Method method) {
|
|
|
+ StringBuilder sb = new StringBuilder("repeat_submit:");
|
|
|
+ sb.append(method.getDeclaringClass().getName());
|
|
|
+ sb.append(":");
|
|
|
+ sb.append(method.getName());
|
|
|
+ sb.append(":");
|
|
|
+
|
|
|
+ // 添加用户标识
|
|
|
+ JSONObject userInfo = JwtUtil.getCurrentUserInfo();
|
|
|
+ if (userInfo != null) {
|
|
|
+ sb.append(userInfo.getInteger("userId"));
|
|
|
+ } else {
|
|
|
+ sb.append(request.getRemoteAddr());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加请求参数
|
|
|
+ sb.append(":");
|
|
|
+ sb.append(DigestUtils.md5DigestAsHex(request.getRequestURI().getBytes()));
|
|
|
+
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加锁 分布式锁实现
|
|
|
+ * 如果键不存在则设置成功并返回 true,否则返回 false
|
|
|
+ * @param key 键
|
|
|
+ * @param noRepeatSubmit 注解
|
|
|
+ * @return 是否加锁成功
|
|
|
+ */
|
|
|
+ private boolean lock(String key, NoRepeatSubmit noRepeatSubmit) {
|
|
|
+ try {
|
|
|
+ // 使用Redis的SET命令的NX和EX选项实现分布式锁
|
|
|
+ // 键的过期时间由 @NoRepeatSubmit 注解中的参数决定
|
|
|
+ Boolean result = baseRedisService.setStringIfAbsent(key, "1",
|
|
|
+ noRepeatSubmit.timeUnit().toSeconds(noRepeatSubmit.expireTime()));
|
|
|
+ return result != null && result;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("防重复提交加锁异常", e);
|
|
|
+ return true; // 出现异常不阻止用户操作
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理重复提交
|
|
|
+ *
|
|
|
+ * @param response 响应
|
|
|
+ * @param message 提示信息
|
|
|
+ * @throws IOException IO异常
|
|
|
+ */
|
|
|
+ private void handleRepeatSubmit(HttpServletResponse response, String message) throws IOException {
|
|
|
+ response.setContentType("application/json;charset=UTF-8");
|
|
|
+ PrintWriter writer = response.getWriter();
|
|
|
+ R<Object> fail = R.fail(message);
|
|
|
+ writer.write(JSONObject.toJSONString(fail));
|
|
|
+ writer.flush();
|
|
|
+ writer.close();
|
|
|
+ }
|
|
|
+}
|