2026-08-03-product-gift-membership-design.md 6.3 KB

商品赠送会员功能设计

  • 日期: 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

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 规则接口)