前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用Redis来实现接口幂等性校验

用Redis来实现接口幂等性校验

作者头像
CBeann
发布2023-12-25 18:05:13
2530
发布2023-12-25 18:05:13
举报
文章被收录于专栏:CBeann的博客CBeann的博客

项目简介

  • springboot
  • redis
  • @ApiIdempotentAnn注解 + 拦截器对请求进行拦截
  • 压测工具: jmeter

实现思路

为需要保证幂等性的每一次请求创建一个唯一标识token, 先获取token, 并将此token存入redis, 请求接口时, 将此token放到header或者作为请求参数请求接口, 后端接口判断redis中是否存在此token:

  • 如果存在, 正常处理业务逻辑, 并从redis中删除此token, 那么, 如果是重复请求, 由于token已被删除, 则不能通过校验, 返回重复提交
  • 如果不存在, 说明参数不合法或者是重复请求, 返回提示即可

请求流程

当页面加载的时候通过接口获取token

当访问接口时,会经过拦截器,如果发现该接口有自定义的幂等性注解,说明该接口需要验证幂等性(查看请求头里是否有key=token的值,如果有,并且删除成功,那么接口就访问成功,否则为重复提交);如果发现该接口没有自定义的幂等性注解,放行。

代码

pom依赖

添加redis依赖

代码语言:javascript
复制
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

自定义注解

即添加了该注解的接口要实现幂等性验证

代码语言:javascript
复制
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiIdempotentAnn {
  boolean value() default true;
}

幂等性拦截器

代码语言:javascript
复制
package com.example.springbootdemointerfacemideng.intceptor;

import com.example.springbootdemointerfacemideng.annotation.ApiIdempotentAnn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Method;

/**
 * @author CBeann
 * @create 2020-07-04 18:06
 */
@Component
public class ApiIdempotentInceptor extends HandlerInterceptorAdapter {

  @Autowired private StringRedisTemplate stringRedisTemplate;

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {

    if (!(handler instanceof HandlerMethod)) {
      return true;
    }

    final HandlerMethod handlerMethod = (HandlerMethod) handler;

    final Method method = handlerMethod.getMethod();
    // 有这个注解
    boolean methodAnn = method.isAnnotationPresent(ApiIdempotentAnn.class);
    if (methodAnn && method.getAnnotation(ApiIdempotentAnn.class).value()) {
      // 需要实现接口幂等性
      boolean result = checkToken(request);

      if (result) {
        return super.preHandle(request, response, handler);
      } else {

        response.setContentType("application/json; charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print("重复调用");
        writer.close();
        response.flushBuffer();
        return false;
      }
    }

    return super.preHandle(request, response, handler);
  }

  @Override
  public void postHandle(
      HttpServletRequest request,
      HttpServletResponse response,
      Object handler,
      ModelAndView modelAndView)
      throws Exception {

    super.postHandle(request, response, handler, modelAndView);
  }

  private boolean checkToken(HttpServletRequest request) {

    String token = request.getHeader("token");
    if (null == token || "".equals(token)) {
      // 没有token,说明重复调用或者
      return false;
    }
    // 返回是否删除成功
    return stringRedisTemplate.delete(token);
  }
}

MVC配置文件

代码语言:javascript
复制
/**
 * @author chaird
 * @create 2020-09-23 16:13
 */
@Configuration
public class MVCConfig extends WebMvcConfigurerAdapter {

  @Autowired private ApiIdempotentInceptor apiIdempotentInceptor;

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    // 获取http请求拦截器
    registry.addInterceptor(apiIdempotentInceptor).addPathPatterns("/*");
  }
}

接口层

代码语言:javascript
复制
package com.example.springbootdemointerfacemideng.controller;

import com.example.springbootdemointerfacemideng.annotation.ApiIdempotentAnn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author chaird
 * @create 2020-09-23 15:47
 */
@RestController
public class ApiController {

  AtomicInteger num = new AtomicInteger(100);

  @Autowired private StringRedisTemplate stringRedisTemplate;

  /**
   * 前端获取token,然后把该token放入请求的header中
   *
   * @return
   */
  @GetMapping("/getToken")
  public String getToken() {
    String token = UUID.randomUUID().toString().substring(1, 9);
    stringRedisTemplate.opsForValue().set(token, "1");
    return token;
  }

  /**
   * 主业务逻辑,num--,并且加了自定义接口
   *
   * @return
   */
  @GetMapping("/submit")
  @ApiIdempotentAnn
  public String rushB() {
    // num--
    num.decrementAndGet();
    return "success";
  }

  /**
   * 查看num的值
   *
   * @return
   */
  @GetMapping("/getNum")
  public String getNum() {
    return String.valueOf(num.get());
  }
}

测试

(1)首先调用http://localhost:8080/getToken 获取token

(2)用JMeter测试

配置一百个线程在1秒内访问

配置添加请求接口

添加请求头里的key和value

(3)分析结果

说明只成功调用了一次http://localhost:8080/submit 接口

代码下载

Demooo/springboot-demo-interface-mideng at master · cbeann/Demooo · GitHub

参考

SpringBoot + Redis + 注解 + 拦截器来实现接口幂等性校验:SpringBoot + Redis + 注解 + 拦截器来实现接口幂等性校验

JMeter的使用:Jmeter的简单使用_CBeann的博客-CSDN博客

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-03-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 项目简介
  • 实现思路
  • 请求流程
  • 代码
    • pom依赖
      • 自定义注解
        • 幂等性拦截器
          • MVC配置文件
            • 接口层
              • 测试
              • 参考
              相关产品与服务
              云数据库 Redis
              腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档