通过拦截器配合自定义注解,解决接口幂等性

一、为什么会出现接口幂等性?

接口幂等性是指一个接口在多次请求的情况下,其执行结果始终保持一致,不会因多次请求而产生不同的结果。

解决接口幂等性问题的方法主要有以下几种:

1.使用Token或令牌:每次请求时,服务器生成一个唯一的Token或令牌,然后将该Token或令牌返回给客户端。客户端在后续的请求中,需要携带该Token或令牌。服务器在接收到请求后,会检查请求中的Token或令牌是否有效。如果无效,则拒绝请求;如果有效,则继续处理请求。这样可以保证每个请求都是唯一的,从而实现接口的幂等性。

2.使用锁:在处理请求时,可以使用锁来保证同一时间只有一个请求在运行。其他请求在检测到锁存在时会等待,直到锁释放后再进行处理。这样可以保证每个请求都只会被执行一次,从而实现接口的幂等性。

3.使用消息队列:在处理请求时,可以将请求放入消息队列中。然后,服务器会从消息队列中取出请求并处理。由于消息队列的特性,可以保证每个请求都只会被处理一次,从而实现接口的幂等性。

4.使用版本号 :在处理请求时,可以为每个请求分配一个版本号。然后在处理请求时,会检查请求的版本号是否有效。如果无效,则拒绝请求;如果有效,则继续处理请求。这样可以保证每个请求都只会被执行一次,从而实现接口的幂等性。

5.使用缓存:在处理请求时,可以将请求的结果缓存起来。然后在处理请求时,会检查缓存中是否有对应的结果。如果有,则直接返回缓存中的结果;如果没有,则继续处理请求并将结果缓存起来。这样可以保证每个请求都只会被处理一次,从而实现接口的幂等性。

二、这里使用 通过拦截器+自定义注解+Redis,解决接口幂等性

在需要处理的请求上打上自定义注解,拦截器获取该请求的接口是否存在自定义注解,根据缓存的接口和请求数据进行拦截(也可以根据注解自己写一个切面类,自定义实现,原理类似,只是获取请求参数方法差异)

2.1 创建自定义注解

代码如下(示例):


/**
 * 自定义注解防止重复请求
 *
 * @author liu
 */
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {

	/**
	 * 默认指定100ms
	 */
	int intervalTime() default 100;

	/**
	 * 时间单位 默认毫秒
	 */
	TimeUnit timeUnit() default TimeUnit.MILLISECONDS;

}

2.2 编写拦截器

代码如下(示例):

@Component
public  class RepeatSubmitInterceptor implements HandlerInterceptor {

	public static final String REPEAT_SUBMIT_KEY = "repeat_submit:";
	
	@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            //获取该方法是否带RepeatSubmit 注解
            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit .class);
            if (annotation != null) {
                if (this.isRepeatSubmit(annotation, request)) {
                    Result result = Result.error("不允许重复提交,请稍后再试");
                    ServletUtils.renderString(response, JSONUtil.toJsonStr(result));
                    return false;
                }
            }
            return true;
        } else {
            return HandlerInterceptor.super.preHandle(request, response, handler);
        }
    }
    /**
     * 验证是否重复提交
     */
    public  boolean isRepeatSubmit(RepeatSubmit annotation, HttpServletRequest request) throws IOException {
        String nowParams = "";
        try {
        	//从自定义的可重复读取HttpServletRequest获取输入流
        	//使用了hutool的IoUtil、SecureUtil、JSONUtil
            nowParams = IoUtil.readUtf8(request.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
        if(StrUtil.isEmpty(nowParams)){
            nowParams = JSONUtil.toJsonStr(request.getParameterMap());
        }
        String submitKey = SecureUtil.md5(nowParams);
        // 请求地址
        String url = request.getRequestURI();
        // 唯一标识(指定key + url + 消息头)
        String cacheRepeatKey = REPEAT_SUBMIT_KEY + url + submitKey;
        //工具类封RedisUtils装了RedissonClient里的getBucket 和setBucket
        String key = RedisUtils.getCacheObject(cacheRepeatKey);
        //使用Redisson 也可以使用 redisTemplate.opsForValue();步骤类似
        RBucket<Object> bucket = redissonClient.getBucket(cacheRepeatKey);
        if (bucket.get() == null) {
            if (bucket.setIfAbsent(cacheRepeatKey)){
                bucket.expire(annotation.intervalTime(),annotation.timeUnit());
                return false;
            }
        }
        return true;
    }
}

因为设置了过期时间,无需手动释放

3. 实现可重复读取HttpServletRequest输入流

参考该链接进行实现:https://blog.csdn.net/LSL1618/article/details/112907142
注意:该拦截器顺序应该在接口拦截器之前执行

4.配置Redis

配置Redisson,参考链接:https://blog.csdn.net/feiying0canglang/article/details/126524833
配置redis,参考链接: https://blog.csdn.net/penanut/article/details/115913922

压力测试

使用ab命令执行压力测试 模拟用户请求,可以看到只有一次输出
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/4f010b12ad9543bc803554b398f2757d.png

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值