You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot拍卖系统实现与实时竞价问题求助

问题1:Postman竞价API 400错误解决

核心原因及修复方案

出现JSON解析异常(无法反序列化为double),通常是以下场景导致:

  • 字段类型不匹配:前端传递字符串格式的价格,后端用double基本类型接收;改用Double包装类可兼容空值校验
  • 字段名大小写/命名不一致:比如后端DTO字段是bidPrice,前端传bidprice或price
  • 缺少参数校验:未对价格字段做非空校验,导致空值传入触发解析失败

代码示例

1. 竞价请求DTO(带校验)

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;

public class BidRequest {
    @NotNull(message = "竞价价格不能为空")
    @Positive(message = "竞价价格必须大于0")
    private Double bidPrice; // 用Double而非double,支持空值校验

    @NotNull(message = "商品ID不能为空")
    private Long productId;

    // getter/setter
}

2. Controller接口(绑定校验)

@PostMapping("/bid")
public ResponseEntity<?> placeBid(
        @RequestBody @Valid BidRequest bidRequest,
        @AuthenticationPrincipal JwtAuthenticationToken token) {
    Long userId = (Long) token.getPrincipal();
    bidService.placeBid(userId, bidRequest.getProductId(), bidRequest.getBidPrice());
    return ResponseEntity.ok("竞价成功");
}

3. Postman请求体示例

{
    "bidPrice": 150.8,
    "productId": 1
}

核心拍卖逻辑实现(可扩展+高效)

1. 实体层扩展

商品实体(新增拍卖状态字段)

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Double basePrice; // 起拍价
    private Double currentHighestBid; // 当前最高价
    private Long highestBidderId; // 当前最高出价用户ID
    private LocalDateTime auctionStartTime;
    private LocalDateTime auctionEndTime;
    private AuctionStatus status; // 拍卖状态枚举

    // getter/setter
}

public enum AuctionStatus {
    NOT_STARTED, IN_PROGRESS, FINISHED
}

竞价记录实体

@Entity
public class Bid {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long productId;
    private Long userId;
    private Double bidAmount;
    private LocalDateTime bidTime;

    // getter/setter
}

2. 倒计时重置逻辑(基于Redis实现)

用Redis的键过期事件实现倒计时,分布式环境下更可靠:

  • 拍卖启动时,给商品设置Redis键auction:countdown:{productId},过期时间10秒
  • 每次新竞价成功,重置该键的过期时间为10秒
  • 监听Redis键过期事件,触发拍卖结束逻辑

Redis配置与监听器

@Configuration
public class RedisConfig {
    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        // 订阅键过期事件
        container.addMessageListener(new AuctionCountdownListener(), new PatternTopic("__keyevent@0__:expired"));
        return container;
    }
}

@Component
public class AuctionCountdownListener implements MessageListener {
    @Autowired
    private ProductRepository productRepository;

    @Override
    public void onMessage(Message message, byte[] pattern) {
        String key = new String(message.getBody());
        if (key.startsWith("auction:countdown:")) {
            Long productId = Long.parseLong(key.split(":")[2]);
            finishAuction(productId);
        }
    }

    private void finishAuction(Long productId) {
        Product product = productRepository.findById(productId).orElseThrow();
        if (product.getStatus() == AuctionStatus.IN_PROGRESS) {
            product.setStatus(AuctionStatus.FINISHED);
            product.setAuctionEndTime(LocalDateTime.now());
            // 此处可添加通知最高出价用户的逻辑(如MQ异步通知)
            productRepository.save(product);
        }
    }
}

竞价业务逻辑(含倒计时重置)

@Service
public class BidService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private ProductRepository productRepository;
    @Autowired
    private BidRepository bidRepository;

    public void placeBid(Long userId, Long productId, Double bidAmount) {
        // 分布式锁防止并发竞价
        String lockKey = "auction:lock:" + productId;
        Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 3, TimeUnit.SECONDS);
        if (!locked) {
            throw new RuntimeException("当前竞价繁忙,请稍后重试");
        }

        try {
            Product product = productRepository.findById(productId).orElseThrow();
            // 校验拍卖状态
            if (product.getStatus() != AuctionStatus.IN_PROGRESS) {
                throw new RuntimeException("拍卖未开始或已结束");
            }
            // 校验出价高于当前最高价
            if (bidAmount <= (product.getCurrentHighestBid() == null ? product.getBasePrice() : product.getCurrentHighestBid())) {
                throw new RuntimeException("出价需高于当前最高价");
            }

            // 保存竞价记录
            Bid bid = new Bid();
            bid.setProductId(productId);
            bid.setUserId(userId);
            bid.setBidAmount(bidAmount);
            bid.setBidTime(LocalDateTime.now());
            bidRepository.save(bid);

            // 更新商品最高价
            product.setCurrentHighestBid(bidAmount);
            product.setHighestBidderId(userId);
            productRepository.save(product);

            // 重置倒计时
            redisTemplate.expire("auction:countdown:" + productId, 10, TimeUnit.SECONDS);
        } finally {
            redisTemplate.delete(lockKey);
        }
    }
}

3. 推荐依赖库

  • Spring Data Redis:处理分布式缓存、锁与键过期事件,实现可靠的倒计时逻辑
  • Spring Validation:参数校验,从源头避免400类错误
  • Spring AMQP:若需异步通知用户(如拍卖结束通知),可结合RabbitMQ实现解耦

内容的提问来源于stack exchange,提问作者Destello

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 21:13:23