# 优惠券系统 & 活动系统 — 前后端开发方案 > 基于现有项目架构: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失败自动写入补偿表 --- *本文档将随开发进度持续更新*