当前位置: 首页 > 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进…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

HTML 列表、表格、表单

1 列表标签 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表。 例如&#xff1a; 1.1 无序列表 标签&#xff1a;ul 嵌套 li&#xff0c;ul是无序列表&#xff0c;li是列表条目。 注意事项&#xff1a; ul 标签里面只能包裹 li…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】

1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件&#xff08;System Property Definition File&#xff09;&#xff0c;用于声明和管理 Bluetooth 模块相…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...