设计稿: specs/2026-08-31-coupon-family-based-design.md
状态: 待执行
预计工时: ~2h(后端主导,前端轻量)
文件: 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='家庭优惠券发放流水表';
| 文件 | 包 | 说明 |
|---|---|---|
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 |
同上 |
文件: service/CouponService.java
改动要点:
FamilyCouponMapper, FamilyCouponGrantLogMapper, UserMapperfamily_coupongetFamilyId(Long userId) 做 userId→familyId 转换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,不动实现| # | 文件 | 改动 |
|---|---|---|
| 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, ...) |
文件: controller/CouponController.java
不改路由、不改响应字段。内部 userId 先解析 userMapper.selectById(userId).getFamilyId(),再调 CouponService 新方法。
apply 方法中增加归属校验。
| 文件 | 改动 |
|---|---|
api/coupon.js |
新增 issueFamilyCoupon、getFamilyCouponGrantLog |
views/Families.vue |
下拉菜单加「发放优惠券」按钮 + 弹窗 |
views/admin/CouponManagement.vue |
批量发放弹窗改为家庭ID列表 |
views/admin/CouponGrantLog.vue |
搜索框加「家庭ID」;列表显示家庭名 |
追加 /api/admin/coupon/issue-family 和 /api/coupon/* 的家庭维度说明。
cd cfc-backend && mvn clean compile
git add -A && git commit && git push