Appearance
团购系统
MineShop 的团购系统支持多人成团、阶梯价格、限时限量等玩法,自动处理开团、成团、失败等状态流转。
🎯 系统架构
┌─────────────────────────────────────────────────────────────┐
│ 团购活动 (GroupBuy) │
│ • 商品信息、团购价格、成团人数 │
│ • 活动时间、库存数量、状态 │
├─────────────────────────────────────────────────────────────┤
│ 团1 (Group) │ 团2 (Group) │ ... │
│ 团长: 用户A │ 团长: 用户B │ │
│ 状态: 拼团中 │ 状态: 已成团 │ │
│ 人数: 2/3 │ 人数: 3/3 │ │
├─────────────────────────────────────────────────────────────┤
│ 参团记录1 │ 参团记录2 │ 参团记录3 │ 参团记录4 │ ... │
└─────────────────────────────────────────────────────────────┘✨ 核心特性
1. 活动管理
| 字段 | 说明 | 示例 |
|---|---|---|
title | 活动标题 | 限时团购 iPhone 15 |
product_id | 关联商品 | 商品 ID |
original_price | 原价 | 5999 |
group_price | 团购价 | 4999 |
min_people | 最少成团人数 | 2 |
max_people | 最多成团人数 | 10 |
group_time_limit | 成团时限(小时) | 24 |
total_quantity | 活动库存 | 100 |
2. 团购实体
php
// GroupBuyEntity.php
final class GroupBuyEntity
{
// 判断是否可以参团
public function canJoin(): bool
{
if (!$this->isEnabled) {
return false;
}
if ($this->status !== 'active') {
return false;
}
if ($this->soldQuantity >= $this->totalQuantity) {
return false;
}
$timeVo = new ActivityTimeVo($this->startTime, $this->endTime);
return $timeVo->isActive();
}
// 30分钟编辑锁定
public function isWithinCacheWarmupPeriod(): bool
{
$startTime = Carbon::parse($this->startTime);
$now = Carbon::now();
if ($startTime->lte($now)) {
return false;
}
return $startTime->diffInMinutes($now) <= 30;
}
}3. 缓存预热
php
// GroupBuyCacheService.php
public function warmStock(int $groupBuyId): void
{
$model = $this->repository->findById($groupBuyId);
if (!$model) {
return;
}
// 计算剩余库存
$remaining = max(0, $model->total_quantity - $model->sold_quantity);
$skuId = (int) $model->sku_id;
// 写入 Redis Hash
$hashKey = sprintf('stock:%d', $groupBuyId);
$this->cache->hMset($hashKey, [(string) $skuId => (string) $remaining]);
}🔄 状态流转
活动状态
┌──────────┐ 开始时间到达 ┌──────────┐ 结束时间到达 ┌──────────┐
│ pending │ ───────────────→ │ active │ ───────────────→ │ ended │
│ 待开始 │ │ 进行中 │ │ 已结束 │
└──────────┘ └──────────┘ └──────────┘
│
│ 库存售罄
↓
┌──────────┐
│ sold_out │
│ 已售罄 │
└──────────┘团状态
┌──────────┐ 人数达标 ┌──────────┐
│ grouping │ ───────────→ │ success │
│ 拼团中 │ │ 已成团 │
└──────────┘ └──────────┘
│
│ 超时未成团
↓
┌──────────┐
│ failed │
│ 已失败 │
└──────────┘🕐 定时任务
php
// GroupBuyActivityStatusCrontab.php
#[Crontab(
name: 'group-buy-activity-status',
rule: '*/10 * * * *', // 每10分钟执行
callback: 'execute'
)]
class GroupBuyActivityStatusCrontab
{
public function execute(): void
{
// 1. 处理待开始的活动(30分钟内)
$this->processPendingActivities();
// 2. 处理已过期的活动
$this->processExpiredActivities();
}
private function processPendingActivities(): void
{
$activities = $this->repository->findPendingActivitiesWithinMinutes(30);
foreach ($activities as $activity) {
$startTime = Carbon::parse($activity->start_time);
if ($startTime->lte(Carbon::now())) {
// 立即激活
$this->groupBuyService->start($activity->id);
$this->cacheService->warmStock($activity->id);
} else {
// 延迟 Job
$delaySeconds = $startTime->diffInSeconds(Carbon::now());
$this->driverFactory->get('default')->push(
new GroupBuyStartJob($activity->id),
$delaySeconds
);
}
}
}
}📦 缓存结构
Redis Key 结构:
groupbuy:stock:{groupBuyId} # 库存数据 (Hash, field=skuId, value=剩余库存)
groupbuy:group:{groupId} # 团信息 (String/JSON)
groupbuy:members:{groupId} # 团成员 (Set)💻 API 接口
后台管理
| 接口 | 方法 | 说明 |
|---|---|---|
/admin/group-buy | GET | 活动列表 |
/admin/group-buy | POST | 创建活动 |
/admin/group-buy/{id} | PUT | 更新活动 |
/admin/group-buy/{id} | DELETE | 删除活动 |
/admin/group-buy/{id}/toggle | POST | 切换状态 |
前端 API
| 接口 | 方法 | 说明 |
|---|---|---|
/api/group-buy/list | GET | 获取活动列表 |
/api/group-buy/{id} | GET | 活动详情 |
/api/group-buy/groups | GET | 获取可参与的团 |
/api/group-buy/create-group | POST | 开团 |
/api/group-buy/join-group | POST | 参团 |
🛡️ 业务规则
开团规则
- 活动必须处于
active状态 - 活动库存充足
- 用户未超过限购数量
参团规则
- 团必须处于
grouping状态 - 团人数未满
- 用户未参与过该团
- 活动库存充足
成团规则
- 团人数达到
min_people - 未超过成团时限
失败处理
- 超时未成团自动标记失败
- 退还用户支付金额
- 回滚库存
📊 数据统计
- 活动销售额
- 成团率分析
- 用户参与度
- 热门商品排行
⚠️ 注意事项
- 库存预热: 活动开始前 30 分钟自动预热
- 编辑限制: 预热期内禁止编辑活动
- 超时处理: 定时任务检查超时团并处理
- 退款流程: 失败团需触发退款流程