首页 > 文章列表 > springboot如何实现接口自动幂等

springboot如何实现接口自动幂等

springboot
312 2023-05-18

springboot如何实现接口自动幂等

幂等

1.概念: 任意多次执行所产生的影响均与一次执行的影响相同。

按照这个含义,最终的含义就是 对数据库的影响只能是一次性的,不能重复处理。如何保证其幂等性,通常有以下手段:

1: 数据库建立唯一性索引,可以保证最终插入数据库的只有一条数据

2: token 机制,每次接口请求前先获取一个 token,然后再下次请求的时候在请求的 header 体中加上这个 token,后台进行验证,如果验证通过删除 token,下次请求再次判断 token

3: 悲观锁或者乐观锁,悲观锁可以保证每次 for update 的时候其他 sql 无法 update 数据 (在数据库引擎是 innodb 的时候, select 的条件必须是唯一索引, 防止锁全表)

4: 先查询后判断,首先通过查询数据库是否存在数据,如果存在证明已经请求过了,直接拒绝该请求,如果没有存在,就证明是第一次进来,直接放行。

redis 实现自动幂等的原理图:

一. 搭建redis的服务Api

1: 首先是搭建 redis 服务器。

2: 引入 springboot 中到的 redis 的 stater,或者 Spring 封装的 jedis 也可以,后面主要用到的 api 就是它的 set 方法和 exists 方法, 这里我们使用 springboot 的封装好的 redisTemplate

代码如下:

/*

redis工具类

*/

@Component

public class RedisService {



    @Autowired

    private RedisTemplate redisTemplate;



    /**

     * 写入缓存

     * @param key

     * @param value

     * @return

     */

    public  boolean set(final String key,Object value){

        boolean result = false;

        try {

            ValueOperations<Serializable,Object> operations = redisTemplate.opsForValue();

            operations.set(key,value);

            result = true;

        }catch (Exception e){

            result = false;

            e.printStackTrace();

        }

        return result;

    }



    /**

     * 写入缓存有效期

     * @return

     */

    public boolean setEx(final String key ,Object value,Long expireTime){

        boolean result = false;

        try {

            ValueOperations<Serializable,Object> operations = redisTemplate.opsForValue();

            operations.set(key,value);

            redisTemplate.expire(key,expireTime, TimeUnit.SECONDS);//有效期

            result = true;

        }catch (Exception e){

            result = false;

            e.printStackTrace();

        }

        return result;

    }



    /**

     * 判断缓存中是否有对应的value

     * @param key

     * @return

     */

    public boolean exists(final String key){

       return redisTemplate.hasKey(key);

    }



    /**

     * 读取缓存

     * @param key

     * @return

     */

    public Object get(final String key){

        Object obj = null;

        ValueOperations<Serializable,Object> operations= redisTemplate.opsForValue();

        obj =  operations.get(key);

        return obj;

    }



    /**

     * 删除对应的value

     * @param key

     * @return

     */

    public boolean remvoe(final String key){

        if(exists(key)){

            Boolean delete = redisTemplate.delete(key);

            return delete;

        }

        return false;

    }





}

二.自定义 AutoIdempotent

自定义一个注解,定义此注解的主要目的是把它添加在需要实现幂等的方法上,凡是某个方法注解了它,都会实现自动幂等。后台利用反射如果扫描到这个注解,就会处理这个方法实现自动幂等,使用元注解 ElementType.METHOD 表示它只能放在方法上,etentionPolicy.RUNTIME 表示它在运行时

package com.yxkj.springboot_redis_interceptor.annotion;



import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;



@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface AutoIdempotent {

}

三. token 创建和检验

1.token服务接口

我们新建一个接口,创建 token 服务,里面主要是两个方法,一个用来创建 token,一个用来验证 token。创建 token 主要产生的是一个字符串,检验 token 的话主要是传达 request 对象,为什么要传 request 对象呢?主要作用就是获取 header 里面的 token, 然后检验,通过抛出的 Exception 来获取具体的报错信息返回给前端

public interface TokenService {



    /**

     * 创建token

     * @return

     */

    String createToken();



    /**

     * 检验token的合法性

     * @param request

     * @return

     * @throws Exception

     */

    boolean checkToken(HttpServletRequest request) throws Exception;

}

2.token 的服务实现类

token 引用了 redis 服务,创建 token 采用随机算法工具类生成随机 uuid 字符串, 然后放入到 redis 中 (为了防止数据的冗余保留, 这里设置过期时间为 10000 秒, 具体可视业务而定),如果放入成功,最后返回这个 token 值。checkToken 方法就是从 header 中获取 token 到值 (如果 header 中拿不到,就从 paramter 中获取),如若不存在, 直接抛出异常。这个异常信息可以被拦截器捕捉到,然后返回给前端。