《Spring Boot 3实战案例合集》现已囊括超过50篇精选实战文章,并且此合集承诺将永久持续更新,为您带来最前沿的技术资讯与实践经验。欢迎积极订阅,享受不断升级的知识盛宴!订阅用户将特别获赠合集内所有文章的最终版MD文档(详尽学习笔记),以及完整的项目源码,助您在学习道路上畅通无阻。
环境:SpringBoot3.2.5
1. 简介
当用户操作一个API数据流或任何数据源时,在实际上他们只进行了一次操作,但由于某些原因,可能是用户的有意为之,或者是黑客的行为,导致了系统数据错误。
为了防止这种情况的发生,我们需要构建一个去重解决方案。本篇文章中,我将基于Redis和Spring Boot来实现去重。
我们会按照如下进行实现:
根据请求体中的某些数据字段,创建一个Redis键。使用哪个字段取决于业务需求,以及响应这些需求的系统架构的选择。
构建键完成后,然后再使用MD5对其进行哈希(在这里使用MD5是可选的,取决于具体的需求)。
每当用户调用API时,都会检查Redis键。如果键存在,则返回重复数据错误;如果不存在,则继续处理逻辑。
在将键插入Redis时,必须配置过期时间。为了演示的方便,我们设置为60s,你完全可以将其定义在配置文件中。
最后,我们会优化程序,让相关的设置支持动态配置(支持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
6379 :
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