本文目录导读:

我来帮你设计一个防止表单重复提交的注解方案,包含完整的实现。
定义注解
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PreventDuplicateSubmit {
/**
* 防重复提交的key前缀
*/
String prefix() default "prevent_duplicate:";
/**
* 锁的超时时间,默认5秒
*/
int timeout() default 5;
/**
* 时间单位,默认秒
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* 请求频率限制,0表示不限制
*/
int interval() default 0;
}
创建切面实现
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
public class PreventDuplicateSubmitAspect {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Around("@annotation(preventDuplicateSubmit)")
public Object around(ProceedingJoinPoint joinPoint, PreventDuplicateSubmit preventDuplicateSubmit) throws Throwable {
// 获取请求信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 生成唯一key
String key = generateKey(joinPoint, request, preventDuplicateSubmit.prefix());
// 检查是否重复提交
if (isDuplicateSubmit(key, preventDuplicateSubmit.timeout(), preventDuplicateSubmit.timeUnit())) {
throw new RuntimeException("请勿重复提交请求");
}
// 执行原方法
return joinPoint.proceed();
}
private String generateKey(ProceedingJoinPoint joinPoint, HttpServletRequest request, String prefix) {
StringBuilder sb = new StringBuilder(prefix);
// 获取类名和方法名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
sb.append(method.getDeclaringClass().getName())
.append(".")
.append(method.getName());
// 获取用户标识(可以是用户ID、IP等)
String userId = getUserId(request);
sb.append(":").append(userId);
// 获取请求参数
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
sb.append(":").append(generateParamKey(args));
}
return sb.toString();
}
private String getUserId(HttpServletRequest request) {
// 从请求中获取用户标识,这里需要根据实际业务实现
// 从token、session等中获取
String token = request.getHeader("Authorization");
if (token != null) {
// 解析token获取用户ID
}
return request.getRemoteAddr(); // 默认使用IP
}
private String generateParamKey(Object[] args) {
// 生成参数key,可以根据实际需要序列化
StringBuilder sb = new StringBuilder();
for (Object arg : args) {
if (arg != null) {
sb.append(arg.hashCode()).append("_");
}
}
return sb.toString();
}
private boolean isDuplicateSubmit(String key, int timeout, TimeUnit timeUnit) {
// 使用Redis的SETNX命令实现分布式锁
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, "1", timeout, timeUnit);
return Boolean.FALSE.equals(success);
}
}
使用示例
@RestController
@RequestMapping("/api/order")
public class OrderController {
@PostMapping("/submit")
@PreventDuplicateSubmit(prefix = "order_submit:", timeout = 5)
public Result submitOrder(@RequestBody OrderDTO orderDTO) {
// 处理订单逻辑
return Result.success();
}
@PostMapping("/pay")
@PreventDuplicateSubmit(prefix = "order_pay:", timeout = 10, interval = 3)
public Result payOrder(@RequestBody PayDTO payDTO) {
// 处理支付逻辑
return Result.success();
}
}
优化版本(支持配置和自定义处理)
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PreventDuplicateSubmit {
String prefix() default "prevent_duplicate:";
int timeout() default 5;
TimeUnit timeUnit() default TimeUnit.SECONDS;
int interval() default 0;
// 自定义处理器
Class<? extends DuplicateSubmitHandler> handler() default DefaultDuplicateSubmitHandler.class;
// 是否启用
boolean enabled() default true;
}
// 处理器接口
public interface DuplicateSubmitHandler {
/**
* 处理重复提交
* @param key 唯一标识
* @param timeout 超时时间
* @param timeUnit 时间单位
* @return true表示是重复提交
*/
boolean handleDuplicateSubmit(String key, int timeout, TimeUnit timeUnit);
/**
* 生成唯一key
*/
String generateKey(ProceedingJoinPoint joinPoint, HttpServletRequest request);
/**
* 处理重复提交后的返回
*/
Object handleDuplicateResponse(ProceedingJoinPoint joinPoint);
}
// 默认实现
@Component
public class DefaultDuplicateSubmitHandler implements DuplicateSubmitHandler {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public boolean handleDuplicateSubmit(String key, int timeout, TimeUnit timeUnit) {
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, "1", timeout, timeUnit);
return Boolean.FALSE.equals(success);
}
@Override
public String generateKey(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
// 默认key生成逻辑
return null;
}
@Override
public Object handleDuplicateResponse(ProceedingJoinPoint joinPoint) {
throw new BusinessException("请勿重复提交");
}
}
// 增强的切面
@Aspect
@Component
public class EnhancedPreventDuplicateSubmitAspect {
@Autowired
private ApplicationContext applicationContext;
@Around("@annotation(preventDuplicateSubmit)")
public Object around(ProceedingJoinPoint joinPoint, PreventDuplicateSubmit preventDuplicateSubmit) throws Throwable {
if (!preventDuplicateSubmit.enabled()) {
return joinPoint.proceed();
}
// 获取自定义处理器
DuplicateSubmitHandler handler = getHandler(preventDuplicateSubmit.handler());
// 生成key
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String key = handler.generateKey(joinPoint, request);
// 检查重复提交
if (handler.handleDuplicateSubmit(key, preventDuplicateSubmit.timeout(), preventDuplicateSubmit.timeUnit())) {
return handler.handleDuplicateResponse(joinPoint);
}
return joinPoint.proceed();
}
private DuplicateSubmitHandler getHandler(Class<? extends DuplicateSubmitHandler> handlerClass) {
if (handlerClass == DefaultDuplicateSubmitHandler.class) {
return new DefaultDuplicateSubmitHandler();
}
return applicationContext.getBean(handlerClass);
}
}
前端配合方案
// 前端防重复提交工具
class PreventDuplicateSubmit {
constructor() {
this.submitting = false;
this.submitMap = new Map();
}
/**
* 检查是否可以提交
* @param {string} key 提交标识
* @param {number} interval 间隔时间(毫秒)
*/
canSubmit(key, interval = 1000) {
const now = Date.now();
const lastSubmit = this.submitMap.get(key);
if (lastSubmit && (now - lastSubmit) < interval) {
console.warn('提交过于频繁');
return false;
}
this.submitMap.set(key, now);
return true;
}
/**
* 清除指定key的提交记录
*/
clearSubmit(key) {
this.submitMap.delete(key);
}
/**
* 清除所有提交记录
*/
clearAll() {
this.submitMap.clear();
}
}
// 使用示例
const preventDuplicate = new PreventDuplicateSubmit();
async function submitForm() {
const submitKey = 'submit_order_' + JSON.stringify(formData);
if (!preventDuplicate.canSubmit(submitKey, 2000)) {
showToast('请勿重复提交');
return;
}
try {
const loading = showLoading();
await api.submitOrder(formData);
hideLoading(loading);
showSuccess('提交成功');
} catch (error) {
preventDuplicate.clearSubmit(submitKey);
showError(error.message);
}
}
这个方案包含了完整的后端注解实现和前端配合方案,可以根据实际需求进行调整和扩展。