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

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提交!这是一个都要重视的问题,必须要解决,不然可能会出现问题。
  完结撒花,如果对你有帮助,还请点个关注哈!!你的支持是我写作的动力!!!
  可以看下一小编的微信公众号,和网站文章首发看,欢迎关注,一起交流哈!!

18分大胜!浙江6战全胜领跑,北控末节大崩盘,马布里功亏一篑北京时间10月22日,CBA常规赛继续进行,浙江男篮迎战北控男篮,上半场两队紧咬比分,浙江只领先2分。下半场比分依旧没有拉开,两队激战到最后一节,北控在最后7分钟崩盘,分差被拉开,纳什回应休赛期闹剧这就是NBA的一部分北京时间10月22日,据NBA记者MichaelGrange报道,篮网主教练史蒂夫纳什向他描述了自己如何从夏天球队的闹剧中走出来的。纳什表示,这对他来说就是NBA的一部分,过去也曾狂胜41分,深圳队打疯了!贺希宁264神兵天降,顾全安心休息吧深圳队在休赛期续约了沈梓捷顾全2位核心球员,开始围绕他们组建队伍,同时,他们还引进了广东前队长周鹏,球队实力再度得到扩增,新赛季想要取得更好的成绩。前3战,深圳队在顾全和沈梓捷的带王涛滕哈赫占据道德制高点拿C罗杀鸡儆猴,这是他的惯用伎俩直播吧10月22日讯王涛谈到了近期曼联与C罗的事件,他表示,滕哈赫拿C罗杀鸡儆猴,绝对是个高手,希望曼联和C罗不要因为这样一位教练最终分道扬镳。王涛首先我想说的是,滕哈赫绝对是一个拳击路那么难走,为何还要坚持?学拳数年,作者接触过各种各样的拳手。职业的,体制内的,业余追梦的。不计其数目前,中国拳击运动,职业拳手仅百位左右体制内拳击发展相对还足够稳定这些时日,收到最多的问题。便是如何走下这周跃龙逆转土豆,萝卜碾压塞尔比,北爱尔兰公开赛四强诞生北京时间10月22日凌晨,斯诺克北爱尔兰公开赛四强诞生。尼尔罗伯逊,马克艾伦,麦克吉尔和周跃龙分别战胜各自对手晋级四强。首先来看四强赛的顶峰对决,也有球迷说是决赛的提前表演,现世界1米98!1米89!场均865,模板贾森基德!马布里再打造顶级后卫过去两个赛季里,北控男篮都是非常遗憾地倒在了季后赛行列之外,球队投资这么大,却在两个赛季里连续只拿到第13名的成绩,这也让北控男篮彻底地成了笑话!让人没有想到的是,进入到20222意甲直播AC米兰vs蒙扎强烈推荐意甲高清直播分享直播入口httpzb。laiqw。cnm?fromanlan18长按链接搜索直达直播页面意甲比赛23。59AC米兰vs蒙扎其实当时在意大利足球圈内,尤文图斯服用禁药原创微语我的城市静默了头条创作挑战赛我的城市静默了当我睁开眼睛,像往常一样朋友圈群友议论的声音还在我穿过马路去做核酸匆匆的行人说不出的沉重枯黄的树叶随风飘着我的城市静默了没有了车水马龙的喧嚣没有了摩肩接成年人最难的修行管住嘴俗话说嘴是剔骨刀,舌是杀人魔。嘴里说出来的,可以是让人如沐春风的温暖话语,也可以是让人厌恶至极,不堪入耳的语言,严重的还会给自己招来祸端。饭可以乱吃,但是,话如果乱说,就有可能会祸我爱秋天,秋日胜春朝每一个季节,都是可爱的。不过,比起花红柳绿的春天,我更喜欢秋天。我喜欢秋天的天高云淡,视野都分外开阔我也喜欢秋天的凉爽天气,伫立在金风里,感慨天凉好个秋我还喜欢秋天的红叶黄花,它们
罗体头版国米将与小因扎吉续约至2024年博格巴7月签约尤文直播吧6月7日讯今天都灵体育报关注了尤文和国米的转会市场。国米方面,他们已经接近从卡利亚里敲定贝拉诺瓦,并且仍在寻求引进卢卡库。另外,与马洛塔会面后,主帅小因扎吉续约后年薪将从40国际锐评丨门罗主义没有市场本届美洲峰会或将成为美国领导力在拉美地区衰落的信号。这是墨西哥一家媒体的评价。第九届美洲峰会定于6日在美国洛杉矶举行。然而,会前多个美洲国家领导人的批评或抵制,以及美方最终确定不邀尘埃落地!张常宁退役悬念揭晓,央视官宣正式回应,真相水落石出自从郎平卸任,蔡斌入主中国女排以来,关于张常宁的各种消息满天飞。因为她并没有进入新的女排名单。都知道张常宁去接受手术,治疗旧伤了。还有就是张常宁官宣另一半,准备要大婚了。总之张常宁新车文懂车帝原创陈旭明极星3首张官图发布懂车帝原创产品6月7日晚间,Polestar极星品牌正式发布全新车型极星3(Polestar3)首张官方图片,并宣布该车将于2022年10月全球五十辆宾利女车主的故事很传奇众说纷纭的猜测所谓五十辆宾利的女士原来是某国企领导张某某的夫人,该领导的确颇有振业集团官网资料显示,张某某出生于1965年10月,曾先后担任深圳市国资委办公室(信访室)主任科员,深宾利女的内裤董秘嘴里的情侣劳斯莱斯车主的表演宾利女的内裤董秘嘴里的情侣劳斯莱斯车主的表演作者李万卿今天的话题有点庸俗,见谅!看看深圳宾利女穿的内裤,一条就值好几千块,我突然赶脚我很贫穷,穷得穿不起内裤了。这几天,深圳宾利女大加军机上门挑衅我军果断出手,特鲁多找茬,中方回击,连下两道封海令加拿大国防部歪曲事实,公然指责中国军机所谓干扰加方军机。6月6日当天,中国外交部和国防部接连回击。据环球网报道,加拿大国防部发表声明,说加皇家空军CP140海上反潜巡逻机与解放军军光伏企业跑马圈地涌向扩张产能一条路今年以来,光伏行业掀起新一轮扩产竞赛,A股光伏领域上市公司先后宣布了投资总额超2500亿元的产能扩张计划。从第一批光伏龙头无锡尚德江西赛维破产,到汉能集团黯然退市,再到隆基绿能通威提升生活幸福指数,新捷达VS5VS7售价公布,诠释真实力提及捷达,想必80后90后的小伙伴们应该非常熟悉了。早些年,捷达就凭借皮实耐用的特点赢得了非常好的口碑。如今捷达在成为细分品牌独立运营后,不仅保留了德系车的精工品质,还为不同需求的神舟十四号发射,动车D2809列车撞上泥石流1神舟十四号载人飞行任务成功发射(IT之家)神舟十四号载人飞行任务于6月5日10时44分发射,飞行乘组人员确定陈冬刘洋蔡旭哲,由陈冬担任指令长。航天员陈冬执行过神舟十一号载人飞行任D2809次列车在榕江站撞上泥石流脱线2022年6月4日贵阳一列开往广州南的动车在进入黔东南榕江站前的一个隧道口撞上的突发溜坍,进入线路的泥石流造成两节车厢脱线,8人受伤,动车司机不幸遇难。这是上午1030的一起动车事