范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

SpringBoot自定义注解AOPredis实现防接口重复提交,概念到实战

  一、前言
  在面试中,经常会有一道经典面试题,那就是: 怎么防止接口重复提交?
  小编也是背过的,好几种方式,但是一直没有实战过,做多了管理系统,发现这个事情真的没有过多的重视。
  最近在测试过程中,发现了多次提交会保存两条数据,进而导致程序出现问题!
  问题已经出现我们就解决一下吧!!
  本次解决是对于高并发不高的情况,适用于一般的管理系统,给出的解决方案!!高并发的还是建议加分布式锁!!
  下面我们来聊聊幂等性是什么? 二、什么是幂等性
  接口幂等性就是用户对于 同一操作  发起的一次请求或者多次请求  的结果是一致的  ,不会因
  为多次点击而产生了副作用;
  比如说经典的支付场景:用户购买了商品支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了条,这就没有保证接口的幂等性;
  可谓:商家美滋滋,买家骂咧咧!!
  防接口重复提交,这是必须要做的一件事情!! 三、REST风格与幂等性
  以常用的四种来分析哈!
  REST
  是否支持幂等
  SQL例子
  GET
  是
  SELECT * FROM table WHER id = 1
  PUT
  是
  UPDATE table SET age=18 WHERE id = 1
  DELETE
  是
  DELETE FROM table WHERE id = 1
  POST
  否
  INSERT INTO table (id,age) VALUES(1,21)
  所以我们要解决的就是 POST  请求!四、解决思路
  大概主流的解决方案: token机制(前端带着在请求头上带着标识,后端验证) 加锁机制 数据库悲观锁(锁表) 数据库乐观锁(version号进行控制) 业务层分布式锁(加分布式锁redisson) 全局唯一索引机制 redis的set机制 前端按钮加限制
  小编的解决方案就是redis的set机制!
  同一个用户,任何POST保存相关的接口,1s内只能提交一次。
  完全使用后端来进行控制,前端可以加限制,不过体验不好!
  后端通过自定义注解,在需要防幂等接口上添加注解,利用AOP切片,减少和业务的耦合!
  在切片中获取用户的 token、user_id、url  构成redis的唯一key!
  第一次请求会先判断key是否存在,如果不存在,则往redis添加一个主键key,设置过期时间;
  如果有异常会主动删除key,万一没有删除失败,等待1s,redis也会自动删除,时间误差是可以接受的!
  第二个请求过来,先判断key是否存在,如果存在,则是重复提交,返回保存信息!! 五、实战
  SpringBoot版本为 2.7.4  1. 导入依赖     org.springframework.boot     spring-boot-starter-data-redis       org.projectlombok     lombok     1.18.2       org.springframework.boot     spring-boot-starter-aop       org.springframework.boot     spring-boot-starter-web        com.alibaba     druid-spring-boot-starter     1.1.16        org.springframework.boot     spring-boot-starter-jdbc         mysql     mysql-connector-java        com.baomidou     mybatis-plus-boot-starter     3.5.1       org.springframework.boot     spring-boot-starter-test     test  2. 编写ymlserver:   port: 8087  spring:   redis:     host: localhost     port: 6379     password: 123456   datasource:     #使用阿里的Druid     type: com.alibaba.druid.pool.DruidDataSource     driver-class-name: com.mysql.cj.jdbc.Driver     url: jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC     username: root     password: 3. redis序列化/**  * @author wangzhenjun  * @date 2022/11/17 15:20  */ @Configuration public class RedisConfig {      @Bean     @SuppressWarnings(value = { "unchecked", "rawtypes" })     public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory)     {         RedisTemplate template = new RedisTemplate<>();         template.setConnectionFactory(connectionFactory);         Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);          // 使用StringRedisSerializer来序列化和反序列化redis的key值         template.setKeySerializer(new StringRedisSerializer());         template.setValueSerializer(serializer);          // Hash的key也采用StringRedisSerializer的序列化方式         template.setHashKeySerializer(new StringRedisSerializer());         template.setHashValueSerializer(serializer);          template.afterPropertiesSet();         return template;     } } 4. 自定义注解/**  * 自定义注解防止表单重复提交  * @author wangzhenjun  * @date 2022/11/17 15:18  */ @Target(ElementType.METHOD) // 注解只能用于方法 @Retention(RetentionPolicy.RUNTIME) // 修饰注解的生命周期 @Documented public @interface RepeatSubmit {      /**      * 防重复操作过期时间,默认1s      */     long expireTime() default 1; } 5. 编写切片
  异常信息大家换成自己想抛的异常,小编这里就没有详细划分异常,就是为了写博客而记录的不完美项目哈!! /**  * @author wangzhenjun  * @date 2022/11/16 8:54  */ @Slf4j @Component @Aspect public class RepeatSubmitAspect {      @Autowired     private RedisTemplate redisTemplate;     /**      * 定义切点      */     @Pointcut("@annotation(com.example.demo.annotation.RepeatSubmit)")     public void repeatSubmit() {}      @Around("repeatSubmit()")     public Object around(ProceedingJoinPoint joinPoint) throws Throwable {          ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder                 .getRequestAttributes();         HttpServletRequest request = attributes.getRequest();         Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();         // 获取防重复提交注解         RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);         // 获取token当做key,小编这里是新后端项目获取不到哈,先写死         // String token = request.getHeader("Authorization");         String tokenKey = "hhhhhhh,nihao";         if (StringUtils.isBlank(token)) {             throw new RuntimeException("token不存在,请登录!");         }         String url = request.getRequestURI();         /**          *  通过前缀 + url + token 来生成redis上的 key          *  可以在加上用户id,小编这里没办法获取,大家可以在项目中加上          */         String redisKey = "repeat_submit_key:"                 .concat(url)                 .concat(tokenKey);         log.info("==========redisKey ====== {}",redisKey);          if (!redisTemplate.hasKey(redisKey)) {             redisTemplate.opsForValue().set(redisKey, redisKey, annotation.expireTime(), TimeUnit.SECONDS);             try {                 //正常执行方法并返回                 return joinPoint.proceed();             } catch (Throwable throwable) {                 redisTemplate.delete(redisKey);                 throw new Throwable(throwable);             }         } else {             // 抛出异常             throw new Throwable("请勿重复提交");         }     } } 6. 统一返回值@Data @NoArgsConstructor @AllArgsConstructor public class Result {     private Integer code;      private String msg;      private T data;      //成功码     public static final Integer SUCCESS_CODE = 200;     //成功消息     public static final String SUCCESS_MSG = "SUCCESS";      //失败     public static final Integer ERROR_CODE = 201;     public static final String ERROR_MSG = "系统异常,请联系管理员";     //没有权限的响应码     public static final Integer NO_AUTH_COOD = 999;      //执行成功     public static  Result success(T data){         return new Result<>(SUCCESS_CODE,SUCCESS_MSG,data);     }     //执行失败     public static  Result failed(String msg){         msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;         return new Result(ERROR_CODE,msg,"");     }     //传入错误码的方法     public static  Result failed(int code,String msg){         msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;         return new Result(code,msg,"");     }     //传入错误码的数据     public static  Result failed(int code,String msg,T data){         msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;         return new Result(code,msg,data);     } } 7. 简单的全局异常处理
  这是残缺版,大家不要模仿!! /**  * @author wangzhenjun  * @date 2022/11/17 15:33  */ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler {      @ExceptionHandler(value = Throwable.class)     public Result handleException(Throwable throwable){         log.error("错误",throwable);         return Result.failed(500, throwable.getCause().getMessage());     } } 8. controller测试/**  * @author wangzhenjun  * @date 2022/10/26 16:51  */ @RestController @RequestMapping("/test") public class TestController {      @Autowired     private SysLogService sysLogService;      // 默认1s,方便测试查看,写10s     @RepeatSubmit(expireTime = 10)     @PostMapping("/saveSysLog")     public Result saveSysLog(@RequestBody SysLog sysLog){         return Result.success(sysLogService.saveSyslog(sysLog));     } } 9. service/**  * @author wangzhenjun  * @date 2022/11/10 16:45  */ @Service public class SysLogServiceImpl implements SysLogService {     @Autowired     private SysLogMapper sysLogMapper;     @Override     public int saveSyslog(SysLog sysLog) {         return sysLogMapper.insert(sysLog);     } } 六、测试1. postman进行测试
  输入请求:
  http://localhost:8087/test/saveSysLog
  请求参数:{     "title":"你好",     "method":"post",     "operName":"我是测试幂等性的" }
  发送请求两次:
  在这里插入图片描述 2. 查看数据库
  只会有一条保存成功!
  在这里插入图片描述 3. 查看redisKey
  在10s会自动删除,就可以在次提交!
  在这里插入图片描述
  4. 控制台
  在这里插入图片描述 七、总结
  这样就解决了幂等性问题,再也不会有错误数据了,减少了一个bug提交!这是一个都要重视的问题,必须要解决,不然可能会出现问题。
  完结撒花,如果对你有帮助,还请点个关注哈!!你的支持是我写作的动力!!!
  可以看下一小编的微信公众号,和网站文章首发看,欢迎关注,一起交流哈!!

369直播选出世一上人选,透露JDG想要进决赛要看中路8月16日,JDG上单369在直播中和网友们聊了很多,他谈到了世一上的人选,以及目前战队状态。弹幕谁是世界第一上单?369目前来看感觉是T1的上路Zeus,因为他打的很好,而且他还红包雨即将二次抵达!坦克世界国服周年庆典狂欢等你参加八月的狂欢,是由坦克世界国服820周年庆典与全球12周年纪念活动组成的。活动精彩奖品丰富,吸引了无数玩家积极参与,用炮火与热情为游戏庆生!国服820周年庆典狂欢盛启坦克世界国服824年来腾讯研发投入超1500亿元ampampquot两条腿ampampquot一路飞奔8月17日下午,腾讯控股发布2022年度第二季度财报,收入1340亿元,同比下滑3,净利润186亿元,同比下滑56。尽管营收盈利形势紧张,但腾讯在研发投入上并未缩水,而是在继续加码腾讯代理协议到期,战争雷霆国服宣布将于10月17日停运IT之家8月17日消息,战争雷霆WarThunder发布公告称由于腾讯与战争雷霆游戏开发商的代理协议即将到期,腾讯将于2022年10月17日1200正式停止战争雷霆在中国大陆地区的新白娘子传奇开播三十年,64岁小青美貌依旧,不见赵雅芝叶童近日,在新白娘子传奇开播三十周年,小青陈美琪在社交网站上晒出了探望夏祖辉夫妇的最新近照,并配文表示我们的30周年,和夏导演夫妇聚会,导演好开心,笑逐颜开!已经64岁的陈美琪素颜出镜曝江疏影与已婚富豪约会,本人不畏流言,空降超话发声安抚粉丝8月16日,娱记张小寒再次放出大瓜,她更新了一则漫画,爆料一位出演都市剧出名的知性女星与已婚富豪在海南某酒店秘密约会,这位富豪还背着妻子送给知性女星价值千万的珠宝,消息曝光后引来吃养老金上涨金额到账了,医保返还金怎么还没有到账?还会上涨吗?大家好,很高兴见到大家,咱们这期内容就来讲解一下,8月份,养老金的上涨金额到账了,那医保返还金的上涨金额怎么还没到账?还会上涨吗?对于部分退休人员来说,8月份的养老金比之前都提高了文案秋天,可真是个温柔的季节头号周刊秋温暖治愈系文字01。秋天,可真是个温柔的季节秋风从树木枝桠缝隙里吹拂到脸上时,生活突然开始变得明朗而又美好。02。把夏天的遗憾都写在秋天将要落下的树叶上吧,他们在土里呆上盘点将手机变成相机监视器的几种方法Hi,我是溢图科技。因为手机行业的充分竞争,手机屏幕不仅亮度高色域广,新一代产品在色准方面也有了很大提升。使用手机作为相机的监视器,不仅可以充分利用手中设备,还能实现截屏录屏直播等新能源汽车下乡,背后有何更广阔的遐想?敲锣打鼓降一毛,无声无息涨一块,面对年内油价在暴跌与微涨间的反复横跳,不少燃油车车主叫苦不迭。油价的高位运行,也使得更多人把目光投向了新能源汽车。2022年上半年,即便在受到疫情反消费降级,能力升级产品能力升级,打胜仗才是最好的团建。刚刚,海底捞发布了盈利预警,提示上半年营收下滑最高17,亏损超过2亿元。尽管海底捞用了很多方法试图让消费者回到店里,但这个效果并不明显。一家商业
揭漏足球博彩公司操纵比赛的秘密,大型赛事会有假球吗?谣零零计划很多人都会听信谣言说假球黑幕啥的,今天看到了这个问答,我感觉很有意思,我感觉我对这个问题有些许发言权,因为我一个月可以告诉你十多场比赛的盘,当然这是在至少四年前,后面渐渐我眼中的法网一支激越的曲子一幅多彩的油画一首励志的诗歌在职业网球赛事中,没有哪一站巡回赛能超越四大满贯。在四大满贯中,没有哪一个比法网更具艺术特质。在我的眼里,法网呈现的不仅是一场场激动人心的网球比赛,更是集音乐绘画诗歌雕塑于一体的艺民营超市第一股步步高日子有点难步步高集团持续亏损一季度末总负债342亿,所持步步高股份六成已质押。作者快速消费品精英俱乐部来源财富质点银箭财观江南都市报对于近日,社交媒体上抖音小红书微博上都频繁出现的关于步步高过早宵夜,美食在铁机路日夜轮转,人们把日子过得活色生香重走铁机路发现美食沸腾的世界铁机路有多长?连住在铁机路的人都不知道。我在靠近武昌江滩的铁机路住了7年。对孤独的树小白灯塔和汽渡码头的故事如数家珍。却对铁机路另一端知之甚少。直到,住诗歌李景富贺神舟十四号载人飞船发射取得圆满成功贺神舟十四号载人飞船发射取得圆满成功作者李景富北京时间2022年6月5日10时44分07秒,搭载神舟十四号载人飞船的长征二号F遥十四运载火箭在酒泉卫星发射中心点火发射,约577秒后十年OTC医药销售业务员工作分享因为已经决定做点自己喜欢的事,看看书写写文章,以前没时间,现在有了也不晚,希望我自己的分享能够帮助到一些人,尤其是那些未参与工作而且想进入医药零售行业的人,就是值得。吾日三省吾身,买对不买贵,618买手机也不能瞎买,注意这3点,至少多用2年您在阅读前请点击上面的关注二字,后续会第一时间为您提供更多有价值的相关内容,感谢您的支持。绝大部分人都知道,在618期间手机的价格都会有大幅的下降,认为618买手机的时候,可以随便你见过这么酷的机械键盘吗?MelGeekmojo68,只有0次和无数次在此之前呢,我一直用的是MelGeekmojo68PLASTIC佳达隆轴机械键盘,半透小巧,最重要的是手感,那敲上去,码字简直太舒服了。这里要吐槽下MelGeek,对于我一个需要各传白云边拟参与凯乐重组,董事长梅林喊出今年有望实现销售75亿文行业观察员苍鹰文斯南文行业资深观察员胡吉洲近日,低调的湖北白酒大王白云边在行业被广泛热议。6月2日,湖北省委编办到白云边酒业调研。据白云边酒业董事长总经理梅林介绍,2022年如无释放消费潜力促进消费逐步恢复徜徉钟楼街满满活力扑面来夜晚的钟楼街人来人往。本报记者曹婷婷摄午后阳光温柔,清风徐来惬意。太原钟楼街1950西餐厅的阳台上,穿着旗袍打卡的游客仿佛置身画里,再配上身后建筑楼顶硕大的红色爱心装饰,随手一拍就美国五大军火商将总部迁往首都圈,紧挨五角大楼,方便套近乎据路透社6月7日报道,继波音公司后,美国最大的军火巨头之一,雷神公司宣布将于秋季把总部从马萨诸塞州沃尔瑟姆,迁至弗吉尼亚州阿灵顿这是五角大楼所在地。随着雷神公司的迁入,美国五大国防