当前位置:   article > 正文

Redis+token实现接口幂等性_redis+token为什么可以幂等,如果两次请求一样的token

redis+token为什么可以幂等,如果两次请求一样的token

概述

秒杀场景或情况下,用户多次发送请求会导致订单表中有多条数据的情况,为此要实现的功能是接口幂等,用户无论发多少次请求都只能创建一个单子

思路

时序图
在这里插入图片描述

首先客户端请求token接口,获取到token,服务端生成token并在redis中存一份,每次请求的时候,客户端将token带过来,由拦截器检验token,token存在redis中则说明是第一次请求,将数据写入数据库中,并删除redis中的token,第二次客户端再携带token时,去redis中查,如果redis中没有,那么说明是第二次请求了返回重复操作提示

demo

首先定义一个幂等性注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiIdempotent {
}
  • 1
  • 2
  • 3
  • 4

幂等性拦截器

public class IdempotentTokenInterceptor implements HandlerInterceptor {

    @Autowired
    private TokenService tokenService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        //幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示
        ApiIdempotent apiIdempotent = handlerMethod.getMethod().getAnnotation(ApiIdempotent.class);
        if (apiIdempotent != null) {
            tokenService.checkToken(request);
        }

        return true;
    }

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

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

拦截器配置类

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册接口幂等性拦截器
        registry.addInterceptor(idempotentTokenInterceptor());
    }

    @Bean
    public IdempotentTokenInterceptor idempotentTokenInterceptor() {
        return new IdempotentTokenInterceptor();
    }

    /**
     * 跨域
     *
     * @return
     */
    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

token Service类

public interface TokenService {

    String createToken();

    void checkToken(HttpServletRequest request) throws Exception;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

token ServiceImpl类

@Service
public class TokenServiceImpl implements TokenService {

    private static final String TOKEN_NAME = "token";

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Override
    public String createToken() {
        //通过UUID来生成token
        String tokenValue = "idempotent:token:" + UUID.randomUUID().toString();
        //将token放入redis中,设置有效期为60S
        stringRedisTemplate.opsForValue().set(tokenValue, "0", 60, TimeUnit.SECONDS);
        return tokenValue;
    }

    /**
     * @param request
     */
    @Override
    public void checkToken(HttpServletRequest request) throws Exception {
        String token = request.getHeader(TOKEN_NAME);
        if (StringUtils.isBlank(token)) {
            token = request.getParameter(TOKEN_NAME);
            if (StringUtils.isBlank(token)) {
                //没有携带token,抛异常,这里的异常需要全局捕获
                throw new Exception("非法参数");
            }
        }
        //token不存在,说明token已经被其他请求删除或者是非法的token
        if (!stringRedisTemplate.hasKey(token)) {
            throw new Exception("请勿重复操作");
        }
        boolean del = stringRedisTemplate.delete(token);
        if (!del) {
            //token删除失败,说明token已经被其他请求删除
            throw new Exception("请勿重复操作");
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

测试

这里我们用Jmeter和postfox来做测试,Jmeter做用户并发请求,postfox获取token

首先测试计划中添加变量
在这里插入图片描述

接着在Jmeter线程组中右键添加事务控制器
在这里插入图片描述

在事务控制器中添加HTTP信息头管理器,设置token,token通过apifox拿
在这里插入图片描述
在添加一个HTTP请求,设置好相应的数据参数
在这里插入图片描述

最后在线程组处添加一个查看结果树

在这里插入图片描述

这里模拟的是一个用户请求n的并发情况,本来想模拟n个用户m次请求,但是疏于对jmeter的使用,为此暂且先考虑1个用户n次请求的情况接下来启动看看

在这里插入图片描述

可以看到请求了n次,只有一个数据被写入的状况,也就大体实现了幂等性,往后看看模拟n个用户m次请求的情况,是否是满足的,我想应该是没用问题的
在这里插入图片描述
补充几个StringRedisTemplate的使用方法

第一个参数是key,第二个参数是value,第三个参数时间,第四个参数是时间单位,通常为TimeUnit中的枚举类

stringRedisTemplate.opsForValue().set(tokenValue, "0", 60, TimeUnit.SECONDS);
  • 1

在这里插入图片描述
stringRedisTemplate.hasKey:判断相应的key是否在Redis中
在这里插入图片描述
stringRedisTemplate.delete(key):在Redis中删除相应的key值,删除成功返回true,否则返回false,如此
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/330047
推荐阅读
相关标签
  

闽ICP备14008679号