2026-08-31-coupon-family-based.md 4.9 KB

优惠券全链路改为家庭维度 — 实施计划

设计稿: specs/2026-08-31-coupon-family-based-design.md 状态: 待执行 预计工时: ~2h(后端主导,前端轻量)


Task 1 — 数据库迁移 + schema.sql(迁移 269)

文件: DatabaseInitializer.java, schema.sql 验证: mvn clean compile

// 迁移269: 创建 family_coupon + family_coupon_grant_log 表
// (优惠券全链路改为家庭维度:2026-08-31)

Schema.sql 末尾追加(在 coupon_grant_log CREATE 语句之后):

CREATE TABLE IF NOT EXISTS family_coupon (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    family_id BIGINT NOT NULL COMMENT '所属家庭ID',
    coupon_id BIGINT NOT NULL COMMENT '券模板ID',
    status VARCHAR(16) DEFAULT 'AVAILABLE' COMMENT 'AVAILABLE/USED',
    received_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    used_at DATETIME,
    order_id BIGINT COMMENT '核销时写入的订单号',
    INDEX idx_family_coupon (family_id, coupon_id),
    INDEX idx_status (status),
    INDEX idx_order_id (order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭优惠券表';

CREATE TABLE IF NOT EXISTS family_coupon_grant_log (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    family_id BIGINT NOT NULL COMMENT '受赠家庭ID',
    coupon_id BIGINT NOT NULL COMMENT '券模板ID',
    grant_type VARCHAR(16) NOT NULL COMMENT 'JOIN/PERIODIC/POPULATION/EXCHANGE/CF_EXCHANGE',
    period VARCHAR(16) COMMENT '周期标识(YYYY-MM或YYYY-Qn),PERIODIC防重用',
    quantity INT DEFAULT 1 COMMENT '发放数量',
    source VARCHAR(64) COMMENT '触发来源(订单号/成员ID等)',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_family_grant (family_id, coupon_id, grant_type, period),
    INDEX idx_coupon (coupon_id),
    INDEX idx_family (family_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭优惠券发放流水表';

Task 2 — Entity + Mapper(4个新文件)

文件 包 说明
entity/FamilyCoupon.java com.etotem.cfc.entity @TableName("family_coupon")
entity/FamilyCouponGrantLog.java com.etotem.cfc.entity @TableName("family_coupon_grant_log")
mapper/FamilyCouponMapper.java com.etotem.cfc.mapper 继承 BaseMapper<FamilyCoupon>,加 @Mapper
mapper/FamilyCouponGrantLogMapper.java com.etotem.cfc.mapper 同上

Task 3 — CouponService 重构(核心)

文件: service/CouponService.java

改动要点:

  1. 注入 FamilyCouponMapper, FamilyCouponGrantLogMapper, UserMapper
  2. 保留所有公开方法签名不变
  3. 内部实现全部替换为读 family_coupon
  4. 新增私有方法 getFamilyId(Long userId) 做 userId→familyId 转换
  5. 新增 public 方法(供外部调用方用):
    • grantFamily(Long familyId, Long couponId, String grantType, String period, String source)
    • grantFamilyJoinCoupons(Long familyId, String levelCode, String source)
    • grantFamilyPopulationCoupons(Long familyId, Long memberId)
    • listActiveMemberFamilyIds(String levelCode)

不删的旧方法(保留供降级兼容,但停止使用):

  • issueToUser — 标记 @Deprecated,不动实现

Task 4 — 6 条发放路径改写

# 文件 改动
1 controller/admin/AdminCouponController.java /issue 参数改为 familyIds;新增 /issue-family
2 service/MembershipService.java grantJoinCoupons(adminUserId,...) → grantFamilyJoinCoupons(order.getFamilyId(),...)
3 service/FamilyMemberService.java grantPopulationCoupons(targetUserId,...) → grantFamilyPopulationCoupons(familyId, member.getId())
4 task/CouponGrantTask.java 改为调用 listActiveMemberFamilyIds + grantFamily
5 service/PointsExchangeService.java 取 familyMemberMapper.selectOne(userId).getFamilyId(),调 grantFamily
6 service/FamilyPlatformPointsService.java 直接调 grantFamily(familyId, ...)

Task 5 — CouponController(小程序端,内部改实现)

文件: controller/CouponController.java

不改路由、不改响应字段。内部 userId 先解析 userMapper.selectById(userId).getFamilyId(),再调 CouponService 新方法。 apply 方法中增加归属校验。


Task 6 — 管理端前端(cfc-web)

文件 改动
api/coupon.js 新增 issueFamilyCoupon、getFamilyCouponGrantLog
views/Families.vue 下拉菜单加「发放优惠券」按钮 + 弹窗
views/admin/CouponManagement.vue 批量发放弹窗改为家庭ID列表
views/admin/CouponGrantLog.vue 搜索框加「家庭ID」;列表显示家庭名

Task 7 — API_REFERENCE.md 更新

追加 /api/admin/coupon/issue-family 和 /api/coupon/* 的家庭维度说明。


Task 8 — 编译验证 + 提交

cd cfc-backend && mvn clean compile
git add -A && git commit && git push