当前位置: 首页 > news >正文

SpringBoot自定义注解 + AOP+分布式Redis 防止重复提交

第一步 引入依赖pom.xml:

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.16.3</version> <!-- 使用最新版本 --></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.11.1</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.11.1</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.11.1</version></dependency></dependencies>

第二步 增加配置:

  redis:host: localhost # Redis服务器地址database: 4 # Redis数据库索引(默认为0)port: 6379 # Redis服务器连接端口password: w23456timeout: 30000ms # 连接超时时间(毫秒)

第三步 增加自定义注解:

package com.hxnwm.ny.diancan.common.annotaiton;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @ClassName SubmitLimit* @Description TODO* @Author wdj* @Date 2023/7/25 18:01* @Version*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface SubmitLimit {/*** 指定时间内不可重复提交(仅相对上一次发起请求时间差),单位毫秒* @return*/int waitTime() default 1000;/*** 指定请求头部key,可以组合生成签名* @return*/String[] customerHeaders() default {};/*** 自定义重复提交提示语* @return*/String customerTipMsg() default "";}
package com.hxnwm.ny.diancan.common.aspect;import com.hxnwm.ny.diancan.common.annotaiton.SubmitLimit;
import com.hxnwm.ny.diancan.common.lock.DistributedLockService;
import com.hxnwm.ny.diancan.common.result.Result;
import com.hxnwm.ny.diancan.common.utils.JacksonUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Objects;
/*** @ClassName SubmitLimitAspect* @Description TODO* @Author wdj* @Date 2023/7/27 9:07* @Version*/
@Order(1)
@Aspect
@Component
public class SubmitLimitAspect {private static final Logger LOGGER = LoggerFactory.getLogger(SubmitLimitAspect.class);/*** redis分割符*/private static final String REDIS_SEPARATOR = ":";/*** 默认重复提交提示语*/private static final String DEFAULT_TIP_MSG = "服务正在处理,请勿重复提交!";@Autowiredprivate DistributedLockService distributedLockService;/*** 方法调用环绕拦截*/@Around(value = "@annotation(com.hxnwm.ny.diancan.common.annotaiton.SubmitLimit)")public Object doAround(ProceedingJoinPoint joinPoint){HttpServletRequest request = getHttpServletRequest();if(Objects.isNull(request)){return Result.error(5001,"请求参数不能为空!");}//获取注解配置的参数SubmitLimit submitLimit = getSubmitLimit(joinPoint);//组合生成key,通过key实现加锁和解锁String lockKey = buildSubmitLimitKey(joinPoint, request, submitLimit.customerHeaders());//尝试在指定的时间内加锁boolean lock = distributedLockService.acquireLock(lockKey, submitLimit.waitTime());if(!lock){String tipMsg = StringUtils.isEmpty(submitLimit.customerTipMsg()) ? DEFAULT_TIP_MSG : submitLimit.customerTipMsg();return Result.error(5001,tipMsg);}try {//继续执行后续流程return execute(joinPoint);} finally {//执行完毕之后,手动将锁释放distributedLockService.releaseLock();}}/*** 执行任务* @param joinPoint* @return*/private Object execute(ProceedingJoinPoint joinPoint){try {return joinPoint.proceed();} catch (Exception e) {return Result.error(5001,e.getMessage());} catch (Throwable e) {LOGGER.error("业务处理发生异常,错误信息:",e);return Result.error(5001,"业务处理发生异常");}}/*** 获取请求对象* @return*/private HttpServletRequest getHttpServletRequest(){RequestAttributes ra = RequestContextHolder.getRequestAttributes();ServletRequestAttributes sra = (ServletRequestAttributes)ra;HttpServletRequest request = sra.getRequest();return request;}/*** 获取注解值* @param joinPoint* @return*/private SubmitLimit getSubmitLimit(JoinPoint joinPoint){MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();Method method = methodSignature.getMethod();SubmitLimit submitLimit = method.getAnnotation(SubmitLimit.class);return submitLimit;}/*** 组合生成lockKey* 生成规则:接口名+方法名+请求参数签名(对请求头部参数+请求body参数,取SHA1值)* @param joinPoint* @param request* @param customerHeaders* @return*/private String buildSubmitLimitKey(JoinPoint joinPoint, HttpServletRequest request, String[] customerHeaders){//请求参数=请求头部+请求bodyString requestHeader = getRequestHeader(request, customerHeaders);String requestBody = getRequestBody(joinPoint.getArgs());String requestParamSign = DigestUtils.sha1Hex(requestHeader + requestBody);String submitLimitKey = new StringBuilder().append(joinPoint.getSignature().getDeclaringType().getSimpleName()).append(REDIS_SEPARATOR).append(joinPoint.getSignature().getName()).append(REDIS_SEPARATOR).append(requestParamSign).toString();return submitLimitKey;}/*** 获取指定请求头部参数* @param request* @param customerHeaders* @return*/private String getRequestHeader(HttpServletRequest request, String[] customerHeaders){if (Objects.isNull(customerHeaders)) {return "";}StringBuilder sb = new StringBuilder();for (String headerKey : customerHeaders) {sb.append(request.getHeader(headerKey));}return sb.toString();}/*** 获取请求body参数* @param args* @return*/private String getRequestBody(Object[] args){if (Objects.isNull(args)) {return "";}StringBuilder sb = new StringBuilder();for (Object arg : args) {if (arg instanceof HttpServletRequest|| arg instanceof HttpServletResponse|| arg instanceof MultipartFile|| arg instanceof BindResult|| arg instanceof MultipartFile[]|| arg instanceof ModelMap|| arg instanceof Model|| arg instanceof ExtendedServletRequestDataBinder|| arg instanceof byte[]) {continue;}sb.append(JacksonUtils.toJson(arg));}return sb.toString();}
}

第四步 增加分布式服务:

package com.hxnwm.ny.diancan.common.lock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;@Component
public class DistributedLockService {// 锁的过期时间,单位秒private static final int LOCK_EXPIRE = 30;@Autowiredprivate RedissonClient redissonClient;private RLock lock;public boolean acquireLock(String lockKey,int lockExpire) {if (lockExpire<=0){lockExpire=LOCK_EXPIRE;}try {lock= redissonClient.getLock(lockKey);// 尝试获取锁,设置锁的自动过期时间为 LOCK_EXPIRE 秒boolean tryLock = lock.tryLock(lockExpire, TimeUnit.SECONDS);return tryLock;} catch (Exception e) {e.printStackTrace();Thread.currentThread().interrupt();return false;}}public void releaseLock() {lock.unlock();}
}

最后一步:使用。在控制层增加@SubmitLimit(customerHeaders = {"token"}, customerTipMsg = "正在加紧为您处理,请勿重复下单!")

 在不改变原有逻辑上增加额外的功能

    /*** 下单详情** @author knight* @time 2022/3/22 13:51*/@SubmitLimit(customerHeaders = {"token"}, customerTipMsg = "正在加紧为您处理,请勿重复下单!")@RequestMapping(value = "submit/detail", method = RequestMethod.POST)public Result submitDetail(@Validated @RequestBody CartOrderInfo cartOrderInfo) {return Result.handlerData(this.orderManage.submitDetail(cartOrderInfo));}

相关文章:

SpringBoot自定义注解 + AOP+分布式Redis 防止重复提交

第一步 引入依赖pom.xml&#xff1a; <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.16.3</version> <!-- 使用最新版本 --></dependency><dependency><groupId&g…...

3.yum安装分布式LNMP--剧本

文章目录 修改hosts创建剧本文件 修改hosts vim /etc/ansible/hosts[webservers] 192.168.242.67[dbservers] 192.168.242.68[phpservers] 192.168.242.69创建剧本文件 vim lnmp.yaml- name: nginx playhosts: webserversremote_user: rootvars:- http_port: 192.168.242.67:…...

论文笔记:Fine-Grained Urban Flow Prediction

2021 WWW 1 intro 细粒度城市流量预测 两个挑战 细粒度数据中观察到的网格间的转移动态使得预测变得更加复杂 需要在全局范围内捕获网格单元之间的空间依赖性单独学习外部因素&#xff08;例如天气、POI、路段信息等&#xff09;对大量网格单元的影响非常具有挑战性——>论…...

系统集成|第八章(笔记)

目录 第八章 进度管理8.1 主要过程8.1.1 规划进度管理8.1.2 定义活动8.1.3 排列活动顺序8.1.4 估算活动资源8.1.5 估算活动持续时间8.1.6 制定进度计划8.1.7 控制进度 8.2 注意与问题 上篇&#xff1a;第七章、范围管理 第八章 进度管理 8.1 主要过程 包括&#xff1a; 规划进…...

【分布式】分布式唯一 ID 的 几种生成方案以及优缺点snowflake优化方案

在互联网的业务系统中&#xff0c;涉及到各种各样的ID&#xff0c;如在支付系统中就会有支付ID、退款ID等。那一般生成ID都有哪些解决方案呢&#xff1f;特别是在复杂的分布式系统业务场景中&#xff0c;我们应该采用哪种适合自己的解决方案是十分重要的。下面我们一一来列举一…...

FFmpeg5.0源码阅读——av_interleaved_write_frame

摘要&#xff1a;本文主要详细描述FFmpeg中封装时写packet到媒体文件的函数av_interleaved_write_frame的实现。   关键字&#xff1a;av_interleaved_write_frame   读者须知&#xff1a;读者需要熟悉ffmpeg的基本使用。 1 基本调用流程 av_interleaved_write_frame的基本…...

力扣 70. 爬楼梯

题目来源&#xff1a;https://leetcode.cn/problems/climbing-stairs/description/ C题解&#xff08;来源代码随想录&#xff09;&#xff1a; 本质上是一道斐波那契数题。 动规五部曲&#xff1a;定义一个一维数组来记录不同楼层的状态 确定dp数组以及下标的含义。dp[i]&am…...

AVFoundation - 媒体捕捉

文章目录 注意使用 NSCameraUsageDescriptioniOS 的摄像头可能比 Mac 更多功能特性@interface Capture ()<AVCaptureFileOutputRecordingDelegate>@property (strong, nonatomic) AVCaptureSession *captureSession; @property (weak, nonatomic) AVCaptureDeviceInput *…...

【新版系统架构补充】-嵌入式技术

嵌入式微处理体系结构 冯诺依曼结构 传统计算机采用冯诺依曼结构&#xff0c;也称普林斯顿结构&#xff0c;是一种将程序指令存储器和数据存储器合并在一起的存储器结构 冯诺依曼的计算机程序和数据共用一个存储空间&#xff0c;程序指令存储地址和数据存储地址指向同一个存…...

fpga开发--蜂鸣器发出连续不同的音调

描述 使用fpga蜂鸣器连续发出do&#xff0c;re&#xff0c;mi&#xff0c;fa&#xff0c;so&#xff0c;la&#xff0c;xi七个不同的音调&#xff0c;每个音调的持续时间为0.5s。 思路 采用状态机实现音调的转化&#xff0c;当do状态持续了0.5s之后转移到re状态&#xff0c;…...

Redis 主从同步原理

一、什么是主从同步&#xff1f; 主从同步&#xff0c;就是将数据冗余备份&#xff0c;主库&#xff08;Master&#xff09;将自己库中的数据&#xff0c;同步给从库&#xff08;Slave&#xff09;。 从库可以一个&#xff0c;也可以多个&#xff0c;如图所示&#xff1a; 二…...

opencv-28 自适应阈值处理-cv2.adaptiveThreshold()

什么是自适应阈值处理? 对于色彩均衡的图像&#xff0c;直接使用一个阈值就能完成对图像的阈值化处理。但是&#xff0c;有时图像的色彩是不均衡的&#xff0c;此时如果只使用一个阈值&#xff0c;就无法得到清晰有效的阈值分割结果图像。 有一种改进的阈值处理技术&#xff…...

Java泛型5——泛型通配符

注&#xff1a;以下内容基于Java 8&#xff0c;所有代码都已在Java 8环境下测试通过 目录&#xff1a; Java泛型1——概述Java泛型2——泛型类Java泛型3——泛型接口Java泛型4——泛型方法Java泛型5——泛型通配符Java泛型6——类型擦除 什么是通配符 在Java中&#xff0c;类…...

牛客 AB25 ranko的手表 JAVA 枚举

描述 ranko 的手表坏了&#xff0c;正常应该显示 xx:xx 的形式&#xff08;4 个数字&#xff09;&#xff0c;比如下午 1 点半应该显示 13:30 &#xff0c;但现在经常会有一些数字有概率无法显示。 ranko 在 &#xfffd;1t1​ 时刻看了下时间&#xff0c;过了一段时间在 &am…...

常微分方程建模R包ecode(二)——绘制相速矢量场

本节中我们考虑一个更为复杂的常微分方程模型&#xff0c; d X C d t ν ( X A Y A ) − β ⋅ X C ⋅ ( Y C Y A ) − ( μ g ) ⋅ X C , ( 1 ) d Y C d t β ⋅ X C ⋅ ( Y C Y A ) − ( μ g ρ ) ⋅ Y C , ( 2 ) d X A d t g ⋅ X C − β ⋅ X A ⋅ ( Y C Y A …...

学习C#编写上位机的基础知识和入门步骤:

00001. 掌握C#编程语言基础和.NET框架的使用。 00002. 学习WinForm窗体应用程序开发技术&#xff0c;包括控件的使用和事件驱动编程。 00003. 熟悉基本的数据结构和算法知识&#xff0c;如链表、栈、队列等。 00004. 理解串口通信协议和通信方法&#xff0c;用于与底层硬件设…...

简单高效!低代码搭建销售自动化程序的方法与实践

在当今数字化时代&#xff0c;销售自动化成为了提高销售效率和业绩的重要手段之一。而低代码平台的兴起&#xff0c;使得搭建销售自动化程序变得更加简单和高效。本文将介绍低代码平台及其优势&#xff0c;并探讨如何利用低代码平台搭建销售自动化程序。 1、低代码平台 1&…...

第九十三回 在Flutter中mock数据

文章目录 概念介绍使用方法示例代码 我们在上一章回中介绍了"在Flutter中解析JSON数据"相关的内容&#xff0c;本章回中将介绍 如何mock数据.闲话休提&#xff0c;让我们一起Talk Flutter吧。 概念介绍 我们在本章回中介绍的mock数据主要是通过相关的代码模拟服务器…...

进程与线程的区别与联系

多进程已经可以很好的实现并发编程的效果了&#xff0c;但是仍然有一个明显的缺点&#xff1a;进程太重了&#xff0c;进程消耗的资源更多&#xff0c;速度更慢。如果进程创建销毁不频繁&#xff0c;那么还好&#xff0c;一旦需要大规模创建和销毁进程&#xff0c;开销就比较大…...

使用gadl对土地利用栅格重分类

要使用Python语言进行土地利用栅格的重分类&#xff0c;可以使用gadl库&#xff08;GDAL的Python绑定&#xff09;来实现。gadl库提供了一组功能强大的函数和类&#xff0c;可用于读取、处理和分析栅格数据。 首先&#xff0c;确保已经安装了gadl库。可以使用以下命令通过pip进…...

线程与进程的区别与联系:操作系统入门详解(含 Python 示例)

、先搞懂&#xff1a;进程与线程到底是什么&#xff1f;&#xff08;通俗类比官方定义&#xff09; 1.1 生活化类比&#xff1a;快速建立认知 如果把计算机的操作系统比作一个大型工厂&#xff1a; 进程&#xff1a;就是工厂里的一个个独立车间。每个车间有自己专属的生产资…...

保姆级教程:用YOLO+DeepSORT在UCF101-24数据集上实现实时时空动作检测

从零搭建实时时空动作检测系统&#xff1a;YOLODeepSORT实战指南 当你在篮球场边拍摄一段视频&#xff0c;能否让AI自动标记出每个球员的投篮动作&#xff1f;或者在游泳比赛中实时框选运动员的跳水瞬间&#xff1f;这就是时空动作检测技术的魅力所在——它不仅要知道"发生…...

Nunchaku FLUX.1-dev 提示词工程入门:编写高质量Prompt的实用技巧与范例

Nunchaku FLUX.1-dev 提示词工程入门&#xff1a;编写高质量Prompt的实用技巧与范例 你是不是也遇到过这种情况&#xff1a;用同一个开源大模型&#xff0c;别人生成的图片精美绝伦&#xff0c;自己生成的却总差点意思&#xff0c;要么主体不对&#xff0c;要么风格跑偏&#…...

一加手机Root后玩机指南:用Magisk Delta模块实现这些实用功能(附模块推荐)

一加手机Root后进阶玩法&#xff1a;Magisk Delta模块实战指南 当你成功为一加手机解锁BL并获取Root权限后&#xff0c;真正的玩机之旅才刚刚开始。作为一款以极客精神著称的品牌&#xff0c;一加手机在Root后的可玩性远超普通设备。本文将聚焦Magisk Delta这一强大工具&#x…...

STorM BGC V1.31硬件 + SimpleBGC源码:从零搭建三轴云台开发环境(含.Net框架避坑)

STorM BGC V1.31硬件 SimpleBGC源码&#xff1a;从零搭建三轴云台开发环境&#xff08;含.Net框架避坑&#xff09; 三轴云台作为稳定控制领域的经典应用&#xff0c;近年来在无人机、摄影设备、工业检测等领域展现出巨大潜力。STorM BGC V1.31硬件平台配合SimpleBGC开源架构&…...

告别单行代码:在Python IDLE中编写完整函数的完整指南

告别单行代码&#xff1a;在Python IDLE中编写完整函数的完整指南 对于刚接触Python的开发者来说&#xff0c;IDLE是一个既熟悉又陌生的环境。熟悉是因为它随Python安装包一起提供&#xff0c;陌生则是因为很多人仅仅把它当作一个简单的交互式Shell&#xff0c;而忽略了它作为完…...

基于OpenCV的多条形码高效定位与识别实战

1. 为什么需要多条形码识别技术 在零售仓储和物流分拣场景中&#xff0c;我们经常需要同时处理多个条形码。比如快递站点的包裹分拣机&#xff0c;每秒钟要处理数十个包裹的条形码&#xff1b;超市收银台的商品堆里&#xff0c;经常叠放着五六件带条形码的商品。传统扫码枪需要…...

Stable-Diffusion-V1-5 效率工具集:Ollama本地LLM辅助提示词生成

Stable-Diffusion-V1-5 效率工具集&#xff1a;Ollama本地LLM辅助提示词生成 你是不是也遇到过这种情况&#xff1a;脑子里有个绝妙的画面&#xff0c;但打开Stable Diffusion&#xff0c;面对那个空白的提示词输入框&#xff0c;却不知道从何写起。要么写得太简单&#xff0c…...

如何用d2s-editor高效管理暗黑破坏神2存档:终极可视化编辑指南

如何用d2s-editor高效管理暗黑破坏神2存档&#xff1a;终极可视化编辑指南 【免费下载链接】d2s-editor 项目地址: https://gitcode.com/gh_mirrors/d2/d2s-editor d2s-editor是一款免费开源的Web版暗黑破坏神2存档编辑器&#xff0c;它将复杂的二进制存档文件转化为直…...

黑丝空姐-造相Z-Turbo应用场景:快速生成创意配图,提升内容创作效率

黑丝空姐-造相Z-Turbo应用场景&#xff1a;快速生成创意配图&#xff0c;提升内容创作效率 1. 镜像概述与核心价值 黑丝空姐-造相Z-Turbo是一款基于Xinference部署的文生图模型服务&#xff0c;专门针对特定风格图片生成进行了优化。该镜像通过gradio提供了直观的Web界面&…...