Files
number/后端架构设计/06-优惠券与活动系统设计.md

676 lines
27 KiB
Markdown
Raw Permalink Normal View History

# 优惠券系统 & 活动系统 — 前后端开发方案
> 基于现有项目架构Java Spring Boot + MyBatis-Plus + RabbitMQ + Redis / Vue 3 + Ant Design Vue
> 创建日期2026-03-21
---
## 一、总体架构
```
┌─────────────────────────────────────────────────────────────┐
│ 用户端 (Vue 3) │
│ 领券中心 │ Skill详情(券选择) │ 下单(券抵扣) │ 活动专区 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 后端服务 (Spring Boot) │
├─────────────────────────────────────────────────────────────┤
│ coupon 模块 │ promotion 模块 │
│ ├ CouponTemplate │ ├ PromotionActivity │
│ ├ UserCoupon │ ├ PromotionSkill │
│ ├ CouponService │ ├ FlashSaleSession │
│ └ CouponController │ ├ PromotionService │
│ │ └ PromotionController │
├─────────────────────────────────────────────────────────────┤
│ order 模块(改造) │
│ ├ previewOrder → 增加优惠券/活动价计算 │
│ ├ createOrder → 增加券核销 + 活动价锁定 │
│ └ cancelOrder → 增加券退回 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ MySQL (coupon_templates / user_coupons / │
│ promotion_activities / promotion_skills / │
│ flash_sale_sessions) │
│ Redis (库存预扣 / 防刷 / 活动缓存) │
│ RabbitMQ (券过期 / 活动开始结束 / 秒杀异步下单) │
└─────────────────────────────────────────────────────────────┘
```
---
## 二、优惠券系统
### 2.1 数据库设计
#### coupon_templates — 优惠券模板(管理员创建)
```sql
CREATE TABLE coupon_templates (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL COMMENT '券名称',
coupon_type ENUM('full_reduce','discount','fixed') NOT NULL COMMENT '满减/折扣/立减',
-- 优惠规则JSON灵活扩展
threshold_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT '使用门槛金额(0=无门槛)',
discount_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT '满减/立减金额',
discount_rate DECIMAL(3,2) DEFAULT 1.00 COMMENT '折扣率(0.80=8折)',
max_discount_amount DECIMAL(10,2) COMMENT '折扣券最大优惠金额(封顶)',
-- 适用范围
scope_type ENUM('all','category','skill') DEFAULT 'all' COMMENT '全场/指定分类/指定Skill',
scope_ids JSON COMMENT '适用的分类ID或SkillID数组',
-- 发放规则
total_count INT NOT NULL COMMENT '发行总量(-1=不限)',
issued_count INT DEFAULT 0 COMMENT '已发放数量',
per_user_limit INT DEFAULT 1 COMMENT '每人限领张数',
-- 时间
valid_type ENUM('fixed','relative') DEFAULT 'fixed' COMMENT '固定时段/领取后N天',
valid_start DATETIME COMMENT 'fixed模式: 生效开始时间',
valid_end DATETIME COMMENT 'fixed模式: 生效结束时间',
valid_days INT COMMENT 'relative模式: 领取后N天有效',
-- 状态
status ENUM('draft','active','paused','expired','exhausted') DEFAULT 'draft',
description VARCHAR(500) COMMENT '使用说明',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_status (status),
INDEX idx_coupon_type (coupon_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='优惠券模板表';
```
#### user_coupons — 用户持有的优惠券
```sql
CREATE TABLE user_coupons (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
template_id BIGINT NOT NULL COMMENT '券模板ID',
coupon_code VARCHAR(32) NOT NULL UNIQUE COMMENT '券码(唯一)',
status ENUM('unused','used','expired','returned') DEFAULT 'unused',
-- 冗余快照(下单时用,避免回查模板)
coupon_type VARCHAR(20) NOT NULL,
threshold_amount DECIMAL(10,2) DEFAULT 0.00,
discount_amount DECIMAL(10,2) DEFAULT 0.00,
discount_rate DECIMAL(3,2) DEFAULT 1.00,
max_discount_amount DECIMAL(10,2),
scope_type VARCHAR(20) DEFAULT 'all',
scope_ids JSON,
-- 有效期(计算后的绝对时间)
valid_start DATETIME NOT NULL,
valid_end DATETIME NOT NULL,
-- 使用记录
used_order_id BIGINT COMMENT '使用的订单ID',
used_at DATETIME COMMENT '使用时间',
received_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '领取时间',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
INDEX idx_user_status (user_id, status),
INDEX idx_template_id (template_id),
INDEX idx_valid_end (valid_end),
INDEX idx_coupon_code (coupon_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户优惠券表';
```
### 2.2 后端模块设计
```
module/coupon/
├── controller/
│ ├── CouponController.java # 用户端: 领券/我的券/可用券查询
│ └── AdminCouponController.java # 管理端: 券模板CRUD/发放/统计
├── dto/
│ ├── CouponTemplateCreateDTO.java # 创建券模板
│ ├── CouponTemplateUpdateDTO.java # 更新券模板
│ └── CouponIssueDTO.java # 手动发券(指定用户)
├── entity/
│ ├── CouponTemplate.java
│ └── UserCoupon.java
├── repository/
│ ├── CouponTemplateRepository.java
│ └── UserCouponRepository.java
├── service/
│ ├── CouponService.java
│ └── impl/CouponServiceImpl.java
├── vo/
│ ├── CouponTemplateVO.java
│ ├── UserCouponVO.java
│ └── CouponCalcResultVO.java # 优惠计算结果
└── task/
└── CouponExpireTask.java # 定时过期未使用券
```
### 2.3 核心 API 设计
| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| GET | `/api/coupons/available` | 可领取的券列表 | 登录 |
| POST | `/api/coupons/{templateId}/receive` | 用户领券 | 登录 |
| GET | `/api/coupons/my` | 我的优惠券(分tab: unused/used/expired) | 登录 |
| GET | `/api/coupons/usable?skillIds=1,2` | 下单时可用的券(传入SkillID自动匹配) | 登录 |
| POST | `/api/coupons/calc` | 计算优惠金额(预览) | 登录 |
| — | — | **管理端** | — |
| POST | `/api/admin/coupons/templates` | 创建券模板 | ADMIN |
| PUT | `/api/admin/coupons/templates/{id}` | 编辑券模板 | ADMIN |
| PUT | `/api/admin/coupons/templates/{id}/status` | 上架/暂停/下架 | ADMIN |
| GET | `/api/admin/coupons/templates` | 券模板列表(分页+筛选) | ADMIN |
| POST | `/api/admin/coupons/issue` | 手动发券给指定用户 | ADMIN |
| GET | `/api/admin/coupons/stats/{templateId}` | 单券统计(发放/使用/过期) | ADMIN |
### 2.4 核心业务逻辑
#### 领券流程
```
1. 校验券模板状态(active) + 时间有效 + 库存充足
2. Redis 原子扣减库存: DECR coupon:stock:{templateId}(防超发)
3. 校验用户领取上限: COUNT user_coupons WHERE user_id AND template_id
4. 生成券码(UUID短码): 插入 user_coupons快照券规则
5. DB 更新 issued_count最终一致Redis为准
6. 若DB插入失败 → Redis INCR 回补库存
```
#### 下单优惠计算(改造现有 OrderServiceImpl.previewOrder
```
1. 原有逻辑:计算 totalAmount / 积分抵扣
2. 新增步骤:
a. 若传入 couponId → 查 user_coupons 校验(unused + 未过期 + 适用范围匹配)
b. 根据 coupon_type 计算优惠:
- full_reduce: totalAmount >= threshold → 减 discount_amount
- discount: totalAmount * discount_rate不超过 max_discount_amount
- fixed: 直接减 discount_amount
c. couponDeductAmount = min(优惠金额, totalAmount)
d. 优惠券抵扣在积分抵扣之前: finalAmount = totalAmount - couponDeduct
e. 再对 finalAmount 计算积分抵扣
```
#### 下单时核销券
```
1. createOrder 增加 couponId 参数
2. CAS 更新 user_coupons SET status='used', used_order_id=?, used_at=NOW()
WHERE id=? AND status='unused' AND valid_end > NOW()
3. 若 rows=0 → 抛异常"优惠券已使用或已过期"
4. orders 表新增字段:
- coupon_id BIGINT COMMENT '使用的优惠券ID'
- coupon_deduct_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT '优惠券抵扣金额'
```
#### 取消/退款时退券
```
cancelOrder / approveRefund 中:
IF order.coupon_id IS NOT NULL:
UPDATE user_coupons SET status='returned', used_order_id=NULL, used_at=NULL
WHERE id=couponId AND status='used'
— 注: returned状态的券不可再次使用防止薅羊毛循环退券重用
— 也可按业务需求改为退回unused状态需评估风险
```
#### 定时过期
```
CouponExpireTask (@Scheduled cron="0 0 2 * * ?"):
UPDATE user_coupons SET status='expired'
WHERE status='unused' AND valid_end < NOW()
— 分批执行每批1000条防止大事务
```
### 2.5 Redis Key 设计
| Key | 说明 | TTL |
|-----|------|-----|
| `coupon:stock:{templateId}` | 券库存(原子扣减防超发) | 券过期后清理 |
| `coupon:receive:lock:{userId}:{templateId}` | 领券防重复(分布式锁) | 5秒 |
| `coupon:user:count:{userId}:{templateId}` | 用户已领数量缓存 | 1小时 |
---
## 三、活动系统(限时折扣 / 秒杀)
### 3.1 说明
现有 `activity` 模块是**内容运营型活动**(轮播展示、跳转链接),不包含价格逻辑。
本次新建 `promotion` 模块,专注**营销促销**:限时折扣、满减活动、秒杀。
### 3.2 数据库设计
#### promotion_activities — 促销活动主表
```sql
CREATE TABLE promotion_activities (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(200) NOT NULL COMMENT '活动名称',
promo_type ENUM('time_discount','flash_sale','full_reduce') NOT NULL
COMMENT '限时折扣/秒杀/满减活动',
-- 活动规则
discount_rate DECIMAL(3,2) COMMENT '限时折扣率(0.70=7折)',
threshold_amount DECIMAL(10,2) COMMENT '满减门槛',
reduce_amount DECIMAL(10,2) COMMENT '满减金额',
-- 时间
start_time DATETIME NOT NULL,
end_time DATETIME NOT NULL,
-- 状态
status ENUM('draft','pending','active','ended','cancelled') DEFAULT 'draft'
COMMENT 'draft=草稿 pending=待开始 active=进行中 ended=已结束',
-- 展示
cover_image VARCHAR(500) COMMENT '活动封面图',
description TEXT COMMENT '活动描述',
banner_url VARCHAR(500) COMMENT '活动Banner图',
sort_order INT DEFAULT 0,
-- 限制
user_limit INT DEFAULT 0 COMMENT '每用户限购次数(0=不限)',
created_by BIGINT COMMENT '创建管理员ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_status (status),
INDEX idx_promo_type (promo_type),
INDEX idx_time (start_time, end_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='促销活动表';
```
#### promotion_skills — 活动关联Skill活动价 / 库存)
```sql
CREATE TABLE promotion_skills (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
activity_id BIGINT NOT NULL,
skill_id BIGINT NOT NULL,
original_price DECIMAL(10,2) NOT NULL COMMENT '原价快照',
promo_price DECIMAL(10,2) NOT NULL COMMENT '活动价',
-- 库存(秒杀/限量用)
total_stock INT DEFAULT -1 COMMENT '活动库存(-1=不限)',
sold_count INT DEFAULT 0 COMMENT '已售数量',
sort_order INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (activity_id) REFERENCES promotion_activities(id),
UNIQUE KEY uk_activity_skill (activity_id, skill_id),
INDEX idx_skill_id (skill_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动Skill关联表';
```
#### flash_sale_sessions — 秒杀场次(可选,秒杀专用)
```sql
CREATE TABLE flash_sale_sessions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
activity_id BIGINT NOT NULL,
session_name VARCHAR(100) COMMENT '场次名(如: 10点场/14点场)',
start_time DATETIME NOT NULL,
end_time DATETIME NOT NULL,
status ENUM('pending','active','ended') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (activity_id) REFERENCES promotion_activities(id),
INDEX idx_activity_id (activity_id),
INDEX idx_time (start_time, end_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='秒杀场次表';
```
### 3.3 后端模块设计
```
module/promotion/
├── controller/
│ ├── PromotionController.java # 用户端: 活动列表/详情/活动价查询
│ └── AdminPromotionController.java # 管理端: 活动CRUD/关联Skill/统计
├── dto/
│ ├── PromotionCreateDTO.java
│ ├── PromotionUpdateDTO.java
│ └── PromotionSkillDTO.java # 添加活动Skill
├── entity/
│ ├── PromotionActivity.java
│ ├── PromotionSkill.java
│ └── FlashSaleSession.java
├── repository/
│ ├── PromotionActivityRepository.java
│ ├── PromotionSkillRepository.java
│ └── FlashSaleSessionRepository.java
├── service/
│ ├── PromotionService.java
│ └── impl/PromotionServiceImpl.java
├── vo/
│ ├── PromotionActivityVO.java
│ ├── PromotionSkillVO.java
│ └── FlashSaleVO.java
└── task/
├── PromotionStatusTask.java # 定时更新活动状态(pending→active→ended)
└── FlashSaleWarmUpTask.java # 秒杀预热: 库存加载到Redis
```
### 3.4 核心 API 设计
| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| GET | `/api/promotions` | 进行中的活动列表 | 公开 |
| GET | `/api/promotions/{id}` | 活动详情(含活动Skill列表+活动价) | 公开 |
| GET | `/api/promotions/skill/{skillId}` | 查询某Skill当前参与的活动(有则返回活动价) | 公开 |
| GET | `/api/promotions/flash-sales` | 秒杀场次列表(含倒计时) | 公开 |
| — | — | **管理端** | — |
| POST | `/api/admin/promotions` | 创建促销活动 | ADMIN |
| PUT | `/api/admin/promotions/{id}` | 编辑活动 | ADMIN |
| PUT | `/api/admin/promotions/{id}/status` | 手动开始/暂停/结束 | ADMIN |
| POST | `/api/admin/promotions/{id}/skills` | 批量添加活动Skill(设置活动价) | ADMIN |
| DELETE | `/api/admin/promotions/{id}/skills/{skillId}` | 移除活动Skill | ADMIN |
| GET | `/api/admin/promotions` | 活动列表(分页+筛选) | ADMIN |
| GET | `/api/admin/promotions/{id}/stats` | 活动数据(浏览/下单/成交) | ADMIN |
### 3.5 核心业务逻辑
#### Skill详情页 — 活动价展示
```
SkillService.getDetail(skillId):
1. 查 skills 表获取原价
2. 查 promotion_skills JOIN promotion_activities
WHERE skill_id=? AND status='active' AND NOW() BETWEEN start_time AND end_time
3. 若命中活动 → 返回 promoPrice + 活动标签 + 倒计时endTime
4. 优先级: 秒杀 > 限时折扣 > 满减同一Skill只取优先级最高的活动
```
#### 下单时活动价锁定(改造 OrderServiceImpl.createOrder
```
1. 创建订单时,检查 skillIds 是否命中活动
2. 若命中:
a. 校验活动状态(active) + 时间范围
b. 校验库存: Redis DECR promo:stock:{activityId}:{skillId}
c. 使用 promo_price 替代 skill.price 计算 totalAmount
d. order_items 新增字段:
- activity_id BIGINT COMMENT '活动ID'
- promo_price DECIMAL(10,2) COMMENT '活动价快照'
3. 若未命中活动 → 原逻辑不变
— 注: 活动价与优惠券可叠加(视业务决策,可配置互斥)
```
#### 秒杀流程(高并发防护)
```
1. 预热(活动开始前5分钟):
FlashSaleWarmUpTask → Redis SET promo:stock:{activityId}:{skillId} = total_stock
2. 秒杀下单:
a. 前端按钮 → POST /api/orders (带 activityId)
b. 后端先 Redis 校验:
- 防刷: SET NX promo:lock:{userId}:{activityId}:{skillId} EX 5 (5秒内不可重复)
- 扣库存: DECR promo:stock:{activityId}:{skillId}
-< 0 INCR 回补 返回"已售罄"
c. Redis通过后 → 发送 MQ 异步创建订单
d. 前端轮询订单状态 or WebSocket推送
3. 补偿:
- 订单超时取消/退款 → INCR 回补 Redis 库存
- DB sold_count 定时与 Redis 同步校正
```
#### 定时任务 — 活动状态流转
```
PromotionStatusTask (@Scheduled fixedRate=60000):
1. UPDATE promotion_activities SET status='active'
WHERE status='pending' AND start_time <= NOW()
2. UPDATE promotion_activities SET status='ended'
WHERE status='active' AND end_time < NOW()
3. 活动结束后清理 Redis 库存缓存
```
### 3.6 Redis Key 设计
| Key | 说明 | TTL |
|-----|------|-----|
| `promo:stock:{activityId}:{skillId}` | 秒杀/限量库存 | 活动结束后清理 |
| `promo:lock:{userId}:{activityId}:{skillId}` | 秒杀防刷锁 | 5秒 |
| `promo:active:list` | 进行中活动列表缓存 | 1分钟 |
| `promo:skill:{skillId}` | Skill当前活动价缓存 | 1分钟 |
---
## 四、订单模块改造
### 4.1 orders 表新增字段
```sql
ALTER TABLE orders ADD COLUMN coupon_id BIGINT COMMENT '使用的优惠券ID' AFTER points_deduct_amount;
ALTER TABLE orders ADD COLUMN coupon_deduct_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT '优惠券抵扣金额' AFTER coupon_id;
```
### 4.2 order_items 表新增字段
```sql
ALTER TABLE order_items ADD COLUMN activity_id BIGINT COMMENT '促销活动ID' AFTER skill_cover;
ALTER TABLE order_items ADD COLUMN promo_price DECIMAL(10,2) COMMENT '活动价快照' AFTER activity_id;
```
### 4.3 previewOrder 改造
```
入参新增: Long couponId, Long activityId(可选)
计算顺序:
1. 获取Skill价格 → 检查是否命中活动 → 用活动价替代原价
2. 计算 totalAmount (可能已是活动价)
3. 若有 couponId → 计算优惠券抵扣 → couponDeductAmount
4. afterCouponAmount = totalAmount - couponDeductAmount
5. 计算积分抵扣(基于 afterCouponAmount)
6. cashAmount = afterCouponAmount - pointsDeductAmount
返回 OrderPreviewVO 新增:
- couponDeductAmount
- couponName (所用券名)
- activityName (活动名)
- originalTotalAmount (原总价,用于前端划线价)
- savedAmount (总共节省金额)
```
### 4.4 createOrder 改造
```
入参新增: Long couponId
核心流程:
1~3. 原有校验 + 活动价替代 + 券核销
4. 重新计算: totalAmount → couponDeduct → pointsDeduct → cashAmount
5. 写入 orders (含 coupon_id, coupon_deduct_amount)
6. 写入 order_items (含 activity_id, promo_price)
7. 冻结积分 + 发MQ
```
---
## 五、前端方案
### 5.1 技术栈
- Vue 3 + Ant Design Vue + Element Plus
- Pinia 状态管理
- Axios 请求
### 5.2 新增页面/组件
#### 用户端
| 路由 | 文件 | 说明 |
|------|------|------|
| `/user/coupons` | `views/user/coupons.vue` | 我的优惠券(Tab: 未使用/已使用/已过期) |
| `/coupons` | `views/coupon/list.vue` | 领券中心(可领券列表+一键领取) |
| `/promotions` | `views/promotion/list.vue` | 活动专区(进行中活动列表) |
| `/promotions/:id` | `views/promotion/detail.vue` | 活动详情(活动Skill列表+活动价) |
| `/flash-sale` | `views/promotion/flash-sale.vue` | 秒杀专区(场次+倒计时+秒杀按钮) |
| — | `components/CouponCard.vue` | 优惠券卡片组件(领取/使用) |
| — | `components/CouponSelect.vue` | 下单时券选择弹窗 |
| — | `components/PromoTag.vue` | 活动价标签(Skill卡片/详情页) |
| — | `components/CountdownTimer.vue` | 倒计时组件 |
#### 管理端
| 路由 | 文件 | 说明 |
|------|------|------|
| `/admin/coupons` | `views/admin/coupons.vue` | 券模板管理(CRUD+状态切换) |
| `/admin/coupons/:id` | `views/admin/coupon-detail.vue` | 券统计详情(发放/使用率) |
| `/admin/promotions` | `views/admin/promotions.vue` | 促销活动管理(CRUD+关联Skill) |
| `/admin/promotions/:id` | `views/admin/promotion-detail.vue` | 活动详情(关联Skill管理+数据统计) |
### 5.3 现有页面改造
#### Skill详情页 (`views/skill/detail.vue`)
```
改造点:
1. 价格区域: 原价 + 活动价(划线价) + 活动标签 + 倒计时
2. 购买弹窗: 增加"选择优惠券"入口 → CouponSelect弹窗
3. 价格预览: 显示 原价 - 活动优惠 - 券优惠 - 积分抵扣 = 实付金额
```
#### 订单详情页 (`views/order/detail.vue`)
```
改造点:
1. 金额明细: 增加"优惠券抵扣"行 + "活动优惠"行
2. 显示使用的券名称 + 活动名称
```
#### 首页 (`views/home/index.vue`)
```
改造点:
1. 新增"限时秒杀"模块(横向滚动,显示倒计时+秒杀价)
2. 新增"领券中心"入口(悬浮icon或Banner位)
3. 活动Banner(复用现有轮播图linkType新增 promotion 类型)
```
### 5.4 API Service 新增
```javascript
// service/couponApi.js
export const couponApi = {
// 用户端
getAvailable: () => get('/api/coupons/available'),
receiveCoupon: (templateId) => post(`/api/coupons/${templateId}/receive`),
getMyCoupons: (params) => get('/api/coupons/my', { params }),
getUsableCoupons: (skillIds) => get('/api/coupons/usable', { params: { skillIds: skillIds.join(',') } }),
calcDiscount: (data) => post('/api/coupons/calc', data),
}
// service/promotionApi.js
export const promotionApi = {
// 用户端
getActiveList: () => get('/api/promotions'),
getDetail: (id) => get(`/api/promotions/${id}`),
getSkillPromo: (skillId) => get(`/api/promotions/skill/${skillId}`),
getFlashSales: () => get('/api/promotions/flash-sales'),
}
```
### 5.5 关键交互流程
#### 领券流程
```
领券中心页面:
1. 加载可领券列表(带已领/已抢光状态)
2. 点击"立即领取" → loading + 防抖
3. 成功 → 动画(券飞入钱包) + toast提示
4. 失败 → 提示(已领完/超过限领数/已领过)
```
#### 下单选券流程
```
Skill详情页 → 点击购买:
1. 调用 previewOrder(不带券) → 显示基础价格
2. 底部显示"有N张可用优惠券" → 点击展开 CouponSelect
3. CouponSelect: 显示可用券列表(按优惠金额排序,最优推荐)
4. 选中券 → 重新调用 previewOrder(带couponId) → 更新价格预览
5. 确认下单 → createOrder(带couponId)
```
#### 秒杀流程
```
秒杀专区页:
1. 显示场次列表 + 倒计时(距开始/距结束)
2. 未开始: 按钮灰色"即将开始" + 倒计时
3. 进行中: 按钮红色"立即抢购" / 已售罄灰色
4. 点击抢购 → 弹窗确认 → POST 下单
5. 后端返回"排队中" → 前端轮询订单状态(每2秒)
6. 成功 → 跳转支付页 / 失败 → 提示"未抢到"
```
---
## 六、实施计划
### Phase 1: 优惠券系统7-8天
| 天数 | 后端任务 | 前端任务 |
|------|---------|---------|
| D1 | 建表 + Entity/Repository | — |
| D2 | CouponService(创建模板/领券/查可用券) | — |
| D3 | CouponService(优惠计算/核销/退券) + Redis库存 | CouponCard组件 + 领券中心页 |
| D4 | AdminCouponController + 管理端API | 我的优惠券页 |
| D5 | OrderService改造(previewOrder/createOrder增加券) | CouponSelect选券弹窗 |
| D6 | 取消退款退券 + 定时过期任务 | Skill详情页+订单详情改造 |
| D7 | 联调 + 边界测试 | 管理端券模板管理页 |
| D8 | 压测领券并发 + 修复 | 联调 + UI打磨 |
### Phase 2: 活动系统 — 限时折扣5-6天
| 天数 | 后端任务 | 前端任务 |
|------|---------|---------|
| D9 | 建表 + Entity/Repository | — |
| D10 | PromotionService(CRUD/状态流转/活动价查询) | 活动专区列表页 |
| D11 | Skill详情接入活动价 + Order改造 | 活动详情页 + PromoTag组件 |
| D12 | AdminPromotionController + 关联Skill | Skill详情页活动价展示 |
| D13 | 定时任务(状态流转) + 联调 | 管理端活动管理页 |
| D14 | 集成测试 + 修复 | 首页活动模块 + 联调 |
### Phase 3: 秒杀系统4-5天可独立排期
| 天数 | 后端任务 | 前端任务 |
|------|---------|---------|
| D15 | 秒杀场次表 + 预热任务 + Redis库存 | — |
| D16 | 秒杀下单(Redis防刷+MQ异步) | 秒杀专区页 + 倒计时组件 |
| D17 | 补偿(取消回补库存) + 库存校正 | 秒杀下单交互(轮询/状态) |
| D18 | 压测(JMeter模拟并发抢购) | 联调 + 异常处理 |
| D19 | 修复 + 优化 | UI打磨 + 最终联调 |
**总计:约 17-19 天**优惠券8天 + 限时折扣6天 + 秒杀5天
---
## 七、注意事项
### 安全
- **领券防刷**: Redis分布式锁 + 用户限领校验(DB唯一约束兜底)
- **金额服务端计算**: 前端只展示,**所有优惠金额在后端重新计算**,不信任客户端传入
- **库存超卖防护**: Redis原子操作(DECR) + DB乐观锁(sold_count CAS)双重保障
- **优惠叠加控制**: 默认活动价+优惠券可叠加,但需配置互斥开关
### 性能
- **热点数据缓存**: 活动列表/活动价/券库存走RedisDB只做持久化
- **秒杀异步**: 下单请求先入MQ异步消费创建订单前端轮询结果
- **批量操作**: 券过期/活动状态更新分批执行(每批1000条)
### 一致性
- **券库存**: Redis预扣 → DB写入 → 失败回补Redis最终一致
- **活动库存**: Redis + DB双写定时任务校正偏差
- **退款退券**: 事务内同步处理(券退回 + 库存回补)
### 与现有系统集成
- **会员折扣**: 会员等级折扣与活动价/优惠券的优先级: 活动价 > 优惠券 > 会员折扣
- **积分抵扣**: 积分在最终实付金额(扣除活动优惠和券优惠后)上计算
- **MQ补偿**: 复用现有 CompensationService秒杀/券相关MQ失败自动写入补偿表
---
*本文档将随开发进度持续更新*