一文详解!实现springboot接口等幂性校验?安排!

   日期:2020-11-15     浏览:87    评论:0    
核心提示:一 前言本篇内容的内容是实现接口等幂次校验。二 实现方案主流的实现方案如下2.1 唯一索引给表加唯一索引,次方法最简单,当数据重复插入时,直接报SQL异常,对应用影响不大;alter table 表名 add unique(字段)示例,两个字段为唯一索引,如果出现完全一样的 order_name, create_time 就直接重复报异常;alter table `order` add unique(order_name,create_time)2.2 锁分布式锁

一 前言

本篇内容的内容是实现接口等幂次校验。

二 实现方案

主流的实现方案如下

2.1 唯一索引

给表加唯一索引,次方法最简单,当数据重复插入时,直接报SQL异常,对应用影响不大;

alter table 表名 add unique(字段)

示例,两个字段为唯一索引,如果出现完全一样的 order_name, create_time 就直接重复报异常;

alter table `order`  add unique(order_name,create_time)

2.2 锁

分布式锁也可以实现接口等幂次校验,知识追寻者有写过一篇使用redis实现分布式锁思路的一篇文件,小伙伴们可以参考下《为什么你不会redis分布式锁?因为你没看到这篇文章》

使用乐观锁(基于版本号实现),或者 悲观锁(表锁或者行锁)实现;

2.3 先查询后判断

入库时先查询是否有该数据,无插入,否则不插入;

2.4 token 机制

token 机制 也就是本篇文章的重点;大致实现思路就是 发起请求的时候先去 redis 获取 token , 将获取的token 放入 请求的hearder , 当请求到达服务端的时候拦截请求,对请求的 hearder 中的token,进行校验,如果校验通过则 放开拦截,删除token,否则 使用自定义异常返回错误信息;

三 使用redis 实现 接口等幂性校验

3.1 redis 工具类

关于 RedisTemplate 的配置可以参考知识追寻者发布的文章 《springboot集成redis(基础篇)》



@Component
public class RedisUtils {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


      
    public Boolean del(String key) {
        if (key != null && key.length() > 0) {
            return redisTemplate.delete(key);
        }else  {
            return false;
        }
    }


    

    public Object get(String key) {

        return key == null ? null : redisTemplate.opsForValue().get(key);
    }


    

    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

}

3.2 token 工具类

使用 uuid 生成 随机字符串,通过md5加密防止token被解密,保证token的唯一性与安全性;,设置过期时间为 30 秒,即在30秒内只能提交成功一次请求,根据不同的业务需求,读者们自行处理;


@Component
public class TokenUtis {

    @Autowired
    RedisUtils redisUtils;

    // token 过期时间为30秒
    private final static Long TOKEN_EXPIRE = 30L;

    private final static String TOKEN_NAME = "token";
    
    public String generateToken() {
        String uuid = UUID.randomUUID().toString();
        String token = DigestUtils.md5DigestAsHex(uuid.getBytes());
        redisUtils.set(TOKEN_NAME,token,TOKEN_EXPIRE);
        return token;
    }
    
    public boolean verifyToken(HttpServletRequest request) {
        String token = request.getHeader(TOKEN_NAME);
        // header中不存在token
        if(StringUtils.isEmpty(token)) {
           // 抛出自定义异常
            System.out.println("token不存在");
            throw new GlobleException(CodeMsg.BAD_REQUEST);
        }
        // 缓存中不出在 
        if(!redisUtils.hasKey(TOKEN_NAME)) {
            // 抛出自定义异常
            System.out.println("token已经过期");
            throw new GlobleException(CodeMsg.BAD_REQUEST);
        }
        String cachToekn = (String)redisUtils.get(TOKEN_NAME);
        if (!token.equals(cachToekn)){
            // 抛出自定义异常
            System.out.println("token校验失败");
            throw new GlobleException(CodeMsg.BAD_REQUEST);
        }
        // 移除token
        Boolean del = redisUtils.del(TOKEN_NAME);
        if (!del){
            // 抛出自定义异常
            System.out.println("token删除失败");
            throw new GlobleException(CodeMsg.BAD_REQUEST);
        }
        return true;
    }
}

3.3 注解

定义注解,使用在方法上,当控制层的方法上被注释时,表示该请求为等幂性请求;


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

}

3.4 拦截器配置

选择 前置拦截器,每次请求都校验 到达的方法上是否有等幂性注解,如果有则进行token 校验;


@Component
public class IdempotentInterceptor implements HandlerInterceptor {

    @Autowired
    private TokenUtis tokenUtis;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        // 对有Idempotent注解的方法进行拦截校验
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        Idempotent methodAnnotation = method.getAnnotation(Idempotent.class);
        if (methodAnnotation != null) {
            // token 校验
            tokenUtis.verifyToken(request);
        }
        return true;
    }


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

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

对拦截器进行url模式匹配,并注入spring容器


@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Autowired
    IdempotentInterceptor idempotentInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 拦截所有请求
        registry.addInterceptor(idempotentInterceptor).addPathPatterns("
@RestController
public class ZszxzController {

    @Autowired
    TokenUtis tokenUtis;

    @GetMapping("zszxz/token")
    public ResultPage getToken(){
        String token = tokenUtis.generateToken();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token",token);
        return ResultPage.sucess(CodeMsg.SUCESS,jsonObject);
    }

    @Idempotent
    @GetMapping("zszxz/test")
    public ResultPage testIdempotent(){
        return ResultPage.sucess(CodeMsg.SUCESS,"校验成功");
    }
}

3.6 测试

请求获取token

 

已经被消耗的token,请求时报错

 

重新获取token 请求成功

 

对于高并发请求可以使用jmeter进行测试,本篇文章也可以使用aop拦截实现;

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服