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

27 KiB
Raw Blame 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 — 优惠券模板(管理员创建)

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 — 用户持有的优惠券

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 — 促销活动主表

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活动价 / 库存)

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 — 秒杀场次(可选,秒杀专用)

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 表新增字段

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 表新增字段

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 新增

// 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失败自动写入补偿表

本文档将随开发进度持续更新