第一种方法:单个防止
在Spring Boot应用中使用Redis来防止表单的重复提交,可以通过以下几个步骤来实现:
确保你的项目中添加了Spring Boot Starter Data Redis和Spring Boot Starter Web依赖。在pom.xml
文件中添加以下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
在application.properties
或application.yml
中配置Redis服务器的信息:
spring.redis.host=localhost
spring.redis.port=6379
创建一个工具类来封装Redis的一些常用操作,比如设置键值对、获取键值对等。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class RedisUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void set(String key, String value) {
stringRedisTemplate.opsForValue().set(key, value);
}
public String get(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
public void delete(String key) {
stringRedisTemplate.delete(key);
}
}
在Controller中使用上面的工具类来实现防重逻辑。通常的做法是在请求开始时检查Redis中是否存在该请求的唯一标识(如用户ID + 时间戳),如果不存在则允许请求继续,并将此标识存入Redis;如果存在,则直接返回错误信息。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@Autowired
private RedisUtil redisUtil;
@PostMapping("/submit")
public String submit(@RequestParam("userId") String userId) {
// 使用用户ID和时间戳作为key,防止并发下的重复提交
String key = "form_submit_" + userId + "_" + System.currentTimeMillis();
if (redisUtil.get(key) != null) {
return "请求重复,请勿重复提交!";
} else {
// 执行业务逻辑
// ...
// 将key存入Redis并设置过期时间
redisUtil.set(key, "1", 5, TimeUnit.MINUTES);
return "提交成功!";
}
}
}
请注意,在实际应用中,你可能需要根据具体需求调整上述代码,比如更改存储在Redis中的键的生成方式、设置更合理的过期时间等。此外,为了提高性能和避免并发问题,可以考虑使用Redis的原子操作,例如SETNX
命令。
第二种方法:aop全局方法
使用AOP(面向切面编程)来统一处理防重复提交是一种很好的实践,它能减少重复代码并保持业务逻辑的清晰性。下面是如何使用Spring AOP来实现这一功能的示例。
首先,你需要在项目中添加Spring AOP的支持,通常情况下Spring Boot项目已经默认包含AOP支持。
创建一个切面类,用于定义防重提交的逻辑。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
public class AntiDuplicateSubmissionAspect {
private static final String KEY_PREFIX = "anti_duplicate_";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Around("@annotation(antiDuplicateSubmission)")
public Object antiDuplicateSubmit(ProceedingJoinPoint joinPoint, AntiDuplicateSubmission antiDuplicateSubmission) throws Throwable {
// 从注解中获取参数名
String paramName = antiDuplicateSubmission.value();
// 获取方法参数
Object[] args = joinPoint.getArgs();
// 假设参数位置已知,或者通过参数名获取参数值
String userId = (String) args[0];
// 生成唯一标识符
String key = KEY_PREFIX + userId + "_" + System.currentTimeMillis();
// 检查是否已经存在
if (stringRedisTemplate.hasKey(key)) {
throw new RuntimeException("请求重复,请勿重复提交!");
}
// 设置过期时间,例如5分钟
stringRedisTemplate.opsForValue().set(key, "1", 5, TimeUnit.MINUTES);
try {
return joinPoint.proceed();
} finally {
// 清理key,这一步在大多数情况下可选,因为设置了过期时间
stringRedisTemplate.delete(key);
}
}
}
创建一个自定义注解,用于标记需要防重提交的控制器方法。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AntiDuplicateSubmission {
String value();
}
在需要防重提交的Controller方法上应用这个注解。
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@PostMapping("/submit")
@AntiDuplicateSubmission(value = "userId")
public String submit(@RequestParam("userId") String userId) {
// 执行业务逻辑
// ...
return "提交成功!";
}
}
这样,每次有请求到达带有@AntiDuplicateSubmission
注解的方法时,AOP切面会先执行防重检查,如果发现重复提交,则阻止业务逻辑执行并抛出异常。
注意,这里的AntiDuplicateSubmission
注解的value
属性应该与请求参数名匹配,以便正确地获取到用户ID或其他用于生成唯一标识符的参数。同时,使用System.currentTimeMillis()
生成的时间戳作为一部分唯一标识符可能在高并发场景下存在并发问题,你可能需要一个更安全的唯一生成策略,例如UUID或者结合用户ID、请求时间以及随机数等生成一个更复杂的唯一标识符。