• Redis+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,如此
    在这里插入图片描述

  • 相关阅读:
    不了解无线调制方式?这几个“老古董”大家现在还在用!
    Notepad++--宏(记录操作过程)
    管理类联考——数学——汇总篇——知识点突破——代数——函数、方程——记忆
    【Python】基础数据结构:列表——元组——字典——集合
    KingbaseES参数track_activity_query_size介绍
    PHP 反序列化漏洞:__PHP_Incomplete_Class 与 serialize(unserialize($x)) !== $x;
    Apollo 应用与源码分析:CyberRT-时间相关API
    猿创征文|Aixos的引入与基本使用
    OpenCV中LineTypes各枚举值(LINE_4 、LINE_8 、LINE_AA )的含义
    vue--vuex 中 Modules 详解
  • 原文地址:https://blog.csdn.net/weixin_40598838/article/details/125260921