Java实现秒杀与拼团:核心代码示例

Java实现秒杀与拼团:核心代码示例

您好!作为一名资深后端开发工程师,我将为您提供秒杀和拼团活动的核心Java代码实现思路与示例。这些代码片段旨在演示关键逻辑,实际生产环境需结合具体业务需求、分布式组件(如Redis、消息队列、分布式锁)和 Spring/Spring Boot 框架进行更完善的集成。

一、 秒杀活动核心代码实现

秒杀的核心在于高并发下的库存扣减与订单创建的原子性及效率。

1. 库存模型 (商品库存服务)

在生产环境中,库存通常由专门的库存服务管理,并利用 Redis 进行预热和快速扣减。


import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

/**
 * 库存服务模拟:实际生产环境会是更复杂的分布式库存服务
 * 仅用于演示核心概念
 */
@Service
public class SeckillStockService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    // 假设商品ID为1001的秒杀商品
    private static final String SECKILL_ITEM_ID = "seckill:item:1001";
    private static final String SECKILL_STOCK_KEY = "seckill:stock:" + SECKILL_ITEM_ID;

    // 初始化库存(假设从数据库加载)
    public void initStock(int initialStock) {
        // 生产环境应该从数据库加载,并缓存到Redis
        redisTemplate.opsForValue().set(SECKILL_STOCK_KEY, String.valueOf(initialStock));
        System.out.println("秒杀商品 " + SECKILL_ITEM_ID + " 库存初始化为: " + initialStock);
    }

    /**
     * 尝试扣减库存
     * 使用Redis的原子操作 INCRBY 实现高并发下的库存预扣
     *
     * @return true 扣减成功,false 库存不足
     */
    public boolean deductStock() {
        // 利用Redis的decrBy操作,如果结果小于0,说明库存不足
        Long currentStock = redisTemplate.opsForValue().decrement(SECKILL_STOCK_KEY);

        if (currentStock != null && currentStock >= 0) {
            System.out.println("库存扣减成功,当前库存:" + currentStock);
            return true;
        } else {
            // 扣减失败,将库存加回去,避免负数
            if (currentStock != null) {
                redisTemplate.opsForValue().increment(SECKILL_STOCK_KEY); // 加回去
            }
            System.out.println("库存不足或已售罄!");
            return false;
        }
    }

    /**
     * 释放库存 (例如:支付超时未支付)
     */
    public void releaseStock() {
        redisTemplate.opsForValue().increment(SECKILL_STOCK_KEY);
        System.out.println("库存释放成功!");
    }

    public int getCurrentStock() {
        String stockStr = redisTemplate.opsForValue().get(SECKILL_STOCK_KEY);
        return stockStr != null ? Integer.parseInt(stockStr) : 0;
    }
}

2. 秒杀订单服务

订单服务负责接收秒杀请求,调用库存服务,并异步创建订单。


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 秒杀订单服务模拟
 * 实际生产环境会通过消息队列削峰
 */
@Service
public class SeckillOrderService {

    @Autowired
    private SeckillStockService seckillStockService;

    // 模拟消息队列,实际使用Kafka/RabbitMQ
    private final ExecutorService orderCreationExecutor = Executors.newFixedThreadPool(10);

    /**
     * 处理秒杀请求
     * @param userId 用户ID
     * @param seckillItemId 秒杀商品ID
     * @return 订单ID或错误信息
     */
    public String handleSeckillRequest(Long userId, String seckillItemId) {
        // 1. 前置检查 (限流、黑名单等) - 这里简化,实际会更复杂
        if (!"seckill:item:1001".equals(seckillItemId)) {
            return "商品不存在或秒杀未开始";
        }

        // 2. 尝试预扣库存 (Redis原子操作)
        if (!seckillStockService.deductStock()) {
            return "秒杀失败:库存不足或已售罄";
        }

        // 3. 异步创建订单 (削峰填谷)
        // 生产环境:将订单信息发送到消息队列,由消费者异步处理
        orderCreationExecutor.submit(() -> {
            try {
                // 模拟订单创建和支付流程
                String orderId = generateOrderId(userId, seckillItemId);
                System.out.println("用户 " + userId + " 成功抢到商品 " + seckillItemId + ",生成订单:" + orderId);
                // 模拟支付,实际支付会通过第三方支付平台回调
                Thread.sleep(500); // 模拟支付耗时
                // 支付成功后,需要进行数据库的最终库存扣减,并更新订单状态
                // 数据库库存扣减通常通过数据库乐观锁或消息队列+TCC事务保证
                System.out.println("订单 " + orderId + " 支付成功,准备发货...");

            } catch (InterruptedException e) {
                // 异常或支付超时,需要释放预扣的库存
                seckillStockService.releaseStock();
                System.err.println("订单创建或支付过程异常,库存已释放。");
                Thread.currentThread().interrupt();
            }
        });

        return "秒杀成功,订单正在处理中!";
    }

    private String generateOrderId(Long userId, String itemId) {
        return "ORDER_" + userId + "_" + itemId + "_" + System.currentTimeMillis();
    }

    // 实际生产环境需要优雅关闭线程池
    public void shutdown() {
        orderCreationExecutor.shutdown();
        try {
            if (!orderCreationExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
                orderCreationExecutor.shutdownNow();
            }
        } catch (InterruptedException e) {
            orderCreationExecutor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

3. 控制器层 (API)


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/seckill")
public class SeckillController {

    @Autowired
    private SeckillOrderService seckillOrderService;
    @Autowired
    private SeckillStockService seckillStockService;

    @GetMapping("/initStock/{stock}")
    public String initStock(@PathVariable int stock) {
        seckillStockService.initStock(stock);
        return "秒杀库存初始化成功:" + stock;
    }

    @GetMapping("/doSeckill/{userId}")
    public String doSeckill(@PathVariable Long userId) {
        // 假设秒杀商品ID固定为1001
        String seckillItemId = "seckill:item:1001";
        String result = seckillOrderService.handleSeckillRequest(userId, seckillItemId);
        return result;
    }

    @GetMapping("/stock")
    public String getCurrentStock() {
        return "当前秒杀商品库存:" + seckillStockService.getCurrentStock();
    }
}
秒杀总结:

二、 拼团活动核心代码实现

拼团的核心在于管理拼团状态、人数,并在成团后触发订单处理

1. 拼团实体与状态


import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

public class GroupBuying {
    public enum GroupStatus {
        INITIATED, // 已发起,待成团
        SUCCESS,   // 已成团
        FAILED     // 拼团失败
    }

    private String groupId;
    private String itemId; // 商品ID
    private Long initiatorUserId; // 开团用户ID
    private int requiredMembers; // 所需成团人数
    private AtomicInteger currentMembers; // 当前参团人数
    private GroupStatus status;
    private LocalDateTime createTime;
    private LocalDateTime expireTime; // 拼团截止时间
    private List<GroupMember> members; // 参团成员列表

    public GroupBuying(String itemId, Long initiatorUserId, int requiredMembers, long durationMinutes) {
        this.groupId = "GB_" + UUID.randomUUID().toString().substring(0, 8);
        this.itemId = itemId;
        this.initiatorUserId = initiatorUserId;
        this.requiredMembers = requiredMembers;
        this.currentMembers = new AtomicInteger(1); // 开团者算一个
        this.status = GroupStatus.INITIATED;
        this.createTime = LocalDateTime.now();
        this.expireTime = createTime.plusMinutes(durationMinutes);
        this.members = new CopyOnWriteArrayList<>(); // 线程安全列表
        this.members.add(new GroupMember(initiatorUserId, generateOrderId(initiatorUserId, itemId))); // 记录开团者
    }

    // 省略 Getter/Setter
    public String getGroupId() { return groupId; }
    public String getItemId() { return itemId; }
    public Long getInitiatorUserId() { return initiatorUserId; }
    public int getRequiredMembers() { return requiredMembers; }
    public int getCurrentMembers() { return currentMembers.get(); }
    public GroupStatus getStatus() { return status; }
    public LocalDateTime getCreateTime() { return createTime; }
    public LocalDateTime getExpireTime() { return expireTime; }
    public List<GroupMember> getMembers() { return members; }

    public synchronized boolean addMember(Long userId) {
        if (status != GroupStatus.INITIATED || currentMembers.get() >= requiredMembers || LocalDateTime.now().isAfter(expireTime)) {
            return false; // 不允许加入
        }
        if (members.stream().anyMatch(m -> m.getUserId().equals(userId))) {
            return false; // 用户已在团中
        }
        currentMembers.incrementAndGet();
        members.add(new GroupMember(userId, generateOrderId(userId, itemId)));
        return true;
    }

    public synchronized void checkStatus() {
        if (status == GroupStatus.INITIATED) {
            if (currentMembers.get() >= requiredMembers) {
                status = GroupStatus.SUCCESS;
                System.out.println("拼团 " + groupId + " 成功!");
                // TODO: 触发订单系统处理成团订单
            } else if (LocalDateTime.now().isAfter(expireTime)) {
                status = GroupStatus.FAILED;
                System.out.println("拼团 " + groupId + " 失败,已过期!");
                // TODO: 触发退款流程
            }
        }
    }

    private String generateOrderId(Long userId, String itemId) {
        return "ORDER_" + userId + "_" + itemId + "_" + System.currentTimeMillis();
    }

    public static class GroupMember {
        private Long userId;
        private String orderId; // 参团时生成的订单ID

        public GroupMember(Long userId, String orderId) {
            this.userId = userId;
            this.orderId = orderId;
        }
        public Long getUserId() { return userId; }
        public String getOrderId() { return orderId; }
    }
}

2. 拼团服务

管理拼团的创建、加入和状态检查。


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 拼团服务模拟
 * 生产环境会使用数据库存储拼团信息,并结合消息队列/定时任务处理过期
 */
@Service
public class GroupBuyingService {

    // 模拟存储所有拼团,生产环境用数据库+Redis
    private final Map<String, GroupBuying> activeGroupBuyings = new ConcurrentHashMap<>();

    // 用于定时检查过期拼团
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    @Autowired
    private SeckillStockService seckillStockService; // 假设拼团也需要扣库存

    public GroupBuyingService() {
        // 每分钟检查一次所有活跃拼团的状态
        scheduler.scheduleAtFixedRate(this::checkAllGroupBuyingsStatus, 0, 1, TimeUnit.MINUTES);
    }

    /**
     * 发起拼团
     * @param itemId 商品ID
     * @param userId 开团用户ID
     * @param requiredMembers 所需人数
     * @param durationMinutes 持续时间(分钟)
     * @return 拼团ID
     */
    public String createGroupBuying(String itemId, Long userId, int requiredMembers, long durationMinutes) {
        // 假设发起拼团也需要先锁定库存,成团成功再真实扣减
        // 生产环境:这里可以考虑预扣一个库存或者先不扣,等成团再扣
        if (!seckillStockService.deductStock()) { // 简化处理,直接扣减一个库存
             System.out.println("创建拼团失败:库存不足");
             return null;
        }

        GroupBuying group = new GroupBuying(itemId, userId, requiredMembers, durationMinutes);
        activeGroupBuyings.put(group.getGroupId(), group);
        System.out.println("用户 " + userId + " 发起了拼团 " + group.getGroupId() + ",目标 " + requiredMembers + " 人,截止时间 " + group.getExpireTime());
        return group.getGroupId();
    }

    /**
     * 参与拼团
     * @param groupId 拼团ID
     * @param userId 参团用户ID
     * @return true 参与成功,false 失败
     */
    public boolean joinGroupBuying(String groupId, Long userId) {
        GroupBuying group = activeGroupBuyings.get(groupId);
        if (group == null) {
            System.out.println("参团失败:拼团不存在");
            return false;
        }
        if (group.getStatus() != GroupBuying.GroupStatus.INITIATED) {
            System.out.println("参团失败:拼团状态不允许加入(已成功/已失败)");
            return false;
        }
        if (LocalDateTime.now().isAfter(group.getExpireTime())) {
            System.out.println("参团失败:拼团已过期");
            return false;
        }

        // 尝试加入,并进行库存预扣
        // 生产环境:这里也需要分布式锁保证addMember的原子性
        synchronized (group) { // 简单粗暴的锁,生产环境用Redisson等分布式锁
            if (!seckillStockService.deductStock()) { // 简化处理,直接扣减一个库存
                System.out.println("参团失败:库存不足");
                return false;
            }
            if (group.addMember(userId)) {
                System.out.println("用户 " + userId + " 成功加入拼团 " + groupId + ",当前人数:" + group.getCurrentMembers() + "/" + group.getRequiredMembers());
                group.checkStatus(); // 检查是否成团
                return true;
            } else {
                seckillStockService.releaseStock(); // 加入失败,释放库存
                System.out.println("参团失败:无法加入(可能已满员或已加入)");
                return false;
            }
        }
    }

    /**
     * 定时任务:检查所有活跃拼团的状态,处理过期或已成团的
     */
    private void checkAllGroupBuyingsStatus() {
        System.out.println("--- 检查所有拼团状态 ---");
        activeGroupBuyings.forEach((groupId, group) -> {
            group.checkStatus();
            if (group.getStatus() == GroupBuying.GroupStatus.SUCCESS || group.getStatus() == GroupBuying.GroupStatus.FAILED) {
                // 生产环境:这里应该把已完成/失败的拼团持久化到数据库,并从活跃列表中移除
                // 为了简化,这里只是打印日志
                System.out.println("拼团 " + groupId + " 状态为 " + group.getStatus() + ",将移出活跃列表。");
                // 实际操作应该是:
                // 1. 持久化GroupBuying对象到数据库
                // 2. 根据状态触发后续流程(发货/退款)
                // 3. activeGroupBuyings.remove(groupId);
            }
        });

        // 移除已完成/失败的拼团 (这里简化为手动移除,实际更复杂)
        activeGroupBuyings.entrySet().removeIf(entry ->
            entry.getValue().getStatus() == GroupBuying.GroupStatus.SUCCESS ||
            entry.getValue().getStatus() == GroupBuying.GroupStatus.FAILED
        );
    }

    public GroupBuying getGroupBuying(String groupId) {
        return activeGroupBuyings.get(groupId);
    }

    public void shutdown() {
        scheduler.shutdown();
        try {
            if (!scheduler.awaitTermination(60, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduler.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

3. 控制器层 (API)


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/groupbuying")
public class GroupBuyingController {

    @Autowired
    private GroupBuyingService groupBuyingService;

    @GetMapping("/create/{itemId}/{userId}/{requiredMembers}/{durationMinutes}")
    public String createGroup(
            @PathVariable String itemId,
            @PathVariable Long userId,
            @PathVariable int requiredMembers,
            @PathVariable long durationMinutes) {
        String groupId = groupBuyingService.createGroupBuying(itemId, userId, requiredMembers, durationMinutes);
        if (groupId != null) {
            return "拼团发起成功,团号:" + groupId + ",请分享给好友!";
        } else {
            return "拼团发起失败,请重试。";
        }
    }

    @GetMapping("/join/{groupId}/{userId}")
    public String joinGroup(
            @PathVariable String groupId,
            @PathVariable Long userId) {
        if (groupBuyingService.joinGroupBuying(groupId, userId)) {
            return "成功加入拼团 " + groupId + "!";
        } else {
            return "加入拼团失败,请检查团号或拼团状态。";
        }
    }

    @GetMapping("/status/{groupId}")
    public String getGroupStatus(@PathVariable String groupId) {
        GroupBuying group = groupBuyingService.getGroupBuying(groupId);
        if (group != null) {
            return "拼团 " + groupId + " 当前人数:" + group.getCurrentMembers() + "/" + group.getRequiredMembers() +
                   ",状态:" + group.getStatus() + ",截止时间:" + group.getExpireTime();
        } else {
            return "拼团不存在。";
        }
    }
}
拼团总结:

三、 辅助工具/依赖

为了运行上述代码,您需要一个 Spring Boot 项目,并添加以下Maven依赖:


<!-- Spring Boot Web Starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Spring Data Redis Starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- 如果使用Redisson作为分布式锁,可选 -->
<!--
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.25.1</version> <!-- 替换为最新版本 -->
</dependency>
-->

<!-- 对于消息队列,例如Kafka -->
<!--
<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>
-->

并在 application.properties (或 application.yml) 中配置 Redis 连接:


spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0
# spring.redis.password=

生产环境注意事项:

这些Java代码片段提供了一个起点,帮助您理解秒杀和拼团的核心逻辑。实际的系统需要更全面、健壮的架构设计和实现。如果您对其中某个模块的分布式实现有更深入的疑问,欢迎随时提问!

互动区域

登录后可以点赞此内容

参与互动

登录后可以点赞和评论此内容,与作者互动交流