接口交易实现幂等性4种方式

如何实现幂等性,通常实现手段有以下几点:

  1. 数据库建立唯一索引,可以保证插入数据库的只有一条数据;
  2. Token机制,每次接口请求前先获取一个token,然后再下次请求的时候在header体中加上这个token,然后进行后天验证,如果验证通过删除token,下次请求再次判断token;
  3. 悲观锁和乐观锁,悲观锁可以保证每次for update的时候,其他sql无法update数据(在数据库引擎是innodb的是时候,select的条件必须为唯一索引,方式锁全表);
  4. 先查询后判断,首选通过查询数据库是否存在数据,如果存在,说明已经请求过了,直接拒绝请求,如果没存在,就说明是第一次,直接放行。
    在这里插入图片描述
    一、搭建redis的服务API
    1、首先是搭建redis服务器
    2、引入springboot中到redis的starter中,或者Spring封装的jedis也可以。后面主要用到的api就是它的set方法和exists方法,这里我们使用Springboot封装好的redisTempate。
    创建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) {
            e.printStackTrace();
        }
        return result;
    }


    /**
     * 写入缓存,带超时失效
     *
     * @param key
     * @param value
     * @param expireTime
     * @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, expireTime);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 检查key是否存在
     *
     * @param key
     * @return
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 读取缓存
     *
     * @param key
     * @return
     */
    public Object get(final String key) {
        Object result = null;
        try {
            ValueOperations operations = redisTemplate.opsForValue();
            result = operations.get(key);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 删除key
     *
     * @return
     */
    public boolean remove(final String key) {
        if (exists(key)) {
            redisTemplate.delete(key);
            return true;
        }
        return false;
    }

    /**
     * redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

}

添加注解类:


/**
 * @author Administrator
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckRedo {
    /**
     * 默认值
     * @return
     */
    String value() default "";
}

自定义拦截器,对自定义注解的扫描处理加入拦截器:

@Component
public class AutoIdInterceptor 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;
        Method method = handlerMethod.getMethod();
        CheckRedo checkRedo = method.getAnnotation(CheckRedo.class);
        if (checkRedo!=null){
            //幂等性校验
            return tokenService.checkToken(request);
        }
        return true;
    }
}

将拦截器加入webconfiguration:

@Component
public class WebConfiguration implements WebMvcConfigurer{

    @Resource
    private AutoIdInterceptor autoIdInterceptor;

    /**
     * 添加拦截器
     *
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(autoIdInterceptor);
    }
}

token判断接口定义

public interface TokenService {

    /**
     * 创建token
     * @return
     */
    public String creatToken();


    /**
     *检查token请求
     * @return
     */
    public boolean checkToken(HttpServletRequest  request) throws Exception;
}

具体接口实现类,处理判断逻辑:

@Component
public class TokenServiceImpl implements TokenService {

    @Autowired
    RedisService redisService;

    @Override
    public String creatToken() {
        String tokenStr = null;
        try {
            String uuid = UUID.randomUUID().toString();
            StringBuilder sb = new StringBuilder();
            tokenStr = sb.append(Constant.redis_prefix).append(uuid).toString();
            if (redisService.set(tokenStr, tokenStr)) {
                return tokenStr;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return tokenStr;
    }

    @Override
    public boolean checkToken(HttpServletRequest request) throws Exception {
        String token = request.getHeader(Constant.redis_prefix);
        if (StringUtils.isEmpty(token)) {
            token = request.getParameter(Constant.redis_prefix);
            if (StringUtils.isEmpty(token)) {
                throw new Exception("token 不能为空或者为null");
            }
        }

        if (!redisService.exists(token)) {
            throw new Exception("redis 中不存在此 token:" + token);
        }

        boolean remove = redisService.remove(token);

        if (!remove) {
            throw new Exception("redis 中删除key失败");
        }
        return true;
    }
}

、Controller 控制层处理

/**
 * @author zhengzheng046
 */
@RestController
public class BusinessController {

    @Autowired
    TokenService tokenService;

    @GetMapping("/getToken")
    public String getToken() {
        String token = tokenService.creatToken();
        if (!StringUtils.isEmpty(token)) {
            System.out.println("token:"+token);
            return token;
        }
        return null;
    }


    @CheckRedo
    @PostMapping("/checkToken")
    public String checkToken() {
        System.out.println("进入方法,测试完成");
        return "success";
    }
}

总结:
本篇文章通过Springboot+redis实现token获取和判断是否存在,从而实现接口的幂等性判断。

发布了21 篇原创文章 · 获赞 4 · 访问量 505

猜你喜欢

转载自blog.csdn.net/weixin_39617728/article/details/105006615
今日推荐