redis 判断存在性_springboot + redis + 注解 + 拦截器 实现接口幂等性校验

提醒:后面有些图片模糊,请点击原文查看清晰图片

一、概念

75c8a01c1806d5ca1e5e1abe368d1d12.png

幂等性, 通俗的说就是一个接口, 多次发起同一个请求, 必须保证操作只能执行一次

比如:

订单接口, 不能多次创建订单

支付接口, 重复支付同一笔订单只能扣一次钱

支付宝回调接口, 可能会多次回调, 必须处理重复回调

普通表单提交接口, 因为网络超时等原因多次点击提交, 只能成功一次

等等

二、常见解决方案

75c8a01c1806d5ca1e5e1abe368d1d12.png

唯一索引 -- 防止新增脏数据

token机制 -- 防止页面重复提交

悲观锁 -- 获取数据的时候加锁(锁表或锁行)

乐观锁 -- 基于版本号version实现, 在更新数据那一刻校验数据

分布式锁 -- redis(jedis、redisson)或zookeeper实现

状态机 -- 状态变更, 更新数据时判断状态

三、本文实现

75c8a01c1806d5ca1e5e1abe368d1d12.png

本文采用第2种方式实现, 即通过redis + token机制实现接口幂等性校验

四、实现思路

75c8a01c1806d5ca1e5e1abe368d1d12.png

为需要保证幂等性的每一次请求创建一个唯一标识token, 先获取token, 并将此token存入redis, 请求接口时, 将此token放到header或者作为请求参数请求接口, 后端接口判断redis中是否存在此token:

如果存在, 正常处理业务逻辑, 并从redis中删除此token, 那么, 如果是重复请求, 由于token已被删除, 则不能通过校验, 返回请勿重复操作提示

如果不存在, 说明参数不合法或者是重复请求, 返回提示即可

五、项目简介

75c8a01c1806d5ca1e5e1abe368d1d12.png

  • springboot

  • redis

  • @ApiIdempotent注解 + 拦截器对请求进行拦截

  • @ControllerAdvice全局异常处理

  • 压测工具: jmeter

说明:

本文重点介绍幂等性核心实现, 关于springboot如何集成redis、ServerResponse、ResponseCode等细枝末节不在本文讨论范围之内, 有兴趣的小伙伴可以查看我的Github项目: https://github.com/wangzaiplus/springboot/tree/wxw

六、代码实现

75c8a01c1806d5ca1e5e1abe368d1d12.png

pom

<dependency>    <groupId>redis.clientsgroupId>    <artifactId>jedisartifactId>    <version>2.9.0version>dependency><dependency>    <groupId>org.projectlombokgroupId>    <artifactId>lombokartifactId>    <version>1.16.10version>dependency>

JedisUtil

@Component@Slf4jpublic class JedisUtil {    @Autowired    private JedisPool jedisPool;    private Jedis getJedis() {        return jedisPool.getResource();    }    /**     * 设值     *     * @param key     * @param value     * @return     */    public String set(String key, String value) {        Jedis jedis = null;        try {            jedis = getJedis();            return jedis.set(key, value);        } catch (Exception e) {            log.error("set key:{} value:{} error", key, value, e);            return null;        } finally {            close(jedis);        }    }    ..... }

自定义注解@ApiIdempotent

/** * 在需要保证 接口幂等性 的Controller的方法上使用此注解 */@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface ApiIdempotent {}

ApiIdempotentInterceptor拦截器

/** * 接口幂等性拦截器 */public class ApiIdempotentInterceptor implements HandlerInterceptor {    @Autowired    private TokenService tokenService;    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {        if (!(handler instanceof HandlerMethod)) {            return true;        }        HandlerMethod handlerMethod = (HandlerMethod) handler;        Method method = handlerMethod.getMethod();        ApiIdempotent methodAnnotation = method.getAnnotation(ApiIdempotent.class);        if (methodAnnotation != null) {            check(request);// 幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示        }        return true;    }    private void check(HttpServletRequest request) {        tokenService.checkToken(request);    }    @Override    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {    }    @Override    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {    }}

TokenServiceImpl

@Servicepublic class TokenServiceImpl implements TokenService {    private static final String TOKEN_NAME = "token";    @Autowired    private JedisUtil jedisUtil;    @Override    public ServerResponse createToken() {        String str = RandomUtil.UUID32();        StrBuilder token = new StrBuilder();        token.append(Constant.Redis.TOKEN_PREFIX).append(str);        jedisUtil.set(token.toString(), token.toString(), Constant.Redis.EXPIRE_TIME_MINUTE);        return ServerResponse.success(token.toString());    }    @Override    public void checkToken(HttpServletRequest request) {        String token = request.getHeader(TOKEN_NAME);        if (StringUtils.isBlank(token)) {// header中不存在token            token = request.getParameter(TOKEN_NAME);            if (StringUtils.isBlank(token)) {// parameter中也不存在token                throw new ServiceException(ResponseCode.ILLEGAL_ARGUMENT.getMsg());            }        }        if (!jedisUtil.exists(token)) {            throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getMsg());        }        Long del = jedisUtil.del(token);        if (del <= 0) {            throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getMsg());        }    }}

TestApplication

@SpringBootApplication@MapperScan("com.wangzaiplus.test.mapper")public class TestApplication  extends WebMvcConfigurerAdapter {    public static void main(String[] args) {        SpringApplication.run(TestApplication.class, args);    }    /**     * 跨域     * @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);    }    @Override    public void addInterceptors(InterceptorRegistry registry) {        // 接口幂等性拦截器        registry.addInterceptor(apiIdempotentInterceptor());        super.addInterceptors(registry);    }    @Bean    public ApiIdempotentInterceptor apiIdempotentInterceptor() {        return new ApiIdempotentInterceptor();    }}

OK, 目前为止, 校验代码准备就绪, 接下来测试验证

七、测试验证

获取token的控制器TokenController

@RestController@RequestMapping("/token")public class TokenController {    @Autowired    private TokenService tokenService;    @GetMapping    public ServerResponse token() {        return tokenService.createToken();    }}

TestController, 注意@ApiIdempotent注解, 在需要幂等性校验的方法上声明此注解即可, 不需要校验的无影响

@RestController@RequestMapping("/test")@Slf4jpublic class TestController {    @Autowired    private TestService testService;    @ApiIdempotent    @PostMapping("testIdempotence")    public ServerResponse testIdempotence() {        return testService.testIdempotence();    }}
  1. 获取token

11b49fbfba17e91eed24396e0c245597.png

查看redis

30485dacbab82ee67c05c2c2e61d28f0.png

  1. 测试接口安全性: 利用jmeter测试工具模拟50个并发请求, 将上一步获取到的token作为参数

ba505a7b2bdb21aa6505253e82874c20.png

  1. header或参数均不传token, 或者token值为空, 或者token值乱填, 均无法通过校验, 如token值为"abcd"

    ad5a4bf731055c2791ce5df4a41b0dda.png

八、注意点(非常重要)

99adc7c86e7695c88b2e17d08ae234c4.png

上图中, 不能单纯的直接删除token而不校验是否删除成功, 会出现并发安全性问题, 因为, 有可能多个线程同时走到第46行, 此时token还未被删除, 所以继续往下执行, 如果不校验jedisUtil.del(token)的删除结果而直接放行, 那么还是会出现重复提交问题, 即使实际上只有一次真正的删除操作, 下面重现一下

稍微修改一下代码:

7d506f2f8a78b6f638cd2b1777c36305.png

再次请求

ec6fa1f9a65c460eaf2d01b0af791294.png

再看看控制台

f62083f4a839fd083cbb035a74d3b4e3.png

虽然只有一个真正删除掉token, 但由于没有对删除结果进行校验, 所以还是有并发问题, 因此, 必须校验

九、总结

其实思路很简单, 就是每次请求保证唯一性, 从而保证幂等性, 通过拦截器+注解, 就不用每次请求都写重复代码, 其实也可以利用spring aop实现, 无所谓

如果小伙伴有什么疑问或者建议欢迎提出

Github
https://github.com/wangzaiplus/springboot/tree/wxw

作者:wangzaiplus

链接:https://www.jianshu.com/p/6189275403ed

【推荐阅读】

数据库之架构:主备+分库?主从+读写分离?

(完)

c7342c3c6590082d02ea4e30211efa96.png

(java思维导图)

长按关注,每天java一下,成就架构师

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/496732.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

汽车行业最大创新仍未到来,四大力量将重塑未来汽车新纪元

来源&#xff1a; 资本实验室 作者&#xff1a;王进自第一辆福特“T”型车问世以来&#xff0c;汽车行业已经诞生了众多层出不穷、持续进化的创新成果。例如&#xff0c;制造商不断创造了新的车体风格&#xff0c;拓展了新的市场区隔&#xff0c;改进了自动换档和动力转向系…

echarts柱形图x轴y轴互换_数控机床在加工零件时,突然出现X、Y、Z轴失控?如何处理...

数控机床现在广泛应用于单品种大批量的零件加工中&#xff0c;由于稳定性强、精度高、效率高&#xff0c;取代了原来的普通机床。同时数控铣床、数控车床被大规模的配置到各产品自动化生产线上&#xff0c;实现了自动化无入管理。但在生产中由于数控机床的伺服系统出现故障&…

python beautiful soup库的用法

Python 爬虫利器二 之 Beautiful Soup 的用法&#xff1a;http://cuiqingcai.com/1319.html Beautiful Soup 4.2.0 文档&#xff1a;https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html Python3 --- BeautifulSoup --- 节点选择器&#xff1a;https://www.…

Java并发编程实战~原子类

对于简单的原子性问题&#xff0c;还有一种无锁方案&#xff0c;先看看如何利用原子类解决累加器问题。 public class Test {AtomicLong count new AtomicLong(0);public void add10K() {int idx 0;while(idx < 10000) {count.getAndIncrement();}}} 无锁方案相对互斥锁…

著名物理学家斯蒂芬•霍金去世,他曾告诫人类要学会避免人工智能可能的风险

据多家媒体报道&#xff0c;著名的英国物理学家斯蒂芬霍金于3 月 14 日去世&#xff0c;享年 76 岁。霍金教授的孩子露西&#xff0c;罗伯特和蒂姆发表了声明确认了这一消息。斯蒂芬威廉霍金(Stephen William Hawking)&#xff0c;1942年1月8日出生于英国牛津&#xff0c;英国剑…

电路板上的插头怎么拔下来_空调插头一直不拔费电吗?实测一周竟然发现了真相!...

随着气温的不断走低&#xff0c;全国绝大部分城市已经进入了真正意义上的秋天。那么&#xff0c;经过了酷热一夏&#xff0c;你家的空调电源插头拔掉了吗? 说道空调&#xff0c;想必大家都知道这“家伙”可是耗电大户。尤其在七八月份&#xff0c;几乎都是24小时不停机在运行。…

C++ stringstream

参考&#xff1a;http://www.usidcbbs.com/read-htm-tid-1898.html 常见格式串 %% 印出百分比符号&#xff0c;不转换。 %c 整数转成对应的 ASCII 字元。 %d 整数转成十进位。 %f 倍精确度数字转成浮点数。 %o 整数转成八进位。 %s 整数转成字符串。 %x 整数转成小写十六进位…

Java并发编程实战~思维导图

#原图 System.out.println("https://www.processon.com/view/link/61a235be1efad425fd6ff5f6")

中国“脑计划”研究正在悄然布局

来源&#xff1a;中国科学报 作者&#xff1a;黄辛 作为“十三五”上海科研发展的重点&#xff0c;一张神奇的脑计划研究“地图”正在悄然加紧布局。日前&#xff0c;在教育部科技司、国家自然科学基金委员会政策局、华东师范大学主办的“交叉融合的教育科学基础研究”研讨会上…

JDBC链接SQLServer2005 Express

SQLServer2005的数据库链接的jar包可以到微软网站上去下载&#xff0c;驱动和链接字符串如下&#xff1a; 1: String driver "com.microsoft.sqlserver.jdbc.SQLServerDriver"; 2: String url "jdbc:sqlserver://localhost:1433;databaseNamepubs"; SQLS…

dnf时装预览怎么打开_DNF:史上最好看时装上线,大佬已经玩疯,全服都是真香的气息...

大家好&#xff01;我是风柜君&#xff0c;这次又是我来给大家带来DNF游戏内外的趣事&#xff0c;希望各位喜欢。DNF国服将在6月18日迎来第11周年庆&#xff0c;而在此之前&#xff0c;国服在6月6日的时候已经更新了一波周年庆预热活动。这次的更新除了普雷妮普通副本以外&…

深度解析:AWS、谷歌云、IBM Cloud和微软 Azure四巨头2018将会有哪些布局?

来源&#xff1a; IDC圈近来&#xff0c;公司规模已经不再是企业选择云服务商的重要因素&#xff0c;市场对云服务商优劣的判断有了多种标准。企业对全球一些大型云计算服务商&#xff08;例如亚马逊AWS&#xff0c;谷歌云平台&#xff0c;IBM Cloud和微软 Azure等&#xff09;…

用法 stl_C++STL 容器篇

前言上一章节主要是详细介绍了C泛型编程基础&#xff0c;不清楚的可以回顾一下哦。本章节主要针对于CSTL(标准模板类库)做个详细介绍。标准模板类库也就是别人写的模板类&#xff0c;主要内容是各种数据结构的封装&#xff0c;以及常用算法。暂时分三个章节介绍&#xff0c;本章…

超级强大的 vim 配置(vimplus)

From : http://www.cnblogs.com/highway-9/p/5984285.html From : http://www.cnblogs.com/ma6174/archive/2011/12/10/2283393.html From : http://www.cnblogs.com/youxia/p/linux002.html From : http://blog.csdn.net/namecyf/article/details/7787479 最近在重新配置Vim&a…

谷歌将屏蔽一切与加密货币相关的广告 6月正式生效

来源&#xff1a;腾讯证券北京时间3月14日下午消息&#xff0c;据外媒报道&#xff0c;谷歌开始对与加密货币相关的广告的进行打击。谷歌可持续广告业务负责人斯科特-斯宾塞&#xff08;Scott Spencer&#xff09;在接受CNBC采访时称&#xff0c;该公司正在调整与金融服务相关的…

ARM体系结构与汇编指令

可编程器件的特点 • CPU在固定频率的时钟控制下节奏运行。 • CPU可以通过总线读取外部存储设备中的二进制指令集&#xff0c;然后解码执行。 • 这些可以被CPU解码执行的二进制指令集是CPU设计的时候确定的&#xff0c;是CPU的设计者&#xff08;ARM公司&#xff09;定义的&a…

微软宣布在机器翻译方面取得突破,中翻英可达人类水平

来源&#xff1a;猎云网一组微软研究团队于本周三宣布&#xff0c;他们已经创造了第一个能够将中文新闻翻译成英文的机器翻译系统&#xff0c;精确度与人类译者一致。该公司表示&#xff0c;它通过来自各种在线报纸的约2000条语句样本对这一系统进行了反复测试&#xff0c;并将…

pla3d打印材料密度_3D打印金属材料模型过程中常见问题

在金属3D打印过程中&#xff0c;可能会出现大量设备操作者试图避免的问题&#xff0c;包括孔隙、残余应力、致密度、翘曲、裂纹及表面光洁度等。下面我们来谈谈这几种常出现的问题。1、表面光洁度在金属3D打印件被使用或放置在展柜之前&#xff0c;它已经经历了大量类似CNC加工…

Java并发编程实战~ThreadLocal

ThreadLocal 的使用方法 static class SafeDateFormat {// 定义 ThreadLocal 变量static final ThreadLocal<DateFormat>tl ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));static DateFormat get(){return tl.get();} }// …

智慧停车介绍、产业链市场全透析

来源&#xff1a;传感器技术最近&#xff0c;Goodwin调查发现&#xff0c;在城市地区&#xff0c;接近30%的交通拥堵源自于司机寻找停车位。据称&#xff0c;到2020年&#xff0c;将会有20亿的汽车在公路上跑着&#xff0c;这就意味着&#xff0c;届时汽车的数量将比目前多了7.…