SpringBoot结合Redis实现幂等

本文介绍了如何在SpringBoot应用中利用Redis实现幂等性。通过获取token并将其存储在Redis中,配合幂等注解和拦截器,确保同一操作在多次请求下仍能保持结果一致。同时,文章涵盖了项目依赖、幂等注解、拦截器、Redis服务、Token服务、异常处理和Controller层的实现细节。

SpringBoot结合Redis实现幂等

1、相关概念和思路

  • 幂等:就是说客户端不管访问多少次,服务器端处理或者返回的结果都是一样
  • 思路:我们首先让客户端访问一个获取token的接口,然后让客户端携带token来访问具有幂等(@Idempotent)注解的接口,从而消费掉这个token,这样就可以避免用户重复访问一个接口了,我们把token存在redis中并且设置好过期时间。

2、项目依赖和配置

这里没有用到数据库,只是使用spring-web和redis两个依赖,配置也就是配置redis相关

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
spring.redis.host=localhost
spring.redis.port=6379

3、幂等注解(@Idempotent)

注解起一个标识的作用,我们还需要使用拦截器用来拦截方法,然后通过方法对象获取到方法上面的注解,

我们判断这个注解是否是我们需要的,如果是,则执行相关操作就可以了,不是,直接放行。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
}

4、拦截器

  • 实现HandlerInterceptor接口,加上注解@Component交给spring管理
@Component
public class IdempotentInterceptor implements HandlerInterceptor {

    // 注解的作用就是起一个标识的作用,使用拦截器可以拦截方法,然后方法可以获取到注解,判断注解上的方法是否是我们想要的注解,
    // 是:执行我们需要的逻辑;不是:注解放行或者拦截

    @Autowired
    private TokenService tokenService;

  	  //预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器,自定义Controller的方法对象
    //返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),
    // 不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // com.lkun.controller.IndexController#hello1()
        System.out.println(handler);
        // 思路:先把错误的情况全部排除
        if (!(handler instanceof HandlerMethod)){
            return true;
        }
        Idempotent idempotent = ((HandlerMethod) handler).getMethodAnnotation(Idempotent.class);
        if (idempotent == null){
            return true;
        }
        // 获取到了方法上的注解,然后怎么办呢,我们就调用TokenService的验证方法,验证下,看是否符合条件
        try {
            return tokenService.checkToken(request);
        } catch (NotExistException e) {
            e.printStackTrace();
            throw e;
        }
    }
}
  • 配置拦截器,我们首先把自定义的拦截器IdempotentInterceptor通过注解@Autowired注入进来,然后在重写的addInterceptors方法中通过参数InterceptorRegistry把拦截器注册进去,并设置好拦截路径。
@Configuration
public class IdempotentConfig implements WebMvcConfigurer {

    @Autowired
    private IdempotentInterceptor interceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor).addPathPatterns("/**");
    }
}

5、RedisService封装了redis相关操作

@Component
public class RedisService {

   @Autowired
    private RedisTemplate redisTemplate;

   public boolean setEx(String key, Object value,Long expireTime){
       boolean result = false;
       try {
           redisTemplate.opsForValue().set(key,value,expireTime, TimeUnit.SECONDS);
           result = true;
       } catch (Exception e) {
           e.printStackTrace();
       }
       return result;
   }

   public boolean exist(String key){
       return redisTemplate.hasKey(key);
   }

   public boolean remove(String key){
       if (exist(key)){
           redisTemplate.delete(key);
           return true;
       }
       return false;
   }

}

6、TokenService用来产生token并把token放入到redis中

@Component
public class TokenService {
    @Autowired
    private RedisService redisService;

    public String getToken(){
        String token = UUID.randomUUID().toString();
        redisService.setEx(token,"token",10000L);
        return token;
    }


    public boolean checkToken(HttpServletRequest request) throws NotExistException {
        // 请求头中获取token
      	String token = request.getHeader("token");
        if (StringUtils.isEmpty(token)){
          	// 请求体中获取token
            token = request.getParameter("token");
            if (StringUtils.isEmpty(token)){
                throw new NotExistException("请携带token访问");
            }
        }
        if (!redisService.exist(token)){
            throw new NotExistException("重复请求");
        }

        boolean remove = redisService.remove(token);
        if (!remove){
            throw new NotExistException("重复请求");
        }
        return true;
    }

}

7、异常处理

这里采用全局异常处理,直接throw,然后使用@RestControllerAdvice注解进行全局异常处理

  • 自定义异常
public class NotExistException extends RuntimeException {

    public NotExistException(String message) {
        super(message);
    }
}
  • @RestControllerAdvice全局异常处理
// 这是一个增强通知,加上了这个注解就可以处理
@RestControllerAdvice
public class GlobalExceptionHandler {


    @ExceptionHandler(NotExistException.class)
    public String HandlerNotExitException(NotExistException e){
        return e.getMessage();
    }

}

8、Controller层

@RestController
public class IndexController {

    @Autowired
    private TokenService tokenService;

    @GetMapping("getToken")
    public String getToken(){
       return tokenService.getToken();
    }

    @PostMapping("hello1")
    @Idempotent
    public String hello1(){
        return "hello1";
    }

    @PostMapping("hello2")
    public String hello2(){
        return "hello2";
    }
}

9、测试

用户先访问getToken接口,获取到token,然后携带token进行访问hello1接口,再次访问就会返回"重复请求",hello2是没有加上幂等注解,可以随意访问。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值