本文目录导读:

我来帮你设计一个防止表单重复提交的注解方案,包含完整的实现。
定义注解
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);
}
}
这个方案包含了完整的后端注解实现和前端配合方案,可以根据实际需求进行调整和扩展。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播