首页 > 数据库 >springboot 中使用 redis 处理接口的幂等性

springboot 中使用 redis 处理接口的幂等性

时间:2023-06-15 23:22:52浏览次数:55  
标签:return springboot redis 接口 token key IdempontentException public String

什么是接口幂等性?

数学中:在一次元运算为幂等时,其作用在任一元素两次后会和其作用一次的结果相同;在二次元运算为幂等时,自己重复运算的结果等于它自己的元素。

计算机学中:幂等指多次操作产生的影响只会跟一次执行的结果相同,通俗的说:某个行为重复的执行,最终获取的结果是相同的,不会因为重复执行对系统造成变化。

例如:用户购买商品后支付扣款成功,但是此时网络发生了异常,导致返回结果失败。因为没收到返回结果,用户就会再次点击付款按钮,就会多付了一笔钱,从而用户付款了两次,这显然是不行的。

即一次操作要么成功要么失败,再次操作则是重新执行。

有如下场景:

  • 用户重复下订单:当用户下单时,因为网络问题或者手速过快,导致重复下单。
  • 消息重复消费:当使用 MQ 消息中间件时候,如果消息中间件发生异常出现错误未及时提交消费信息,导致消息被重复消费。
  • 抽奖活动(券):当用户参加抽奖活动需要消耗抽奖券时,如果出现并发请求导致抽奖券余额更新错误。
  • 重复提交表单:当用户填写表单提交时,可能会因为用户点多次连击提交或者网络波动导致服务端未及时响应,会导致用户重复的提交表单,就出现了同一个表单多次请求。

这些只是我们常见的一些状况,还需要根据自己的项目的实际情况进行分析,判断是否需要幂等操作。

涉及到数据的修改一般会涉及到幂等性问题,就是执行多次与执行一次结果一样

以下操作是基于分布式锁-----token令牌实现的。就是生成全局的唯一 id 实现,这里是基于 redis 来实现的。还可以有其他方案:如snowflake 雪花算法美团 Leaf 算法滴滴 TinyID 算法百度 Uidgenerator 算法uuid

什么是token

Token在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。

这种机制适用范围较广,有多种不同的实现方式。其核心思想是每一次操作生成唯一的身份码,操作完成身份码也就不存在。

一个 Token 在操作的每一个阶段只有一次执行权,一旦执行成功就直接保存执行结果。

具体操作如图:

一、拦截器实现

1、创建创建redisservice类

见aop实现

2、创建 tokenservice 类

见aop实现

3、自定义注解

见aop实现

4、创建拦截器

拦截器中需要进行前置拦截,也是通过 controller 中的方法是否有自定义的注解来判断

@Component
public class IdempontentInterceptor implements HandlerInterceptor {
    @Autowired
    TokenService tokenService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断 handler 的类型是否为 HandlerMethod
        if (!(handler instanceof HandlerMethod)){
            return true;
        }

        //获取方法
        Method method = ((HandlerMethod) handler).getMethod();
        //获取方法上的注解
        AutoIdempontent autoIdempontent = method.getAnnotation(AutoIdempontent.class);
        //判断方法是否有该注解
        if (autoIdempontent != null){
            try {
                return tokenService.checkToken(request);
            } catch (IdempontentException e) {
                throw e;
            }
        }
        return true;
    }
}

5、配置类配置拦截器

在配置类中配置拦截器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    IdempontentInterceptor interceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //设置拦截的路径
        registry.addInterceptor(interceptor).addPathPatterns("/**");
    }
}

6、测试

见aop实现中的 controller 测试

二、aop实现

1、创建redisservice类

用于对 redis 中存入数据

@Service
public class RedisService {
    @Autowired
    StringRedisTemplate stringRedisTemplate;

    //1、存储key-value
    public boolean setEx(String key,String value,Long expireTime){
        boolean result = false;
        try {
            //获取操作
            ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
            //redis存入key-value
            ops.set(key, value);
            //设置过期时间
            stringRedisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            //设置返回值
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    //判断key是否存在
    public boolean isExists(String key){
        return stringRedisTemplate.hasKey(key);
    }

    //移除key-value
    public boolean remove(String key){
        if (isExists(key)){
            return stringRedisTemplate.delete(key);
        }
        return false;
    }

}

2、创建 tokenservice 类

用于创建token、判断是否有token

@Service
public class TokenService {
    @Autowired
    RedisService redisService;

    //创建令牌,也就是在 redis 中存入key-value
    public String createToken(){
        String key = UUID.randomUUID().toString();
        redisService.setEx(key, key, 10000L);
        return key;
    }

    //判断token
    public boolean checkToken(HttpServletRequest request) throws IdempontentException {
        String token = request.getHeader("token");
        //1、判断请求头中是否有token
        if (StringUtils.isEmpty(token)){
            //2、如果请求头中为空,则再从请求体中判断
            token = request.getParameter("token");
            if (StringUtils.isEmpty(token)){//判断请求体中token
                throw new IdempontentException("token 不存在");
            }
        }
        //3、判断 token 是否存在
        if (!redisService.isExists(token)){//redis key 可能过期了,被 redis 删除
            throw new IdempontentException("重复操作");
        }
        //4、如果 token 存在,则移除token
        boolean result = redisService.remove(token);
        if (!result){//令牌过期或已消费过
            throw new IdempontentException("重复操作");
        }
        return true;
    }
}

自定义异常

public class IdempontentException extends Exception{
    public IdempontentException(String message) {
        super(message);
    }
}

因为在其中自定义了异常,所以通过全局的异常处理来将异常信息输出

@RestControllerAdvice
public class GlobalException {
    @ExceptionHandler(IdempontentException.class)
    public String idempontentException(IdempontentException e){
        return e.getMessage();
    }
}

3、自定义注解

用于判断方法检查是否有token

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

4、定义切面

@Component
@Aspect
public class IdempontentAspect {
    @Autowired
    TokenService tokenService;

    @Pointcut("@annotation(com.xrr.idempontent.ann.AutoIdempontent)")
    public void pcl(){

    }

    @Before("pcl()")
    public void before() throws IdempontentException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        try {
            tokenService.checkToken(request);
        } catch (IdempontentException e) {
            throw e;
        }
    }

}

在切面中通过自定义的注解来定义切点,再通过 tokenservice 来检查是否有 token

5、controller测试

在controller中定义三个方法,创建token、测试token

@RestController
public class HelloController {
    @Autowired
    TokenService tokenService;

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

    @PostMapping("/h1")
    @AutoIdempontent
    public String hello1(){
        return "hello1";
    }

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

标签:return,springboot,redis,接口,token,key,IdempontentException,public,String
From: https://www.cnblogs.com/xiarongblogs/p/17484474.html

相关文章

  • 2023-06-15:说一说Redis的Key和Value的数据结构组织?
    2023-06-15:说一说Redis的Key和Value的数据结构组织?答案2023-06-15:全局哈希表Redis使用哈希表作为保存键值对的数据结构,通过哈希函数将Key映射为哈希表中的一个索引位置,使得Key-Value可以在O(1)时间复杂度内被快速访问。在Redis中,哈希表是由多个哈希桶(也称为槽位/数组元素)组成......
  • SpringBoot快速整合RabbitMq小案例
    对于一个直接创建的springBoot项目工程来说,可以按照以下步骤使用rabbitmq添加依赖:添加rabbitMQ的依赖。<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>配置连接:在配置文件中配置虚拟主......
  • springboot项目启动
    报错:Couldnotresolveplaceholder'config.info'invalue"${config.info}"项目的bootstrap.yml配置文件的file-extension:yaml#这里我们获取的yaml格式的配置需要跟nacos配置列表的DataId结尾保持一致 ......
  • SpringBoot集成kafka
    环境springboot2.7+kafka3.0。kafka安装请自行百度,话不多说直接上代码。1、添加maven依赖<dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId></dependency> 2、application.yml添加kafka配置sp......
  • 通过python封装接口获取淘宝商品页面数据、淘宝商品详情数据
    可以使用GET或POST方法,请求参数中应包含商品详情页面数据、标题、价格、图片、库存、销量等信息。解析返回的response中的HTML页面或JSON格式数据,提取需要的商品信息,如商品标题、价格、评价人数等。使用pandas库将提取的商品信息保存到数据框中,以方便后续处理和分析。......
  • SpringBoot整合Redis
    第一步:导入坐标 第二步:在application.yml中进行相关配置第三步:使用对应的API对操作接口进行操作 操作 key-value格式的 操作 hash格式的  ......
  • 轻松掌握Python+主流测试框架Requests接口自动化,快速转型自动化测试
    轻松掌握Python+主流测试框架Requests接口自动化,快速转型自动化测试最近几年,自动化测试已经成为了软件测试的主流趋势,而Python语言和Requests库作为主流测试框架,也成为了越来越多测试工程师的首选。使用Python+Requests接口自动化进行测试,不仅可以提高测试效率和覆盖面,还可以降低......
  • redis学习八:数据类型命令及落地运用 (Zset)
    有序,附带分数,适用于排行榜1.zaddkeyscore1v1score2v2新增键值对;zrangezsetstartend查看对应范围值zrangekeystartendwithscores带着分数查看;zrevrangekey倒序查看,用法和zrange类似; 2.zrangebyscorekeyminmax取分数范围内的value;也可以在前面加上(是不......
  • springboot集成kafka
    导入spring-kafka依赖信息点击查看代码<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--kafkfa-->......
  • pytest接口自动化(三)—— 优秀的脚本规范
    上一篇给大家讲了单接口脚本和多接口脚本,这一篇给大家讲讲个人对接口自动化的一些理解和看法。从整个软件自动化测试来说,接口自动化从公司规模,测试效率,测试成本,可执行性,稳定性等来说,都是任何一个公司的测试人员首选的测试方式。接着我们来详细的说下。对于任何一个......