|
|
@@ -0,0 +1,197 @@
|
|
|
+# 家庭平台积分(CF值)体系设计
|
|
|
+
|
|
|
+> 日期:2026-08-12
|
|
|
+> 分支:cfclub
|
|
|
+> 状态:待评审
|
|
|
+
|
|
|
+## 1. 背景与目标
|
|
|
+
|
|
|
+现有系统有三套并行积分体系,职责不清、兑换能力空壳:
|
|
|
+
|
|
|
+| 体系 | 维度 | 发放 | 消费 |
|
|
|
+|------|------|------|------|
|
|
|
+| 孩子积分 `PointsService` | 孩子(family_member) | 任务/打卡/签到/文章 | 心愿兑换、优惠券兑换、兑换商品 |
|
|
|
+| 家长积分 `User.totalPoints` | 家长(user) | 无自动发放 | 商品订单积分抵扣(1积分=¥0.01) |
|
|
|
+| CF值 `PlatformPointsService` | 用户(user_id 唯一键) | 商品订单支付时、推荐分润、活动签到 | 仅提现;`spend()` 零调用 |
|
|
|
+
|
|
|
+**用户需求**(已确认):
|
|
|
+
|
|
|
+1. 平台积分(CF值)是给**整个家庭**的共享池;家庭内部积分(家长积分+孩子积分)在**每个人身上**,两者是不同维度。
|
|
|
+2. 商品按**实际购买金额**送积分:1元 = 1积分,可针对商品设置**积分倍数**,按实付金额计算。
|
|
|
+3. 积分可兑换优惠券(**保持现状**:孩子积分兑换,家长积分/平台CF值不参与)。
|
|
|
+4. 每个产品设置优惠券的可用积分(**现状已有**:`Coupon.pointsPrice`)。
|
|
|
+5. 平台CF值消费场景本次一并做:**会员续费抵扣 + 商品购买抵扣**。
|
|
|
+6. 旧 `user_platform_balance` 余额**一并迁入家庭池**(按 user→family 归属;无家庭自动创建)。
|
|
|
+7. 家长积分**彻底退出商品购买抵扣**(现有结算页家长积分抵扣**替换为平台CF值抵扣**),家长积分只用于家庭内部许愿兑换。
|
|
|
+
|
|
|
+## 2. 目标架构
|
|
|
+
|
|
|
+```
|
|
|
+平台CF值(家庭维度,family_platform_balance)
|
|
|
+├─ 发放:商品确认收货(实付×倍数)、推荐分润、活动签到
|
|
|
+├─ 消费:会员续费抵扣、商品购买抵扣、提现
|
|
|
+└─ 原语:earn / spend / freeze / unfreeze / getBalance / getLogs / adjust
|
|
|
+
|
|
|
+家庭内部积分(个人维度)
|
|
|
+├─ 孩子(family_member):任务/打卡 → 心愿、兑换商品、兑换优惠券(现状保持)
|
|
|
+└─ 家长(user.total_points):仅家庭内部许愿兑换(商品抵扣入口移除)
|
|
|
+```
|
|
|
+
|
|
|
+## 3. 数据库设计
|
|
|
+
|
|
|
+### 3.1 新表 `family_platform_balance`
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE family_platform_balance (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
|
+ family_id BIGINT NOT NULL,
|
|
|
+ total_earned INT NOT NULL DEFAULT 0, -- 累计获得
|
|
|
+ available INT NOT NULL DEFAULT 0, -- 可用
|
|
|
+ frozen INT NOT NULL DEFAULT 0, -- 提现冻结
|
|
|
+ withdrawn INT NOT NULL DEFAULT 0, -- 已提现
|
|
|
+ exchanged INT NOT NULL DEFAULT 0, -- 已消费(抵扣/兑换)
|
|
|
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
+ UNIQUE KEY uk_family (family_id)
|
|
|
+);
|
|
|
+```
|
|
|
+
|
|
|
+### 3.2 新表 `family_platform_balance_log`
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE family_platform_balance_log (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
|
+ family_id BIGINT NOT NULL,
|
|
|
+ type VARCHAR(20) NOT NULL, -- earn/spend/freeze/unfreeze/withdraw/adjust
|
|
|
+ amount INT NOT NULL,
|
|
|
+ balance_after INT NOT NULL,
|
|
|
+ ref_type VARCHAR(50) NOT NULL, -- product_order/referral_dist/activity/withdrawal/adjust/refund
|
|
|
+ ref_id BIGINT,
|
|
|
+ remark VARCHAR(255),
|
|
|
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ KEY idx_family (family_id),
|
|
|
+ KEY idx_ref (ref_type, ref_id)
|
|
|
+);
|
|
|
+```
|
|
|
+
|
|
|
+### 3.3 修改 `products`
|
|
|
+
|
|
|
+```sql
|
|
|
+ALTER TABLE products ADD COLUMN points_multiplier DECIMAL(4,2) NOT NULL DEFAULT 1.00 COMMENT '平台积分倍数,1元=1积分×倍数';
|
|
|
+```
|
|
|
+
|
|
|
+### 3.4 迁移
|
|
|
+
|
|
|
+- 一次性迁移:`user_platform_balance` 按 user.family_id 归属累加进 `family_platform_balance`;user 无 familyId 的自动创建家庭(无则 new)。
|
|
|
+- 迁移记录落 `family_platform_balance_log`(refType=`migration`,remark=来源 userId 列表),避免重复。
|
|
|
+- 迁移后 `user_platform_balance` 表**保留**(只读历史),新发放入家庭池。
|
|
|
+
|
|
|
+## 4. 后端实现
|
|
|
+
|
|
|
+### 4.1 `FamilyPlatformPointsService`(新)
|
|
|
+
|
|
|
+仿 `PlatformPointsService` 实现,键为 familyId:
|
|
|
+
|
|
|
+- `earn(familyId, amount, refType, refId, remark)`:total_earned+available,写流水,refId 幂等
|
|
|
+- `spend(familyId, amount, refType, refId, remark)`:available→exchanged,余额不足抛错
|
|
|
+- `freeze(familyId, amount, refId)` / `unfreeze(familyId, amount, refId)` / `confirmWithdraw(familyId, amount, refId)`:提现
|
|
|
+- `adjust(familyId, amount, remark)`:管理端手动调整(正负),写 adjust 流水
|
|
|
+- `getBalance(familyId)` / `getLogs(familyId, page, size)` / `getWithdrawable(familyId)`
|
|
|
+- 并发安全:`SELECT ... FOR UPDATE` 锁行
|
|
|
+
|
|
|
+### 4.2 商品送积分(核心)
|
|
|
+
|
|
|
+触发点:`ProductOrderService.confirmReceive()`(L787-813)用户确认收货,`status: pending_receipt → completed` 成功分支之后。
|
|
|
+
|
|
|
+计算:`积分 = floor(moneyAmount / 100) × points_multiplier`(向下取整)
|
|
|
+
|
|
|
+- `moneyAmount`:订单实付金额(分),确认收货时已定
|
|
|
+- `points_multiplier`:从 `products` 表按 `productId` 取
|
|
|
+- 发放:`familyPlatformPointsService.earn(familyId, points, "product_order", orderId, "购买商品: {productName}")`
|
|
|
+- **幂等**:earn 按 (ref_type, ref_id) 查流水,存在则跳过
|
|
|
+- 订单需能拿到 familyId:`ProductOrder.buyerId` → user.familyId
|
|
|
+
|
|
|
+### 4.3 商品购买抵扣(替换家长积分抵扣)
|
|
|
+
|
|
|
+- 结算页 `checkout.vue`:积分抵扣输入从"家长积分"改为"家庭平台积分"
|
|
|
+- 下单入参 `CreateProductOrderDTO.pointsUsed` 语义不变(数量)
|
|
|
+- `ProductOrderService`:支付成功分支的 `deductUserPoints` 改为 `familyPlatformPointsService.spend(familyId, pointsUsed, "product_order", orderId, "商品购买积分抵扣")`
|
|
|
+- 支付失败/退款:`refundUserPoints` 改为 `earn` 回补(refType=`refund`)
|
|
|
+- `User.totalPoints` 商品抵扣路径删除(保留字段,仅许愿兑换使用)
|
|
|
+
|
|
|
+### 4.4 会员续费抵扣
|
|
|
+
|
|
|
+`MemberSubscriptionService.renew()`:增加可选平台积分抵扣参数
|
|
|
+
|
|
|
+- 抵扣比例可配置:sys_config `membership_points_deduct_ratio`(默认如 1000 = 10%)
|
|
|
+- 抵扣后实付 = max(0, 原价 - 抵扣额)
|
|
|
+- 成功即 `spend(familyId, points, "membership", subId, "会员续费抵扣")`
|
|
|
+
|
|
|
+### 4.5 提现
|
|
|
+
|
|
|
+`WithdrawalService` 改走家庭池(freeze/confirmWithdraw/unfreeze 换服务),新增提现记录含 familyId。
|
|
|
+
|
|
|
+### 4.6 管理端
|
|
|
+
|
|
|
+- `AdminFamilyBalanceController`:`/api/admin/family-platform-balance/*`
|
|
|
+ - `list`(分页:family_id/余额/流水摘要)
|
|
|
+ - `logs`(family_id + type/refType 筛选)
|
|
|
+ - `adjust`(手动调整 + remark)
|
|
|
+- cfc-web 新增 `FamilyPlatformBalanceManage.vue` 页面(余额列表 + 流水 + 调整弹窗)
|
|
|
+
|
|
|
+### 4.7 API(小程序)
|
|
|
+
|
|
|
+- `GET /api/family-platform/balance`:家庭余额(含累计/冻结/可提现)
|
|
|
+- `GET /api/family-platform/logs`:流水分页
|
|
|
+- 商品详情接口 `ProductDTO` 增加 `pointsMultiplier` 字段(透出)
|
|
|
+
|
|
|
+## 5. 前端实现
|
|
|
+
|
|
|
+### 5.1 小程序(cfc-frontend)
|
|
|
+
|
|
|
+- `utils/api.js`:新增 `getFamilyPlatformBalance` / `getFamilyPlatformLogs`;`ProductDTO.pointsMultiplier` 透出
|
|
|
+- 商品详情页 `product-detail.vue`:价格区显示"购此商品可得 X 积分"(倍数>1 高亮"×N 倍积分")
|
|
|
+- 结算页 `checkout.vue`:积分抵扣改为家庭平台积分(余额取自新接口;1积分=¥0.01 规则沿用);显示"预计可得 X 积分"
|
|
|
+- 支付页 `payment.vue`:积分抵扣文案更新
|
|
|
+- 家庭积分页(并入 `pages/wealth/index.vue` 或新建):余额 + 流水 + 提现入口
|
|
|
+
|
|
|
+### 5.2 管理端(cfc-web)
|
|
|
+
|
|
|
+- 商品编辑表单:新增"积分倍数"输入(ProductForm)
|
|
|
+- 新增 `FamilyPlatformBalanceManage.vue`:余额列表、流水、手动调整
|
|
|
+
|
|
|
+## 6. 错误处理与边界
|
|
|
+
|
|
|
+| 场景 | 处理 |
|
|
|
+|------|------|
|
|
|
+| 积分余额不足 | 抵扣/提现抛业务异常,前端提示"平台积分不足" |
|
|
|
+| 并发抵扣 | 行锁 + 事务,二次校验余额 |
|
|
|
+| 支付失败退款 | 回补积分(refType=refund),幂等 |
|
|
|
+| 确认收货重复请求 | earn 按 refId 幂等跳过 |
|
|
|
+| 孤儿 user(无 family) | 迁移时自动创建家庭 |
|
|
|
+| 向下取整 | floor,不四舍五入(如 599元×1.5=898 积分) |
|
|
|
+
|
|
|
+## 7. 测试
|
|
|
+
|
|
|
+- 单元:积分计算(floor + 倍数)、earn/spend/freeze 余额变化、幂等防重
|
|
|
+- 集成:确认收货→积分到账;结算抵扣→支付→spend;支付失败→回补
|
|
|
+- 迁移:旧余额按 family 汇总正确、孤儿自动建家庭、不重复
|
|
|
+- E2E:商品详情积分展示、家庭积分页
|
|
|
+
|
|
|
+## 8. 实施顺序
|
|
|
+
|
|
|
+1. 数据库:两表 + products 字段 + 迁移(DatabaseInitializer + schema.sql 同步)
|
|
|
+2. `FamilyPlatformPointsService` 原语
|
|
|
+3. 商品送积分(confirmReceive 挂点)
|
|
|
+4. 商品购买抵扣替换 + 会员续费抵扣
|
|
|
+5. 提现改造
|
|
|
+6. 管理端接口 + 页面
|
|
|
+7. 小程序前端
|
|
|
+8. 测试 + 部署
|
|
|
+
|
|
|
+## 9. 明确不做(YAGNI)
|
|
|
+
|
|
|
+- 不新建"家庭内部积分"表:家长/孩子积分保持现状
|
|
|
+- 优惠券兑换不改:孩子积分兑换,平台CF值/家长积分不参与
|
|
|
+- 不做积分商城(兑换实物商品):现状 PointsExchangeProduct 已有,不动
|
|
|
+- 不迁移 `points_log`(孩子积分流水),仅迁移 `user_platform_balance`
|