本文目录导读:

我就从实际开发的角度,给你整理几种拦截Java重复数据的常见案例和实现方案。
接口幂等性拦截(高频请求)
场景
用户快速点击提交按钮,导致同一请求多次提交
解决方案:基于Token+Redis
// 1. 生成Token
@PostMapping("/token")
public Result<String> getToken() {
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("idempotent:" + token, "1", 30, TimeUnit.MINUTES);
return Result.success(token);
}
// 2. 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
String message() default "请勿重复提交";
}
// 3. AOP拦截
@Aspect
@Component
public class IdempotentAspect {
@Around("@annotation(idempotent)")
public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
// 获取当前请求
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
String token = request.getHeader("token");
if (token == null) {
throw new BusinessException("请求未携带token");
}
// 判断token是否存在(存在则删除,实现一次有效)
Boolean success = redisTemplate.opsForValue()
.setIfAbsent("idempotent:" + token, "1", 1, TimeUnit.MINUTES);
if (Boolean.FALSE.equals(success)) {
throw new BusinessException(idempotent.message());
}
return joinPoint.proceed();
}
}
数据库唯一索引拦截
场景
用户注册、订单号重复等问题
解决方案:唯一约束 + 优雅捕获
// 1. 数据库字段加唯一索引
ALTER TABLE `user` ADD UNIQUE INDEX `uk_phone` (`phone`);
// 2. 插入时捕获异常
@Service
public class UserService {
public Result<String> register(User user) {
try {
userMapper.insert(user);
return Result.success("注册成功");
} catch (DuplicateKeyException e) {
// 捕获数据库唯一索引异常
return Result.error("该手机号已注册");
}
}
}
基于数据库锁的防重复
场景
抢红包、优惠券领取等并发场景
解决方案:数据库乐观锁
// 1. 数据库增加版本号字段
ALTER TABLE `coupon` ADD COLUMN `version` INT DEFAULT 0;
// 2. 更新时检查版本号
@Service
public class CouponService {
public boolean grabCoupon(Long userId, Long couponId) {
// 1. 查询是否已领取(业务层面判断)
CouponRecord record = couponRecordMapper.selectByUserIdAndCouponId(userId, couponId);
if (record != null) {
return false; // 已领取
}
// 2. 更新库存(乐观锁)
int count = couponMapper.updateCouponStock(couponId);
if (count <= 0) {
return false; // 库存不足或并发失败
}
// 3. 插入领取记录(唯一索引兜底)
try {
couponRecordMapper.insert(new CouponRecord(userId, couponId));
} catch (DuplicateKeyException e) {
// 库存回滚
couponMapper.rollbackStock(couponId);
return false;
}
return true;
}
}
// Mapper层
@Mapper
public interface CouponMapper {
@Update("UPDATE coupon SET stock = stock - 1, version = version + 1 " +
"WHERE id = #{couponId} AND stock > 0")
int updateCouponStock(Long couponId);
}
Redisson分布式锁(微服务场景)
场景
多个服务实例同时处理同一订单
@Service
public class OrderService {
@Autowired
private RedissonClient redissonClient;
public String createOrder(OrderDTO orderDTO) {
// 构建锁key(业务唯一标识)
String lockKey = "order:lock:" + orderDTO.getUserId() + ":" + orderDTO.getProductId();
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试加锁(等待5秒,持有锁30秒)
boolean isLock = lock.tryLock(5, 30, TimeUnit.SECONDS);
if (!isLock) {
throw new BusinessException("系统繁忙,请稍后再试");
}
// 二次检查是否已创建(防止重复创建)
Order order = orderMapper.selectByOrderNo(orderDTO.getOrderNo());
if (order != null) {
return "订单已存在";
}
// 执行业务逻辑
return doCreateOrder(orderDTO);
} catch (Exception e) {
throw new BusinessException("创建订单失败");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
Guava本地缓存(单机场景)
场景
防止短时间内重复请求
@Component
public class RepeatRequestInterceptor extends HandlerInterceptorAdapter {
// 创建本地缓存(10分钟过期,最大1000条)
private Cache<String, String> cache = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000)
.build();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (handler instanceof HandlerMethod) {
// 生成唯一标识(url + 参数 + 用户ID)
String key = generateKey(request);
String value = cache.getIfPresent(key);
if (value != null) {
throw new BusinessException("请求过于频繁,请稍后再试");
}
cache.put(key, "1");
}
return true;
}
private String generateKey(HttpServletRequest request) {
// 结合请求参数生成唯一标识
String uri = request.getRequestURI();
String params = request.getQueryString();
String userId = request.getHeader("userId");
return MD5.encode(uri + ":" + params + ":" + userId);
}
}
综合方案推荐
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Redis+Token | 表单提交 | 实现简单,用户体验好 | 依赖Redis |
| 唯一索引 | 数据唯一性 | 可靠,最终一致性 | 业务侵入 |
| 乐观锁 | 库存扣减 | 性能好 | 需要重试机制 |
| 分布式锁 | 微服务多实例 | 集群安全 | 性能损耗 |
| 本地缓存 | 单机防抖 | 速度快 | 不支持集群 |
实战建议
- 防重设计要尽早:在架构设计阶段就考虑,后面加比较困难
- 多种方案组合:
- 前端:按钮灰化+Loading
- 业务层:Token+Redis
- 持久层:唯一索引兜底
- 日志记录要详尽:记录谁、什么时间、请求了什么接口、参数是什么
需要的话,我可以给你一个完整的防重复提交的Demo项目代码。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播