防重复请求注解测试

相同参数,一定时间内,重复请求拦截

主要代码如下:

RepeatRequest.java 
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatRequest {
String value() default "";
}
RepeatRequestAspect.java

@Aspect
@Component
public class RepeatRequestAspect {

private static final Logger LOGGER = LoggerFactory.getLogger(RepeatRequestAspect.class);

/**
* 执行切面拦截逻辑
*/
@Around("@annotation(repeatRequest)")
public Object execute(ProceedingJoinPoint joinPoint, RepeatRequest repeatRequest) throws Throwable {
if (repeatRequest != null) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

StringBuffer sb=new StringBuffer("");
//1.获取参数
HttpServletRequest request=attributes.getRequest();
HttpServletResponse response=attributes.getResponse();

//ip
sb.append("ip:");
String ip = ToolUtil.getClientIp(request);
sb.append(ip);

//header
sb.append("head:");
Enumeration<String> headers=request.getHeaderNames();
while(headers.hasMoreElements()){
String heardName =headers.nextElement();
String heardValue= request.getHeader(heardName);
sb.append(heardName).append("=").append(heardValue).append(";");
}

//body
sb.append("body:");
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
Object o = args[i];
sb.append(o.toString());
}

//2.所有 请求头、报文体 进行组装,查询redis 是否存在记录

LOGGER.info("sb="+sb.toString());

//sha 加密 、base64
QETag tag = new QETag();
String hash = tag.calcETag(sb.toString());
LOGGER.info("hash="+hash);

//存redis
String key = "request_key_" + hash;
String value=CacheUtils.get(key);

if(!StringUtil.isEmpty(value)){
//3.存在记录
//response.setStatus(304);
//怕摘要重复 可进行value处理,碰撞几率太小,可以忽略...
return RestResponse.failure("请勿重复请求");
}else{
//4.不存在记录,将所有参数组装写入redis,失效时间为3秒
CacheUtils.put(key, sb.toString(), 3);
}



}
return joinPoint.proceed();
}


}

tag.calcETag 其实就是 sha1 加密,然后再base64 -_-

就这样啦
 

转载于:https://www.cnblogs.com/xiaohouzixiashan/p/11558524.html

<think>好的,我现在需要帮用户解决使用注解方式Java重复提交的问题。首先,用户已经提供了一些引用内容,看起来是关于如何实现自定义注解和拦截器来重复提交的。不过这些引用可能不完全符合用户当前的需求,但需要从中提取有用的信息。 首先,我需要回顾用户的问题:用户想要查找使用注解方式Java重复提交的解决方案或示例代码。他们希望了解如何通过自定义注解和拦截器来实现这一点,并且可能需要具体的步骤和代码示例。 接下来,我需要考虑用户可能的背景。用户可能是一个Java开发者,正在开发一个Web应用,遇到了表单重复提交的问题,想要一种优雅的解决方案,而不是在每个方法里手动处理。使用注解的方式可以让他们在需要的方法上简单地添加注解,减少重复代码。 根据引用内容,特别是引用[1]、[2]、[4],用户提供的示例中提到了定义@RepeatSubmit注解,并设置间隔时间和提示消息。引用[3]提到重复提交的两种方式,一种是针对URL,另一种是URL加请求体,后者更合理。因此,我需要结合这些信息,设计一个基于注解和拦截器的解决方案,该方案应该考虑请求的URL和参数,避免误判。 接下来,我需要确定实现步骤。通常,这种实现包括以下几个部分: 1. 定义自定义注解@RepeatSubmit,包含间隔时间、提示信息等属性。 2. 创建拦截器,在请求处理前检查是否有该注解,如果有,则进行重复提交判断。 3. 生成唯一的请求标识符,可能是基于用户会话、请求参数、方法信息等。 4. 使用缓存(如Redis)或ConcurrentHashMap来存储请求标识符和最后一次提交时间。 5. 在拦截器中判断当前请求是否在允许的间隔时间内,如果是,则阻止提交并返回错误信息。 需要考虑线程安全,如果使用ConcurrentHashMap的话,可能需要处理并发情况。另外,使用Redis的好处是可以跨服务实例共享,适合分布式环境,而内存缓存则只适用于单实例。 然后,编写示例代码。根据引用中的示例,自定义注解需要包含interval、message等属性。拦截器部分需要实现HandlerInterceptor,在preHandle方法中进行判断。生成请求键的方式可能需要结合用户令牌(如果有的话)、请求的URI、参数等,确保唯一性。 还需要注意,如何获取请求参数。对于GET请求,参数在URL中,而POST请求可能需要读取请求体。但拦截器中直接读取请求体可能会有问题,因为输入流只能读取一次。因此,可能需要使用包装类来缓存请求体,如ContentCachingRequestWrapper,但这可能需要额外的配置。 此外,需要考虑不同用户之间的提交不应互相干扰,因此请求键应该包含用户唯一的标识,比如session ID或用户登录后的ID。如果用户未登录,可能需要其他方式,比如IP地址,不过IP可能不可靠。 在代码示例中,需要展示如何定义注解,如何实现拦截器,以及如何配置拦截器使其生效。同时,要处理异常情况,比如重复提交时返回JSON错误信息,或者跳转到错误页面。 可能还需要考虑注解的可配置性,比如允许不同的方法设置不同的间隔时间,或者不同的提示信息,这样用户可以根据具体需求进行调整。 最后,需要验证实现的正确性,比如测试在指定时间内重复提交是否被拦截,超过间隔时间后是否允许提交,以及不同用户或不同请求参数是否不会冲突。 总结来说,解决方案应包括定义注解、实现拦截器逻辑、生成唯一请求键、存储和检查提交记录,以及处理重复提交的响应。同时,需要提供示例代码,并解释关键部分的作用,帮助用户理解和应用。</think>### Java重复提交的注解实现方案 通过自定义注解结合拦截器实现接口重复提交,核心逻辑是**通过请求特征识别重复操作**。以下是具体实现步骤和代码示例: --- #### 一、定义重复提交注解 ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RepeatSubmit { /** * 重复时间窗口(单位:毫秒) */ int interval() default 5000; /** * 错误提示信息 */ String message() default "请勿重复提交请求"; /** * 是否验证请求体内容 */ boolean checkBody() default true; } ``` [^4] --- #### 二、实现拦截器逻辑 ```java public class RepeatSubmitInterceptor implements HandlerInterceptor { // 使用ConcurrentHashMap存储提交记录(生产环境建议替换为Redis) private static final Map<String, Long> SUBMIT_CACHE = new ConcurrentHashMap<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; RepeatSubmit annotation = handlerMethod.getMethodAnnotation(RepeatSubmit.class); if (annotation != null) { String requestKey = buildRequestKey(request, annotation.checkBody()); Long lastSubmitTime = SUBMIT_CACHE.get(requestKey); long currentTime = System.currentTimeMillis(); if (lastSubmitTime != null && (currentTime - lastSubmitTime) < annotation.interval()) { response.setContentType("application/json"); response.getWriter().write( String.format("{\"code\":400,\"message\":\"%s\"}", annotation.message())); return false; } SUBMIT_CACHE.put(requestKey, currentTime); } } return true; } // 生成唯一请求标识 private String buildRequestKey(HttpServletRequest request, boolean checkBody) { String sessionId = request.getSession().getId(); String uri = request.getRequestURI(); String params = request.getQueryString(); // 包含请求体时需要额外处理 if (checkBody) { String bodyHash = DigestUtils.md5Hex(getRequestBody(request)); return sessionId + ":" + uri + "?" + params + "#" + bodyHash; } return sessionId + ":" + uri + "?" + params; } // 获取请求体内容 private String getRequestBody(HttpServletRequest request) { try { return new BufferedReader(new InputStreamReader(request.getInputStream())) .lines().collect(Collectors.joining("\n")); } catch (IOException e) { return ""; } } } ``` [^2][^3] --- #### 三、注册拦截器 ```java @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new RepeatSubmitInterceptor()) .addPathPatterns("/**"); } } ``` --- #### 四、业务层使用示例 ```java @RestController public class OrderController { @PostMapping("/createOrder") @RepeatSubmit(interval = 3000, message = "订单创建中,请勿重复点击") public ApiResult createOrder(@RequestBody OrderDTO dto) { // 业务处理逻辑 return ApiResult.success(); } } ``` --- ### 核心实现原理 1. **唯一请求标识**:通过`SessionID + URI + 参数 + 请求体哈希值`生成唯一键,确保不同用户/不同内容的请求不会冲突[^3] 2. **时间窗口控制**:通过`interval`参数设置重复时间阈值 3. **请求体校验**:`checkBody`参数控制是否校验请求内容,适用于POST/PUT等含请求体的操作[^4] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值