676 lines
27 KiB
Markdown
676 lines
27 KiB
Markdown
# 优惠券系统 & 活动系统 — 前后端开发方案
|
||
|
||
> 基于现有项目架构: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)双重保障
|
||
- **优惠叠加控制**: 默认活动价+优惠券可叠加,但需配置互斥开关
|
||
|
||
### 性能
|
||
- **热点数据缓存**: 活动列表/活动价/券库存走Redis,DB只做持久化
|
||
- **秒杀异步**: 下单请求先入MQ,异步消费创建订单,前端轮询结果
|
||
- **批量操作**: 券过期/活动状态更新分批执行(每批1000条)
|
||
|
||
### 一致性
|
||
- **券库存**: Redis预扣 → DB写入 → 失败回补Redis(最终一致)
|
||
- **活动库存**: Redis + DB双写,定时任务校正偏差
|
||
- **退款退券**: 事务内同步处理(券退回 + 库存回补)
|
||
|
||
### 与现有系统集成
|
||
- **会员折扣**: 会员等级折扣与活动价/优惠券的优先级: 活动价 > 优惠券 > 会员折扣
|
||
- **积分抵扣**: 积分在最终实付金额(扣除活动优惠和券优惠后)上计算
|
||
- **MQ补偿**: 复用现有 CompensationService,秒杀/券相关MQ失败自动写入补偿表
|
||
|
||
---
|
||
|
||
*本文档将随开发进度持续更新*
|