Ver código fonte

docs: 商品赠送会员功能设计(方案B 独立规则表)

iwt 1 mês atrás
pai
commit
b4cce529ee

+ 135 - 0
docs/superpowers/specs/2026-08-03-product-gift-membership-design.md

@@ -0,0 +1,135 @@
+# 商品赠送会员功能设计
+
+- **日期**: 2026-08-03
+- **状态**: 已确认(方案 B:独立赠送规则表)
+- **关联**: 商品购买 → 支付成功 → 自动赠送对应级别会员
+
+## 1. 需求背景
+
+运营希望指定某几个商品,用户**原价购买**后赠送会员,不同商品赠送**不同级别**的会员(如商品 A 送 FAMILY、商品 B 送 PREMIUM)。
+
+### 已确认业务规则
+
+| 决策点 | 结论 |
+|--------|------|
+| 赠送级别 | 任意有效等级(从 `membership_levels` 表下拉选择,过滤 FREE) |
+| 时长 | 不送时长,送级别;有效期按该级别年费周期(默认 365 天) |
+| 赠送对象 | **家庭创建者**(`users.member_level` 权威字段,与现有开通流程一致,全家共享) |
+| 已有会员 | 家庭已有**同级或更高级**有效会员时不送;无会员或等级更低则送(升级) |
+| 原价判定 | 无折扣无抵扣:`pointsUsed == 0 && couponId == null && discountAmount 为空` |
+| 退款 | 本期不回收会员(与测评额度逻辑一致) |
+
+## 2. 数据模型
+
+### 新表 `product_gift_rules`
+
+```sql
+CREATE TABLE IF NOT EXISTS product_gift_rules (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    product_id BIGINT NOT NULL COMMENT '关联products表',
+    level_code VARCHAR(50) NOT NULL COMMENT '赠送会员等级编码(FAMILY/PREMIUM...)',
+    status TINYINT(1) DEFAULT 1 COMMENT '1=启用 0=停用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_product_level (product_id, level_code),
+    INDEX idx_product_id (product_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品赠送会员规则';
+```
+
+- 与 `assessment_products`(测评商品扩展表)完全同构:一商品一规则(UNIQUE 约束),`status` 支持随时停用赠品活动而不动商品。
+- **新增后端文件**:
+  - `entity/ProductGiftRule.java` — MyBatis-Plus 实体(`@TableName("product_gift_rules")`)
+  - `mapper/ProductGiftRuleMapper.java` — Mapper
+  - `service/ProductGiftRuleService.java` — 规则查询/保存/停用(`@Resource` 注入,遵循项目 DI 规范)
+- **迁移**:`DatabaseInitializer.runMigrations()` 添加建表语句(幂等 `CREATE TABLE IF NOT EXISTS` + log.info),并同步 `schema.sql`。
+- **管理端接口**:
+  - `POST /api/admin/product-gift-rule/save`(productId, levelCode, status)— 新增/更新规则
+  - 查询随商品详情接口返回(见 §4)
+
+## 3. 赠送触发(后端)
+
+### 收口点:`ProductOrderService.handlePaymentSuccess(orderNo, transactionId)`
+
+在现有"测评商品发放额度"逻辑之后追加赠送会员逻辑(两者模式完全一致,失败仅记日志、不阻塞支付流程):
+
+```
+支付成功
+  → 查 product_gift_rules(productId 匹配 && status=1)
+  → 校验"原价购买":order.pointsUsed==0 && order.couponId==null
+      && (order.discountAmount==null || order.discountAmount==0)
+  → 调 membershipService.grantMembershipByGift(...)
+  → 异常捕获:log.error,不抛错
+```
+
+### 新方法:`MembershipService.grantMembershipByGift`
+
+仿照 `processPaymentCallback` 核心(写 FamilyMembership + 更新 users + 写升级记录),差异点:
+
+| 项目 | 行为 |
+|------|------|
+| 参数 | `(familyId, userId, levelCode, sourceOrderNo, transactionId)` |
+| 目标用户 | 家庭创建者(查 family.creatorId;与现有流程一致) |
+| 前置校验 | 家庭创建者无有效会员,或有效等级**低于**赠送等级(等级高低按 `membership_levels.id` 排序)→ 否则跳过 |
+| 有效期 | 按级别年费周期:默认 `+365 天`(沿用 `upgradeToFamily` 的 365 天常量逻辑) |
+| 订阅记录 | 插入 `family_memberships`(paymentStatus 沿用现有 'paid' 值,不引入新枚举) |
+| 升级记录 | `member_upgrade_records.upgrade_type = 'gift'`(新增类型) |
+| 佣金 | **跳过**佣金结算(赠送非销售) |
+
+### 等级高低比较
+
+`membership_levels` 表按 `id` 自增顺序即等级从低到高(FREE < FAMILY < PREMIUM < PROVIDER)。比较逻辑:
+
+```
+当前等级 = getMemberLevel(userId)(校验过期,返回 FREE 视为无有效会员)
+当前等级 id >= 赠送等级 id → 跳过不送
+否则 → 赠送(升级)
+```
+
+## 4. 商品详情展示
+
+- `ProductService` 详情组装时查询 `product_gift_rules`(status=1),将 `giftRule.levelCode` + 等级名附带进详情 DTO。
+- 小程序 `pages/discover-detail/product-detail/product-detail.vue` 显示"购买即送 XX 会员"徽标(等级名来自 membership_levels 或前端映射)。
+
+## 5. 管理端配置(cfc-web)
+
+`ProductEdit.vue` 新增"赠送会员"区块:
+- 等级下拉:数据来自 `POST /api/membership/levels`,过滤 `FREE`,显示 level_name
+- 启用开关:控制 `status`(1=启用 0=停用)
+- 保存:调 `POST /api/admin/product-gift-rule/save`
+
+## 6. 边界情况
+
+| 场景 | 行为 |
+|------|------|
+| 家庭已有同级/更高级会员 | 不送(订单正常完成) |
+| 家庭无会员 / 等级更低 | 赠送(低等级则升级) |
+| 用积分/优惠券/折扣购买 | 不送(非原价) |
+| 赠品规则停用(status=0) | 不送 |
+| 赠送失败(异常) | 记日志,支付流程不受影响 |
+| 退款 | 本期不回收会员(与测评额度逻辑一致) |
+
+## 7. 验证
+
+- 后端:`mvn clean compile` 通过(项目规范)
+- 前端:`npm run build:mp-weixin` 构建通过;cfc-web 构建通过
+- 部署:`mvn clean package -DskipTests`(部署脚本命令)通过
+
+## 8. 涉及文件清单
+
+### 后端(cfc-backend)
+- 新增 `entity/ProductGiftRule.java`
+- 新增 `mapper/ProductGiftRuleMapper.java`
+- 新增 `service/ProductGiftRuleService.java`
+- 修改 `config/DatabaseInitializer.java`(建表迁移)
+- 修改 `resources/schema.sql`(同步建表)
+- 修改 `service/ProductOrderService.java`(handlePaymentSuccess 追加赠送逻辑)
+- 修改 `service/MembershipService.java`(新增 grantMembershipByGift)
+- 新增/修改 `controller/admin/` 规则管理接口
+- 修改 `service/ProductService.java`(详情附带 giftRule)
+
+### 前端(cfc-frontend)
+- 修改 `pages/discover-detail/product-detail/product-detail.vue`(赠送徽标)
+
+### 管理端(cfc-web)
+- 修改 `src/views/admin/ProductEdit.vue`(赠送会员区块)
+- 修改 `src/api/` 或对应 API 封装(save 规则接口)