Spring Boot 一个注解防止重复请求

文摘   2024-12-04 18:00   新疆  

Spring Boot 3实战案例合集》现已囊括超过50篇精选实战文章,并且此合集承诺将永久持续更新,为您带来最前沿的技术资讯与实践经验。欢迎积极订阅,享受不断升级的知识盛宴!订阅用户将特别获赠合集内所有文章的最终版MD文档(详尽学习笔记),以及完整的项目源码,助您在学习道路上畅通无阻。

环境:SpringBoot3.2.5



1. 简介

当用户操作一个API数据流或任何数据源时,在实际上他们只进行了一次操作,但由于某些原因,可能是用户的有意为之,或者是黑客的行为,导致了系统数据错误。

为了防止这种情况的发生,我们需要构建一个去重解决方案。本篇文章中,我将基于Redis和Spring Boot来实现去重。

我们会按照如下进行实现:

  1. 根据请求体中的某些数据字段,创建一个Redis键。使用哪个字段取决于业务需求,以及响应这些需求的系统架构的选择。

  2. 构建键完成后,然后再使用MD5对其进行哈希(在这里使用MD5是可选的,取决于具体的需求)。

  3. 每当用户调用API时,都会检查Redis键。如果键存在,则返回重复数据错误;如果不存在,则继续处理逻辑。

  4. 在将键插入Redis时,必须配置过期时间。为了演示的方便,我们设置为60s,你完全可以将其定义在配置文件中。

  5. 最后,我们会优化程序,让相关的设置支持动态配置(支持SpEL表达式)。

     

尽管上述方法可以有效地减少重复请求,但在真实世界的应用中,你可能需要根据具体情况调整方案,并考虑到诸如分布式系统的复杂性、高并发场景下的性能问题等。此外,使用MD5进行哈希可能会有安全方面的顾虑,尤其是在涉及到敏感信息的情况下,建议采用更安全的哈希算法如SHA-256。

接下来, 我们将详细介绍每一步的实现过程。

2. 实战案例

2.1 环境准备

我们将基于Spring Boot3 + Redis实现,所以我们需要引入下面的依赖:

<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-data-redis</artifactId></dependency>

Redis连接配置

spring:  data:    redis:      host: localhost      port: 6379      connect-timeout: 10000      timeout: 10000

我们还需要准备2个工具类及一个DTO,分别用来将byte[]转16进制、根据配置信息计算MD5值,最终接口返回使用统一的DTO对象。

public class Hex {
private static final char[] HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
public static byte[] decode(CharSequence s) { int nChars = s.length(); if (nChars % 2 != 0) { throw new IllegalArgumentException("16进制数据错误"); } byte[] result = new byte[nChars / 2]; for (int i = 0; i < nChars; i += 2) { int msb = Character.digit(s.charAt(i), 16); int lsb = Character.digit(s.charAt(i + 1), 16); if (msb < 0 || lsb < 0) { throw new IllegalArgumentException( "Detected a Non-hex character at " + (i + 1) + " or " + (i + 2) + " position"); } result[i / 2] = (byte) ((msb << 4) | lsb); } return result; }
public static String encode(byte[] buf) { StringBuilder sb = new StringBuilder(); for (int i = 0, leng = buf.length; i < leng; i++) { sb.append(HEX[(buf[i] & 0xF0) >>> 4]).append(HEX[buf[i] & 0x0F]); } return sb.toString(); }}

下面工具类是用来计算MD5值。

public class Utils {
public static Object getBody(ProceedingJoinPoint pjp) { try { for (int i = 0; i < pjp.getArgs().length; i++) { Object arg = pjp.getArgs()[i]; if (arg != null && isAnnotatedWithRequestBody(pjp, i)) { return arg; } } } return null ; }
private static boolean isAnnotatedWithRequestBody(ProceedingJoinPoint pjp, int paramIndex) { var method = getMethod(pjp); var parameterAnnotations = method.getParameterAnnotations(); for (Annotation annotation : parameterAnnotations[paramIndex]) { if (RequestBody.class.isAssignableFrom(annotation.annotationType())) { return true; } } return false; }
private static Method getMethod(ProceedingJoinPoint pjp) { MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); return methodSignature.getMethod(); }
public static String hashMD5(String source) { String res = null; try { var messageDigest = MessageDigest.getInstance("MD5"); var mdBytes = messageDigest.digest(source.getBytes()); res = Hex.encode(mdBytes) ; } return res; }}

统一返回结果对象DTO

Spring全家桶实战案例源码
spring, springboot, springcloud 案例开发详解
 最新文章