# 浠艾福(XAF)后端接口参考文档 > **维护规范:** > 1. 新增接口前必须先查阅本文档,确认是否已有可复用或可修改的接口 > 2. 有相似功能但需调整 → 评估修改成本,必要时新增(标注依赖) > 3. 实在没有 → 才新增接口,并在此文档同步记录 > 4. 废弃接口保留注释,不直接删除,待确认无调用后清理 --- ## 一、接口规范 ### 1.1 统一约定 | 项目 | 规范 | |------|------| | 请求方法 | 统一 `@PostMapping`,禁止 `@GetMapping/@PutMapping/@DeleteMapping`(仅支付回调等第三方 webhook 例外) | | 响应格式 | `Result` — `{ code, message, data }` | | 认证 | JWT Bearer Token,Header: `Authorization: Bearer {token}` | | 角色校验 | Controller 内手动检查 `@RequestAttribute("role")` | | DI | `@Resource`,字段名与 Bean Name 一致 | | 公开路径(免鉴权)| `/api/auth/*`、`/api/config/public/*`、`/api/media/upload`、`/api/articles/*`(部分)| ### 1.2 新增接口检查清单 ```bash # 1. 搜索现有相似接口 grep -rn "功能关键词" cfc-backend/src/main/java/com/etotem/cfc/controller/ --include="*.java" # 2. 检查路由冲突 grep -rn '@PostMapping("' cfc-backend/src/main/java/com/etotem/cfc/controller/ | grep -oP '@\w+Mapping\("\K[^"]*' | sort -u # 3. 检查 Bean 命名冲突(新增 Service/Controller 时) find cfc-backend/src/main/java -name "*XxxService.java" -o -name "*XxxController.java" | xargs grep -l "class Xxx" ``` --- ## 二、Controller 总览 | Controller | 路由前缀 | 说明 | 废弃状态 | |------------|----------|------|----------| | `AuthController` | `/api/auth` | 登录/注册/角色切换 | — | | `UserController` | `/api/user` | 用户信息/头像/吉祥物 | — | | `FamilyController` | `/api/family` | 家庭基础操作 | 部分废弃见下 | | `FamilyMembersController` | `/api/family/member` | 家庭成员 CRUD/切换/可见性 | — | | `FamilyInviteController` | `/api/family/invite` | 邀请码/二维码/申请加入 | — | | `FamilyUserController` | `/api/family/user` | 家长端家庭成员管理 | 部分废弃见下 | | `PointsController` | `/api/points` | 积分余额/流水 | — | | `TaskController` | `/api/tasks` | 任务 CRUD/完成/审核 | — | | `WishController` | `/api/wishes` | 心愿 CRUD/审批/兑换 | — | | `RewardController` | `/api/rewards` | 奖励模板/兑换 | — | | `AssessmentAppointmentController` | `/api/assessment/appointment` | 测评预约 | — | | `DanAssessmentController` | `/api/dan-assessment` | DAN 测评全链路 | — | | `EnergyController` | `/api/energy` | 五维能量概览/流水/配置 | — | | `GrowthController` | `/api/growth` | 成长记录 | — | | `GrowthPlanController` | `/api/growth/plan` | 成长计划 | — | | `HealthCheckinController` | `/api/health/checkin` | 健康打卡(行为记录) | — | | `DailyCheckinController` | `/api/daily/checkin` | 每日健康打卡(饮食/运动/心情) | — | | `EmotionCheckinController` | `/api/mind/checkin` | 情绪日记(心情打卡同步目标) | — | | `HealthStatusController` | `/api/health-status` | 健康现状档案 | — | | `ProductController` | `/api/product` | 商品 | — | | `ProductOrderController` | `/api/product/order` | 商品订单 | — | | `CartController` | `/api/cart` | 购物车 | — | | `MembershipController` | `/api/membership` | 会员 | — | | `SubscriptionController` | `/api/subscription` | 订阅 | — | | `AIChatController` | `/api/ai` | AI 对话 | — | | `InnatePortraitController` | `/api/mind/innate` | 先天画像(画像/AI解读/数字能量/成长轨迹) | — | | `ArticleController` | `/api/articles` | 文章 | — | | `NotificationController` | `/api/notification` | 通知 | — | | `StatsController` | `/api/stats` | 统计 | — | | `CfCommissionController` | `/api/commission/cf` | CF 分佣(团队规模/比例/钱包/流水) | — | | `CfTransferController` | `/api/cf/transfer` | CF 成员间转让 | — | | `CfBalanceController` | `/api/cf` | C 端 CF 查询(余额快照/流水/家庭券,只读) | F-7a | | `AdminCfRateTierController` | `/api/admin/cf-rate-tier` | CF 返佣阶梯配置(admin) | — | | `ContactController` | `/api/contact` | 联系人 CRUD/互动/邀请入家庭 | — | | `ContactMatchController` | `/api/contact/match` | 联系人对接(send/accept/reject/列表) | — | | `OctopusController` | `/api/octopus` | 章鱼图(帮助记录/触手聚合) | — | | `PearlController` | `/api/pearl` | 珍珠图(25 类社会连接聚合 + 互动/提醒/优先级) | — | | `AbilityController` | `/api/ability` | 能力图(SKILL 帮助记录聚合为能力节点) | — | | `ServiceRoleApplicationController` | `/api/service-role` | 统一服务角色申请(多角色合一 + 管理端审批) | — | | `GuideTeamController` | `/api/guide/team` | 规划师团队/邀请码/上下级 | — | | `GuideFamilyTaskController` | `/api/guide/families` | 规划师侧家庭任务管理 + 家庭解绑 | — | | `PlannerCertificationController` | `/api/planner` | 规划师培训认证中心(我的认证/课程/线下课打卡/考核) | — | | `XiaoeWebhookController` | `/api/xiaoe` | 小鹅通 webhook 回调(线上课时事件) | — | | `AdminPlannerCertCategoryController` | `/api/admin/planner-cert-categories` | 管理端-学习种类 CRUD | — | | `AdminPlannerCourseController` | `/api/admin/planner-courses` | 管理端-线上课程 CRUD | — | | `AdminPlannerOfflineCourseController` | `/api/admin/planner-offline-course-map` | 管理端-线下课↔类别映射 CRUD | — | | `AdminActivityCheckinController` | `/api/admin/activity-checkins` | 管理端-签到记录/手动补录 | — | | `AdminPlannerExamController` | `/api/admin/planner-exam` | 管理端-试卷/题库 CRUD | — | | `AdminPlannerCertificationController` | `/api/admin/planner-certifications` | 管理端-认证看板/同步课时/存量补登 | — | --- ## 三、已废弃接口清单 > 标记 `410 Gone` 响应,前端已迁移至新接口。可考虑在确认无调用后清理。 ### 3.1 `FamilyController` — 邀请码系统(已废弃) | 路径 | 说明 | 替代接口 | |------|------|----------| | `POST /api/family/invite-code` | 获取邀请码(已废弃) | `POST /api/family/invite/generate` | | `POST /api/family/invite-code/generate` | 生成邀请码(已废弃) | `POST /api/family/invite/generate` | ### 3.2 `FamilyUserController` — 角色切换(已废弃) | 路径 | 说明 | 替代接口 | |------|------|----------| | `POST /api/family/user/switch-mode` | 切换家长/孩子模式(已废弃) | `POST /api/family/member/switch` | | `POST /api/family/user/switch-to-child` | 切换到孩子身份(已废弃) | `POST /api/family/member/switch` | | `POST /api/family/user/switch-back-to-parent` | 切回家长身份(已废弃) | `POST /api/family/member/switch` | | `POST /api/family/user/switch-back-verify` | 切换验证(已废弃) | — | ### 3.3 `ButlerController` — 服务记录/佣金(已废弃) | 路径 | 说明 | 替代接口 | |------|------|----------| | `POST /api/butler/service-records` | 服务记录(已废弃) | — | | `POST /api/butler/commissions` | 佣金记录(已废弃) | — | ### 3.4 `TeacherController` — 教学任务(已废弃) | 路径 | 说明 | 替代接口 | |------|------|----------| | `POST /api/teacher/tasks` | 创建教学任务(已废弃) | `POST /api/guide/families/{familyId}/tasks/{taskId}` | | 其余 `/api/teacher/*` 多个接口均标注 `@Deprecated` | — | — | ### 3.5 `DanAssessmentController` — 当前测评(已废弃) | 路径 | 说明 | 替代接口 | |------|------|----------| | `POST /api/dan-assessment/current` | 获取当前测评(已废弃) | — | ### 3.6 `NoticeController` — 公告列表(已废弃) | 路径 | 说明 | 替代接口 | |------|------|----------| | `GET /api/notices/list` | 使用 GET 方法,违反统一 POST 规范 | — | --- ## 四、接口详细清单 ### 4.1 认证与用户(`/api/auth`, `/api/user`) | 路径 | 说明 | 参数 | 废弃 | |------|------|------|------| | `POST /api/auth/send-code` | 发送验证码 | phone | — | | `POST /api/auth/phone-login` | 手机号登录 | phone, code | — | | `POST /api/auth/wechat-phone-login` | 微信一键绑定手机号 | code | — | | `POST /api/auth/silent-login` | 静默登录(仅获取 token) | — | — | | `POST /api/auth/auto-login` | 自动登录(refresh token) | — | — | | `POST /api/auth/register-with-idcard` | 身份证注册 | — | — | | `POST /api/auth/wechat-login` | 微信授权登录 | code | — | | `POST /api/auth/info` | 获取当前用户信息 | — | — | | `POST /api/auth/set-password` | 设置密码 | password | — | | `POST /api/auth/verify` | 验证密码 | password | — | | `POST /api/auth/check-phone` | 检查手机号是否存在 | phone | — | | `POST /api/auth/direct-register` | 直接注册(无邀请码) | — | — | | `POST /api/auth/register-with-invite` | 邀请码注册 | inviteCode | — | | `POST /api/auth/switch-role` | 切换角色 | role | — | | `POST /api/user/address/list` | 收货地址列表 | — | — | | `POST /api/user/address/get` | 获取地址详情 | id | — | | `POST /api/user/address/create` | 创建地址 | — | — | | `POST /api/user/address/update` | 更新地址 | — | — | | `POST /api/user/address/delete` | 删除地址 | id | — | | `POST /api/user/address/setDefault` | 设为默认 | id | — | | `POST /api/user/mascot/set` | 设置吉祥物 | mascotId | — | | `POST /api/user/mascot/get` | 获取吉祥物 | — | — | ### 4.2 家庭与成员(`/api/family/*`, `/api/family/member/*`) | 路径 | 说明 | 废弃 | |------|------|------| | `POST /api/family/invite-code` | ~~获取邀请码~~ | ✅ 废弃 | | `POST /api/family/invite-code/generate` | ~~生成邀请码~~ | ✅ 废弃 | | `POST /api/family/guide-bind` | 绑定规划师 | — | | `POST /api/family/member/add` | 添加成员 | — | | `POST /api/family/member/list` | 成员列表(统一入口) | — | | `POST /api/family/member/switch` | 切换成员视角 | — | | `POST /api/family/member/kick` | 移除成员 | — | | `POST /api/family/member/leave` | 主动退出家庭(微信用户;创建者需先转让管理员) | — | | `POST /api/family/member/recycle` | 从回收箱回收成员(管理员操作) | — | | `POST /api/family/member/update` | 更新成员信息;body 可含 `isChild`(1=孩子/0=非孩子,仅家庭管理员可设置,落库到 users.is_child;非管理员返回 403) | — | | `POST /api/family/member/rename-family` | 修改家庭名称(仅家庭管理员);body `{name}`,返回新名称 | — | | `POST /api/family/member/editable` | 检查是否可编辑 | — | | `POST /api/family/member/logs` | 成员变更日志 | — | | `POST /api/family/invite/qrcode` | 生成邀请二维码 | — | | `POST /api/family/invite/request-join-by-code` | 申请加入(token 路径绑定邀请人,等待邀请人确认;旧码路径管理员兜底) | inviteCode 或 token + familyRole | — | | `POST /api/family/invite/approve-request` | 同意加入申请(审批人=邀请人且仍为目标家庭成员;旧数据无邀请人时家庭管理员兜底) | — | | `POST /api/family/invite/reject-request` | 拒绝加入申请(权限规则同上) | — | | `POST /api/family/invite/pending-requests` | 待我处理的申请列表(按邀请人查询;旧数据由目标家庭管理员兜底可见) | — | | `POST /api/family/invite/my-request` | 我的申请 | — | | `POST /api/family/invite/cancel-request` | 取消申请 | — | | `POST /api/family/invite/generate` | 生成邀请令牌(复用同家庭未过期未用尽的有效令牌;仅未过期记录计入 10 条额度) | — | | `POST /api/family/invite/validate` | 验证邀请令牌 | — | | `POST /api/family/invite/accept` | 接受邀请 | — | | `POST /api/family/invite/check-family` | 检查家庭状态 | — | | `POST /api/family/invite/revoke` | 撤销邀请 | — | | `POST /api/family/invite/transfer-admin` | 转让管理员 | — | | `POST /api/family/invite/validate-family-code` | **验证家庭短邀请码(落地页展示用,不创建请求)** | `inviteCode` | | `POST /api/family/invite/bind-inviter` | **绑定家庭邀请人(仅首次注册调用)** | `familyInviteCode` | ### 4.2 Family Member 接口详细(切换/回收箱) #### `POST /api/family/member/leave` — 主动退出家庭 **说明**:微信登录用户主动退出当前家庭,回到自己的初始家庭(自己创建的家庭 `families.creator_id = userId`);若无初始家庭则自动创建新家庭。退出时成员资产(测评/报告/能量值/档案属性)随成员迁移到目标家庭,CF 值(`system_points`)清零丢弃留在原家庭。 **约束**: - 仅微信登录用户可退出(`openid` 非 `phone:` 前缀);非微信用户调用返回错误 - 家庭创建者禁止退出,需先通过 `POST /api/family/invite/transfer-admin` 转让管理员 - 退出后该用户自动归属初始家庭(或新建家庭),不会处于"无家庭"状态 **请求体**:无(空 body 或 `{}`) **响应**: ```json { "code": 200, "message": "退出成功", "data": null } ``` **错误码**: | code | message | 处理建议 | |------|---------|----------| | 500 | 您是家庭创建者,请先转让管理员权限后再退出 | 先转让管理员 | | 500 | 非微信登录用户不能自主退出家庭 | 非微信用户不支持主动退出 | | 500 | 用户未加入家庭 / 家庭不存在 / 未找到该用户的家庭成员记录 | 提示用户状态异常 | --- #### `POST /api/family/member/kick` — 移除成员(含回收箱分流) **说明**:家庭管理员将成员移出家庭。行为按成员类型分流: - **微信登录用户**:资产迁移回其初始家庭(无则自动创建),CF 值(`system_points`)随成员带走,同时收回该家庭发放给该成员的人口券(POPULATION,来源 `member:{id}`,仅作废 AVAILABLE 状态的券) - **非微信登录用户**:进入回收箱(`family_members.status = recycled`),`users.family_id` 置空,资产保留在原家庭记录上,后续可通过 `POST /api/family/member/recycle` 回收 - **无关联用户的手动成员**:直接删除记录 **约束**:仅家庭创建者可踢出;不能踢出家庭创建者本人。 **请求体**: ```json { "memberId": 12345 } ``` **响应**: ```json { "code": 200, "message": "踢出成功", "data": null } ``` --- #### `POST /api/family/member/recycle` — 从回收箱回收成员 **说明**:目标家庭管理员将回收箱中的成员(非微信用户,`status=recycled`)重新加入指定家庭,资产(测评/报告/能量值/档案属性)迁移到目标家庭,关联用户 `family_id` 归位到目标家庭。 **约束**:仅目标家庭创建者可回收;成员必须在回收箱中。 **请求体**: ```json { "memberId": 12345, "targetFamilyId": 67890 } ``` **响应**: ```json { "code": 200, "message": "回收成功", "data": null } ``` **错误码**: | code | message | 处理建议 | |------|---------|----------| | 500 | 该成员不在回收箱中 | 仅回收箱成员可回收 | | 500 | 只有目标家庭管理员才能回收成员 | 权限不足 | --- ### 4.2 Family Invite 接口详细 #### `POST /api/family/invite/request-join-by-code` — 申请加入家庭 **说明**:用户通过邀请码/令牌提交加入家庭的申请,等待邀请人审批。 **请求体**: ```json { "inviteCode": "fam_abc123", // 必填,邀请码或令牌 "familyRole": "爸爸" // 可选,用户在目标家庭中的身份 } ``` **响应**: ```json { "code": 200, "message": "申请已提交,等待邀请人确认", "data": { "requestId": 12345, "familyName": "张三的家庭" } } ``` --- #### `POST /api/family/invite/bind-inviter` — 绑定家庭邀请人 **说明**:新用户首次注册时调用,建立「被邀请人 → 邀请人」关系,用于追踪邀请来源。**仅首次注册调用**,已有家庭邀请人的用户调用会返回失败。 **请求体**: ```json { "familyInviteCode": "fam_abc123" } ``` **响应**: ```json { "code": 200, "message": "绑定成功", "data": null } ``` **错误码**: | code | message | 处理建议 | |------|---------|----------| | 400 | 用户已有家庭邀请人 | 跳过,已在目标家庭中 | | 404 | 邀请码无效 | 提示用户链接无效 | --- #### `POST /api/family/invite/validate` — 验证邀请令牌 **说明**:邀请落地页使用,验证 token 有效性并返回家庭信息(仅供展示)。 **请求体**: ```json { "token": "abc123def456" } ``` **响应**: ```json { "code": 200, "data": { "familyId": 100, "familyName": "张三的家庭", "inviterName": "张三", "inviterAvatar": "/static/avatar.jpg", "memberCount": 3 } } ``` --- #### `POST /api/family/invite/check-family` — 检查用户家庭状态 **请求体**:无额外参数(从 JWT 取 userId) **响应**: ```json { "code": 200, "data": { "inFamily": true, "familyId": 100, "familyName": "张三的家庭", "isAdmin": true } } ``` --- #### `POST /api/family/invite/pending-requests` — 查询待我处理的申请 **说明**:邀请人视角,查询他人提交给自己的加入申请。 **请求体**:无额外参数 **响应**: ```json { "code": 200, "data": [ { "id": 1001, "requesterUserId": 200, "requesterNickname": "李四", "requesterAvatar": "/static/avatar.jpg", "familyRole": "妈妈", "status": "pending", "createdAt": "2026-08-31T10:00:00" } ] } ``` --- #### `POST /api/family/invite/approve-request` — 批准加入申请 **请求体**: ```json { "requestId": 1001, "comment": "" } ``` **响应**: ```json { "code": 200, "message": "已同意" } ``` --- #### `POST /api/family/invite/reject-request` — 拒绝加入申请 **请求体**: ```json { "requestId": 1001, "comment": "不符合条件" } ``` **响应**: ```json { "code": 200, "message": "已拒绝" } ``` --- #### `POST /api/family/invite/my-request` — 查询我的申请 **请求体**:无额外参数 **响应**: ```json { "code": 200, "data": { "id": 1001, "targetFamilyId": 100, "targetFamilyName": "张三的家庭", "familyRole": "爸爸", "status": "pending", "createdAt": "2026-08-31T10:00:00" } } ``` **status 枚举**:`pending` | `approved` | `rejected` ### 4.3 任务(`/api/tasks`) | 路径 | 说明 | |------|------| | `POST /api/tasks/minigame-options` | 小游戏选项 | | `POST /api/tasks/create` | 创建任务 | | `POST /api/tasks/today` | 今日任务(`memberId` 查单成员;`familyWide=true` 查全家庭今日任务) | | `POST /api/tasks/{id}/start` | 开始任务(两阶段入口,含购物任务自动加购/下单) | | `POST /api/tasks/{id}/complete` | 完成任务 | | `POST /api/tasks/{id}/review` | 审核任务 | | `POST /api/tasks/history` | 历史任务 | | `POST /api/tasks/pending-review` | 待审核 | | `POST /api/tasks/today-parent` | 家长今日任务 | | `POST /api/tasks/{id}` | 任务详情 | | `POST /api/tasks/{id}/complete-minigame` | 小游戏完成 | | `POST /api/tasks/batch-complete` | 批量完成 | | `POST /api/tasks/batch-delete` | 批量删除 | | `POST /api/tasks/my-pending` | 我的待办 | | `POST /api/tasks/accept` | 接受任务 | | `POST /api/tasks/reject/{id}` | 拒绝任务 | #### 4.3.1 购物任务联动(`actionType = cart/order`) 任务创建时通过 `actionType` + `actionConfig` 声明购物联动;调用 `POST /api/tasks/{id}/start` 时,后端自动执行加购/下单。 **actionConfig JSON 格式:** | 字段 | 必填 | 说明 | |------|------|------| | `productId` | ✅ | 商品ID | | `quantity` | ❌ | 数量,默认 1 | | `skuId` | ❌ | SKU ID(可选) | | `consigneeId` | ❌ | 收货地址ID(`order` 模式下可选;未提供时取用户默认地址) | | `deliveryMethod` | ❌ | 配送方式:1=快递/2=自提/4=线上可选人/5=线上单人 | **actionType 行为:** | actionType | 行为 | 返回字段 | |-----------|------|---------| | `minigame` | 跳转小游戏页 | — | | `focus` | 跳转专注计时器页 | — | | `survey` | 跳转问卷填写页 | — | | `article` | 跳转文章中心页 | — | | `activity` | 跳转活动列表页 | — | | `cart` | 自动加购,返回加购结果 | `execution.extra` 含 `cartAdded=true`、`productId`、`quantity` | | `order` | 先加购,再创建 pending 订单 | `execution.extra` 含 `orderId`、`orderNo`(成功时)或 `orderFailed=true`、`orderError`(失败时) | | `diet` | 跳转饮食打卡页(`/pages/growth/diet-checkin?taskId={id}`) | — | | `sleep` | 跳转睡眠打卡页(`/pages/growth/sleep-checkin?taskId={id}`) | — | | `exercise` | 跳转运动打卡页(`/pages/growth/exercise-checkin?taskId={id}`) | — | **下单失败常见原因:** 缺默认收货地址、商品已下架、库存不足、配送方式校验失败。失败时加购结果保留,任务状态正常置 `in_progress`,前端可引导用户去购物车/结算页手动完成。 ### 4.4 心愿(`/api/wishes`) | 路径 | 说明 | |------|------| | `POST /api/wishes/create` | 创建心愿 | | `POST /api/wishes/{id}/set-price` | 设置价格 | | `POST /api/wishes/{id}/reject` | 拒绝心愿 | | `POST /api/wishes/{id}/exchange` | 兑换心愿 | | `POST /api/wishes/{id}/approve-exchange` | 审批兑换 | | `POST /api/wishes/{id}/fulfill` | 完成心愿 | | `POST /api/wishes/{id}/confirm` | 确认心愿 | | `POST /api/wishes/{id}/cancel` | 取消心愿 | | `POST /api/wishes/list` | 心愿列表 | | `POST /api/wishes/pending` | 待审批心愿 | | `POST /api/wishes/{id}` | 心愿详情 | ### 4.5 能量与积分(`/api/energy`, `/api/points`) | 路径 | 说明 | |------|------| | `POST /api/energy/overview` | 能量概览 | | `POST /api/energy/logs` | 能量流水 | | `POST /api/energy/config` | 能量配置 | | `POST /api/energy/dimension` | 五维详情 | | `POST /api/points/balance` | 积分余额 | | `POST /api/points/logs` | 积分流水 | | `POST /api/points/adjust` | 手动调整 | | `POST /api/points/system-balance` | 系统余额 | | `POST /api/points/logs-by-category` | 分类流水 | ### 4.6 成长档案(`/api/growth/*`) | 路径 | 说明 | |------|------| | `POST /api/growth/record/create` | 创建记录 | | `POST /api/growth/record/create-with-order` | 下单关联创建 | | `POST /api/growth/record/list` | 记录列表 | | `POST /api/growth/record/child/{memberId}` | 指定成员记录 | | `POST /api/growth/record/{id}` | 记录详情 | | `POST /api/growth/record/{id}/delete` | 删除记录 | | `POST /api/growth/record/external/sync` | 外部同步 | | `POST /api/growth/record/upload-and-parse` | 上传解析 | | `POST /api/growth/record/create-from-upload` | 从上传创建 | | `POST /api/growth/record/supplement` | 补充记录 | | `POST /api/growth/plan/create` | 创建计划 | | `POST /api/growth/plan/child/{memberId}` | 成员计划 | | `POST /api/growth/plan/{id}` | 计划详情 | | `POST /api/growth/plan/{id}/review` | 审核计划 | ### 4.7 健康打卡(`/api/health/*`, `/api/daily/checkin`, `/api/mind/checkin`) | 路径 | 说明 | |------|------| | `POST /api/health/checkin/list` | 行为打卡列表 | | `POST /api/health/checkin/create` | 创建行为打卡 | | `POST /api/daily/checkin/list` | 每日打卡列表(饮食/运动/睡眠/心情) | | `POST /api/daily/checkin/create` | 创建每日打卡 | | `POST /api/daily/checkin/update` | 更新打卡 | | `POST /api/daily/checkin/delete` | 删除打卡 | | `POST /api/mind/checkin/create` | 情绪日记创建 | | `POST /api/mind/checkin/list` | 情绪日记列表 | | `POST /api/mind/checkin/latest` | 最新情绪日记 | | `POST /api/mind/checkin/trend` | 情绪趋势 | | `POST /api/mind/checkin/stats` | 情绪统计 | | `POST /api/mind/checkin/weekly-report` | 周报 | | `POST /api/mind/checkin/emotion/recognize` | ~~情绪识别(URL方式,走Dify)~~ — 已废弃 | | `POST /api/mind/emotion/analyze` | 照片情绪识别(文件上传,走 LangGraph DeepFace) | | `POST /api/mind/emotion/analyze-url` | 照片情绪识别(URL方式,走 LangGraph DeepFace) | | `POST /api/health-status/get` | 健康现状档案获取 | | `POST /api/health-status/save` | 健康现状档案保存 | | `POST /api/health-status/signature` | 提交手签名(仅更新 `signature` + `signed_at` 列,不影响其他字段;body: `{memberId, signature}`,signature 为 base64 PNG data URL) | | `POST /api/health/exercise/sync-wechat-steps` | 同步微信运动步数(传 encryptedData+iv,返回当日步数与近30天数据) | | `POST /api/health/meal/list` | 饮食打卡列表 | | `POST /api/health/meal/create` | 创建饮食打卡(支持 taskId 任务联动 + isBackfill 补打) | | `POST /api/health/meal/daily` | 获取某天的饮食记录 | | `POST /api/health/meal/weekly` | 获取某周食材频次与营养汇总 | | `POST /api/health/meal/alerts` | 获取食材摄入预警 | | `POST /api/health/sleep/list` | 睡眠打卡列表 | | `POST /api/health/sleep/create` | 创建睡眠打卡(支持 taskId 任务联动) | | `POST /api/health/exercise/list` | 运动打卡列表 | | `POST /api/health/exercise/create` | 创建运动打卡(支持 taskId 任务联动 + stepCount 微信步数) | #### 健康打卡任务联动(`taskId` 参数) 饮食/睡眠/运动打卡接口均支持可选 `taskId` 参数(Long),用于将打卡记录与任务系统联动。 **请求体新增参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `taskId` | Long | ❌ | 关联任务ID;为 null 时仅存打卡记录,不触发任务逻辑 | **联动逻辑:** | 场景 | 行为 | |------|------| | `taskId` 指向 `tasks.is_daily_progress=1` 的任务 | 调用 `TaskService.updateProgress(userId, taskKey, 1)` 累加进度,达标后自动 completed + 发放积分/能量 | | `taskId` 指向普通任务(`is_daily_progress=0`) | 调用 `TaskService.completeTask(taskId, memberId, ...)` 直接完成 | | `taskId` 为 null | 仅存打卡记录 + 发放 5CF 打卡积分,不触发任务逻辑 | | 补打(`isBackfill=1`)| **忽略 taskId**,不触发任何任务逻辑 | **taskKey 约定(每日进度型任务):** | actionType | taskKey | 匹配方式 | |-----------|---------|---------| | `diet` | `"DIET"` | title 精确匹配 OR title 以 `"[DIET]"` 前缀匹配 | | `sleep` | `"SLEEP"` | 同上 | | `exercise` | `"EXERCISE"` | 同上 | 前端跳转路径:`/pages/growth/{type}-checkin?taskId={id}`(由 `tasks.vue` 的 `navigateByAction()` 自动构造) ### 4.8 商品与订单(`/api/product/*`) | 路径 | 说明 | |------|------| | `POST /api/product/list` | 商品列表 | | `POST /api/product/detail` | 商品详情 | | `POST /api/product/create` | 创建商品 | | `POST /api/product/update` | 更新商品 | | `POST /api/product/my` | 我的商品 | | `POST /api/product/shelve` | 上下架 | | `POST /api/product/order/create` | 创建订单 | | `POST /api/product/order/pay` | 支付 | | `POST /api/product/order/cancel` | 取消订单 | | `POST /api/product/order/my` | 我的订单 | | `POST /api/product/order/detail` | 订单详情 | | `POST /api/product/order/confirm` | 确认收货 | | `POST /api/product/order/refund/apply` | 申请退款 | | `POST /api/product/order/vendor` | 服务商查询收到的订单 | | `POST /api/product/order/vendor-confirm` | 服务商确认发出/服务完成 | | `POST /api/product/order/ship` | 服务商/管理端发货(填物流单号+快递公司)。发货成功后自动调用微信「小程序交易管理服务」`upload_shipping_info` 同步物流到微信订单管理(用户在微信侧收发货通知/查物流/确认收货);自提订单或无单号跳过;失败只记日志不阻塞(fail-open);`wechat.test-mode=true` 或 `wechat.fulfillment-enabled=false`(环境变量 `WECHAT_FULFILLMENT_ENABLED`)时跳过 | | `POST /api/product/order/review/update` | 供应商审核修改待支付订单(修改价格/配送方式/添加优惠券/赠品) | | `POST /api/cart/add` | 加入购物车 | | `POST /api/cart/list` | 购物车列表 | | `POST /api/cart/update` | 更新数量 | | `POST /api/cart/remove` | 移除商品 | | `POST /api/cart/count` | 购物车数量 | | `POST /api/consignee/list` | 收货地址列表 | | `POST /api/consignee/save` | 保存地址 | | `POST /api/consignee/delete` | 删除地址 | ### 4.9 AI 对话(`/api/ai/*`) | 路径 | 说明 | |------|------| | `POST /api/ai/chat/send` | 发送消息 | | `POST /api/ai/chat/conversations` | 对话列表 | | `POST /api/ai/chat/messages` | 消息列表 | | `POST /api/ai/chat/conversations/{id}/delete` | 删除对话 | | `POST /api/ai/nutrition/send` | 营养对话 | | `POST /api/ai/health-coach/send` | 健康教练(请求体可选字段 `coach_id` 由服务端自动注入,客户端无需传;LangGraph 按 xibao/fubao 三级回退路由话术) | | `POST /api/ai/butler/send` | 管家对话 | | `POST /api/ai/context` | 上下文管理 | | `POST /api/butler/sessions/create` | 创建会话 | | `POST /api/butler/sessions/update-conversation` | 更新会话 | | `POST /api/butler/sessions/archive` | 归档会话 | | `POST /api/butler/sessions/list` | 会话列表 | | `POST /api/butler/sessions/detail` | 会话详情 | | `POST /api/butler/sessions/delete` | 删除会话 | ### 4.10 测评(`/api/assessment/*`, `/api/dan-assessment/*`) | 路径 | 说明 | |------|------| | `POST /api/assessment/appointment/create` | 预约测评 | | `POST /api/assessment/appointment/confirm/{id}` | 确认预约 | | `POST /api/assessment/appointment/cancel/{id}` | 取消预约 | | `POST /api/assessment/appointment/my-list` | 我的预约 | | `POST /api/assessment/latest-result` | 最新测评结果 | | `POST /api/assessment/history` | 测评历史 | | `POST /api/dan-assessment/result/upload` | 上传报告 | | `POST /api/dan-assessment/result/{resultId}/guidance/save` | 保存指导建议 | | `POST /api/dan-execution/complete` | 完成测评执行 | | `POST /api/dan-execution/confirm` | 确认执行 | | `POST /api/dan-execution/rate` | 评分 | | `POST /api/dan-execution/my-list` | 我的执行记录 | ### 4.11 饮食推荐(`/api/diet/*`) | 路径 | 说明 | |------|------| | `POST /api/diet/preferences/current-member` | 当前成员饮食偏好 | | `POST /api/diet/preferences/save` | 保存饮食偏好 | | `POST /api/diet/recommendation/today` | 今日推荐(返回 `data.id / data.menu / data.nutritionSummary / data.status`) | | `POST /api/diet/recommendation/generate` | 生成推荐(body: `date, meal_type, selected_foods JSON string`;返回 `data.id / data.menu / data.nutritionSummary`) | | `POST /api/diet/recommendation/complete` | 完成推荐 | | `POST /api/diet/record/save` | 保存饮食记录 | | `POST /api/diet/record/daily` | 每日记录 | | `POST /api/diet/ingredients/suggest` | 食材推荐(返回 `data.ingredients` 列表) | | `POST /api/diet/ingredients/recommend` | 换一批(返回 `data.ingredients`,每次不同) | | `POST /api/diet/ingredients/search` | 搜索食材(body `{keyword}`,返回 `data.foods:[{id, name, category}]`) | | `POST /api/diet/meals/config` | 餐食配置(body: `date_type` + `meal_type`,返回 `data.participantMemberIds` 等) | | `POST /api/diet/meals/config/save` | 保存餐食配置,**批量格式**(推荐,单次调用):body `{date_type:"weekday", configs:{breakfast:"[1,2]", lunch:"[]", dinner:"[3]"}}`(各餐次值为成员ID数组的 JSON 字符串);亦兼容**单条格式**:body `{date_type, meal_type, participant_member_ids, notes?}`。前端 `saveMealConfig` 位于 `cfc-frontend/utils/api.js`(2772行) | ### 4.12 内容(`/api/articles`, `/api/content/*`) | 路径 | 说明 | |------|------| | `POST /api/articles/list` | 文章列表 | | `POST /api/articles/detail` | 文章详情 | | `POST /api/articles/featured` | 精选文章 | | `POST /api/articles/record-read` | 记录阅读 | | `POST /api/articles/report-reading-time` | 上报阅读时长(按 articleId+childId+日期累加,首次上报递增 view_count) | | `POST /api/articles/reading-stats` | 阅读统计 | | `POST /api/articles/daily-tip` | 每日提示 | | `POST /api/articles/ai-questions` | AI 生成问题 | | `POST /api/articles/submit-answers` | 提交答案 | | `POST /api/articles/my-posts` | 我的文章 | | `POST /api/articles/comments/list` | 评论列表 | | `POST /api/articles/comments/create` | 创建评论 | | `POST /api/articles/comments/audit` | 审核评论 | | `POST /api/articles/quiz/generate` | 生成测验 | | `POST /api/articles/quiz/submit` | 提交测验 | | `POST /api/content-sections/visible` | 可见区块 | | `POST /api/content-sections/list` | 区块列表 | | `POST /api/content/related-items` | 内容关联推荐:以活动/文章/商品任一为源(`sourceType`+`sourceId`),按共享标签一次返回 `data.articles / data.activities / data.products` 三组(支持 `size`/`page` 分页,游客可访问) | ### 4.13 管理员接口(`/api/admin/*`) > 管理员接口需 `role=admin`,详见 `AdminInterceptor`。 | 路径前缀 | 说明 | |----------|------| | `/api/admin/articles/*` | 文章管理 | | `POST /api/admin/articles/move` | 调整精选文章的推荐顺序(`{ id, direction: 'up'|'down' }`,已边界时返回 400) | | `/api/admin/articles/reading-records` | 按文章查询阅读记录明细(分页,含孩子昵称/时长) | | `/api/admin/assessment/*` | 测评材料管理 | | `/api/admin/bazi-config/*` | 八字配置 | | `/api/admin/blood-type-config/*` | 血型配置 | | `/api/admin/butler/*` | 管家分配管理(分配列表/强制解绑/停接开关) | | `/api/admin/commission/*` | 佣金管理 | | `/api/admin/dimension/*` | 维度配置 | | `/api/admin/dimension-config/*` | 维度权重 | | `/api/admin/energy/*` | 能量配置 | | `/api/admin/energy-config/*` | 五行能量来源/每日限制配置 | | `/api/admin/ecom-supplier/*` | 电商供应商管理 | | `/api/admin/file-record/*` | 后台素材库(上传入库/检索/标签字典/统计,见 §4.56) | | `/api/admin/guide/*` | 成长规划师管理(申请/套餐) | | `/api/admin/growth-archive-product/*` | 成长档案关联商品 | | `/api/admin/health-plan/*` | 健康方案管理(跨家庭列表/详情) | | `/api/admin/knowledge-base/*` | 知识库管理 | | `/api/admin/notices/*` | 公告管理 | | `/api/admin/innate-portrait-config/*` | 先天画像来源权重配置(list/update/invalidate) | | `/api/admin/numsoul-detail-config/*` | 数字能量详细配置(list/create/update/delete) | | `/api/admin/package-templates/*` | 套餐模板 | | `/api/admin/ppoint-config/*` | P点配置(品类级比例+商品级固定值) | | `/api/admin/product/*` | 商品管理 | | `/api/admin/report-parser/*` | 报告解析配置 | | `/api/admin/report-template/*` | 报告展示模板管理 | | `/api/admin/supply-*` | 供应链相关 | | `/api/admin/system/*` | 系统配置 | | `/api/admin/unlock-gates/*` | 通关配置 | | `/api/migration/run` | 数据迁移 | ### 4.14 先天画像(`/api/mind/innate/*`) > 五维能量体系"心维度"先天画像聚合接口,基于星座/八字/血型/数字能量生成,AI 解读失败时自动降级模板文案。 | 路径 | 说明 | |------|------| | `POST /api/mind/innate/portrait` | 先天画像(聚合 mind_base_score + 数字能量详情 + 来源贡献明细) | | `POST /api/mind/innate/reading` | AI 解读(缓存到 innate_portrait_report,失败降级模板) | | `POST /api/mind/innate/numsoul` | 数字能量计算(生命灵数/天赋数/生日数/命运数) | | `POST /api/mind/innate/trajectory` | 先天+后天成长轨迹(EnergyBalance 后天能量对比) | **请求参数**(portrait/reading/trajectory): ```json { "memberId": 123, "memberType": "child", "familyId": 1 } ``` **numsoul 请求参数**: ```json { "year": 1990, "month": 5, "day": 15 } ``` ### 4.15 统计与通知(`/api/stats`, `/api/notification`) | 路径 | 说明 | |------|------| | `POST /api/stats/dashboard` | 统计仪表盘 | | `POST /api/stats/overview` | 总览 | | `POST /api/stats/revenue` | 收入统计 | | `POST /api/stats/membership` | 会员统计 | | `POST /api/stats/trend` | 趋势 | | `POST /api/notification/list` | 通知列表 | | `POST /api/notification/read` | 标记已读 | ### 4.16 报告模板管理(`/api/admin/report-template`) | 路径 | 说明 | |------|------| | `POST /api/admin/report-template/list` | 模板列表,支持 `reportType`/`isActive` 过滤 | | `POST /api/admin/report-template/get` | 按 reportType 获取模板,参数 `reportType` | | `POST /api/admin/report-template/save` | 保存模板(创建或更新),body 含 `id/name/reportType/config` | | `POST /api/admin/report-template/delete` | 删除模板,参数 `id` | | `POST /api/admin/report-template/toggle` | 启停模板,body `{ id, isActive }` | | `POST /api/admin/report-template/indicators` | 获取可选指标列表,支持 `domain` 过滤 | **config JSON 结构:** ```json { "fixedBlocks": [ { "type": "score", "title": "健康评分", "items": ["overallScore","gutHealthScore"] }, { "type": "indicator", "title": "血脂指标", "groupLabel": "血脂", "indicatorNames": ["总胆固醇","甘油三酯"] }, { "type": "list", "title": "菌种详情", "listKey": "flora" }, { "type": "text", "title": "评语", "content": "..." } ], "freeDisplay": { "enabled": true, "groupName": "其他指标" } } ``` ### 4.17 其他接口 | 路径 | 说明 | |------|------| | `POST /api/config/public/value` | 公开配置值 | | `POST /api/media/upload` | 媒体上传(免鉴权) | | `POST /api/recommend/search` | 推荐搜索 | | `POST /api/recommend/repurchase-reminders` | 复购提醒 | | `POST /api/task-reminders/upcoming/{memberId}` | 即将到期提醒 | | `POST /api/tianpan/dashboard` | 天盘概览 | | `POST /api/tianpan/member/{memberId}` | 成员详情 | | `POST /api/tianpan/compatibility` | 兼容性分析 | | `POST /api/tianpan/annual-energy` | 年度能量 | | `POST /api/tianpan/daily-fortune` | 每日运势 | | `POST /api/tianpan/related-items` | 相关事项 | | `POST /api/tianpan/recalibrate` | 重算五维基础分(管理员) | | `POST /api/wisdom/gut-cognition` | 肠道认知 | | `POST /api/wealth/checkin/*` | 财富打卡 | | `POST /api/insurance/*` | 保险规划 | ### 4.18 日常任务聚合(`/api/daily-task/*`) | 路径 | 说明 | |------|------| | `POST /api/daily-task/overview` | 今日任务总览(打卡/任务/阅读活动/测评报告四类聚合) | ### 4.19 健康维度评分(`/api/dimension/*`) | 路径 | 说明 | |------|------| | `POST /api/dimension/overview` | 获取健康维度概览(七维评分+百分位) | | `POST /api/dimension/upload` | 手动录入维度评分 | | `POST /api/dimension/questionnaire` | 问卷提交维度评分 | | `POST /api/dimension/history` | 获取维度历史记录 | ### 4.20 家庭成员关系质量(`/api/family/relationship/*`) | 路径 | 说明 | |------|------| | `POST /api/family/relationship/scores` | 获取家庭成员关系质量评分 | | `POST /api/family/relationship/calculate` | 计算并保存关系质量评分 | | `POST /api/family/relationship/health-alerts` | 获取健康预警列表 | | `POST /api/family/relationship/milestones` | 获取即将到来的里程碑 | | `POST /api/family/relationship/pairwise` | 获取以指定成员为中心的所有成员关系 | ### 4.21 佣金管理(`/api/commission/*`) | 路径 | 说明 | |------|------| | `POST /api/commission/summary` | 获取佣金汇总 | | `POST /api/commission/list` | 佣金流水列表 | | `POST /api/commission/team` | 团队统计(L1/L2) | | `POST /api/commission/stats` | 佣金统计面板 | | `POST /api/commission/balance` | 可提现余额详情 | | `POST /api/commission/team/list` | 团队成员列表 | | `POST /api/commission/funnel` | 佣金转化漏斗 | | `POST /api/commission/team/list` | 扩展:返回中每个成员新增 `memberConsumption`(该成员已完成履约订单总额,分)、`myCfEarnedFromMember`(我从该成员消费获得的 CF 总额,分) | | `POST /api/commission/team/consumption` | 团队总消费额分级汇总,请求 `{maxLevel}`(默认 3),返回 `{totalConsumption: 分, levelBreakdown[{level, memberCount, consumption}], totalMembers, maxLevel}` | | `POST /api/commission/my-referrer` | 获取我的直推人信息,返回 `{hasReferrer, referrerId, nickname, avatar, referralCode, bindTime}`,无推荐人时 hasReferrer=false | | `POST /api/commission/team/tree` | 团队树形扁平数据(不含根节点),请求 `{maxLevel}`(默认 3),返回 `{nodes[{userId, nickname, avatar, level, parentId, referrerId, memberConsumption, myCfEarnedFromMember}], totalMembers, maxLevel}` | ### 4.22 管理端-能量配置(`/api/admin/energy-config/*`) | 路径 | 说明 | |------|------| | `POST /api/admin/energy-config/source-list` | 能量来源配置列表 | | `POST /api/admin/energy-config/source-save` | 保存能量来源配置(新增/更新) | | `POST /api/admin/energy-config/source-delete` | 删除能量来源配置 | | `POST /api/admin/energy-config/daily-limits` | 获取每日能量限制 | | `POST /api/admin/energy-config/daily-limit-save` | 保存每日能量限制 | | `POST /api/admin/energy-config/overview` | 能量配置总览(来源+限制) | ### 4.23 管理端-公告(`/api/admin/notices/*`) | 路径 | 说明 | |------|------| | `POST /api/admin/notices/list` | 公告列表(分页) | | `POST /api/admin/notices/all` | 全量公告列表 | | `POST /api/admin/notices/create` | 创建公告 | | `POST /api/admin/notices/update` | 更新公告 | | `POST /api/admin/notices/delete` | 删除公告(软删) | | `POST /api/admin/notices/pin` | 置顶公告 | ### 4.24 管理端-成长规划师(`/api/admin/guide/*`) | 路径 | 说明 | |------|------| | `POST /api/admin/guide/applications/pending` | 待审批规划师申请 | | `POST /api/admin/guide/applications/{id}/approve` | 审批通过 | | `POST /api/admin/guide/applications/{id}/reject` | 驳回申请 | | `POST /api/admin/guide/guides` | 获取所有成长规划师列表 | | `POST /api/admin/guide/guides/level/{level}` | 按级别获取规划师 | | `POST /api/admin/guide/guides/approved` | 获取已入驻(approved)的服务商列表 | | `POST /api/admin/guide/packages` | 获取所有套餐模板 | | `POST /api/admin/guide/packages/create` | 创建套餐 | | `POST /api/admin/guide/packages/{id}/update` | 更新套餐 | | `POST /api/admin/guide/packages/{id}/status` | 更新套餐状态 | ### 4.25 管理端-商品(`/api/admin/product/*`) | 路径 | 说明 | |------|------| | `POST /api/admin/product/list` | 商品列表(分页/筛选/搜索) | | `POST /api/admin/product/review` | 审核商品(approve/reject) | | `POST /api/admin/product/shelve` | 商品上下架 | | `POST /api/admin/product/detail` | 商品详情 | | `POST /api/admin/product/create` | 创建商品 | | `POST /api/admin/product/update` | 更新商品 | | `POST /api/admin/product/copy` | 复制商品 | | `POST /api/admin/product/delete` | 删除商品 | | `POST /api/admin/product/batch-shelve` | 批量上下架(body: `{productIds, shelve}`) | ### 4.26 管理端-成长档案关联商品(`/api/admin/growth-archive-product/*`) | 路径 | 说明 | |------|------| | `POST /api/admin/growth-archive-product/save` | 保存关联商品规则 | | `POST /api/admin/growth-archive-product/detail` | 关联商品规则详情 | | `POST /api/admin/growth-archive-product/delete` | 删除关联商品规则 | ### 4.27 管理端-电商供应商(`/api/admin/ecom-supplier/*`) | 路径 | 说明 | |------|------| | `POST /api/admin/ecom-supplier/list` | 供应商列表(分页/搜索) | | `POST /api/admin/ecom-supplier/all` | 所有供应商(下拉选择) | | `POST /api/admin/ecom-supplier/save` | 创建供应商 | | `POST /api/admin/ecom-supplier/update` | 更新供应商 | | `POST /api/admin/ecom-supplier/delete` | 删除供应商 | ### 4.28 管理端-测评材料(`/api/admin/assessment/*`) | 路径 | 说明 | |------|------| | `POST /api/admin/assessment/materials` | 测评资料列表 | | `POST /api/admin/assessment/materials/{id}` | 测评资料详情 | | `POST /api/admin/assessment/materials/create` | 创建测评资料 | | `POST /api/admin/assessment/materials/{id}/update` | 更新测评资料 | | `POST /api/admin/assessment/materials/{id}/delete` | 删除测评资料(软删) | | `POST /api/admin/assessment/config` | 获取积分配置 | | `POST /api/admin/assessment/config/update` | 更新积分配置 | ### 4.29 管理端-P点配置(`/api/admin/ppoint-config/*`) | 路径 | 说明 | |------|------| | `POST /api/admin/ppoint-config/category/list` | 品类级P点比例列表 | | `POST /api/admin/ppoint-config/category/save` | 保存品类级P点比例 | | `POST /api/admin/ppoint-config/category/delete` | 删除品类级P点比例 | | `POST /api/admin/ppoint-config/category/by-category` | 按品类ID获取P点比例 | | `POST /api/admin/ppoint-config/product/list` | 商品级P点列表 | | `POST /api/admin/ppoint-config/product/save` | 保存商品级P点 | | `POST /api/admin/ppoint-config/product/delete` | 删除商品级P点 | | `POST /api/admin/ppoint-config/product/by-product` | 按商品ID获取P点 | | `POST /api/admin/ppoint-config/effective` | 获取商品综合有效P点值 | ### 4.30 管理端-报告解析(`/api/admin/report-parser/*`) | 路径 | 说明 | |------|------| | `POST /api/admin/report-parser/types` | 报告类型列表 | | `POST /api/admin/report-parser/types/save` | 保存/更新报告类型 | | `POST /api/admin/report-parser/types/toggle` | 启用/停用报告类型 | | `POST /api/admin/report-parser/types/delete` | 删除报告类型 | | `POST /api/admin/report-parser/fingerprints/list` | 指纹规则列表 | | `POST /api/admin/report-parser/fingerprints/save` | 保存指纹规则 | | `POST /api/admin/report-parser/fingerprints/delete` | 删除指纹规则 | | `POST /api/admin/report-parser/imports` | 导入记录列表 | | `POST /api/admin/report-parser/imports/import` | 上传导入包 | | `POST /api/admin/report-parser/imports/approve` | 审批导入 | | `POST /api/admin/report-parser/imports/reject` | 驳回导入 | | `POST /api/admin/report-parser/unknown-clusters` | 未知报告聚类列表 | | `POST /api/admin/report-parser/unknown-uploads` | 聚类上传记录 | | `POST /api/admin/report-parser/generate-type` | 自学习生成报告类型 | --- ## 五、新增接口流程 ``` 1. 搜索现有接口:grep -rn "关键词" cfc-backend/src/main/java/com/etotem/cfc/controller/ 2. 检查路由冲突:grep -rn '@PostMapping("' cfc-backend/src/main/java/com/etotem/cfc/controller/ | grep -oP '@\w+Mapping\("\K[^"]*' | sort -u 3. 检查 Bean 冲突:find ... -name "*XxxService.java" | xargs grep "class Xxx" 4. 如有相似接口:评估修改成本,必要时扩展 5. 如确需新增: a. 创建 Entity(如需要新表) b. 创建 Mapper c. 创建 Service d. 创建 Controller(放在合适子目录) e. 数据库迁移(DatabaseInitializer + schema.sql) f. 更新本文档 6. mvn clean compile 验证 ``` ### 4.31 家庭管家选择(`/api/butler/*`) | 路径 | 说明 | |------|------| | `POST /api/butler/available-list` | 可选管家列表(浏览开放,脱敏;仅 L2 有效订阅家庭可绑定) | | `POST /api/butler/my-butler` | 我家当前管家绑定信息(含管家昵称/等级/分配时间) | | `POST /api/butler/select` | 绑定/更换管家(校验 L2 订阅、管家容量与接单状态;换绑自动解绑旧关系) | ### 4.32 优惠券(家庭维度,`/api/coupon/*` + `/api/admin/coupon/*`) > 2026-08-31 起,优惠券由「用户维度」全链路改为「家庭维度」。`user_coupon` 保留历史数据,新发放与消费统一走 `family_coupon`。 | 路径 | 说明 | |------|------| | `POST /api/admin/coupon/list` | 优惠券模板分页列表(admin),`{ page, size, keyword?, sort? }`,返回 `Page`(records/total/current/size),keyword 对名称/ID 模糊匹配 | | `POST /api/admin/coupon/create` | 新建优惠券模板(admin) | | `POST /api/admin/coupon/update` | 更新优惠券模板(admin) | | `POST /api/admin/coupon/issue` | 批量发放到家庭,`{ couponId, familyIds: [Long] }`(admin) | | `POST /api/admin/coupon/issue-family` | 单家庭发放,`{ familyId, couponId, quantity }`(admin,家庭管理页使用) | | `POST /api/admin/coupon/grant-log` | 家庭发券流水查询,`{ familyId?, couponId?, grantType?, source? }`(admin,返回 family_coupon_grant_log,每行含批量填充的 `familyName`,消除前端逐条查家庭名 N+1) | | `POST /api/admin/coupon/grant-user` | 管理员定向发放「会员升级券」给指定用户,`{ userId, amountYuan \| amount, validUntil?, remark? }`(admin,自动创建 applicableTo=MEMBERSHIP 无门槛券模板并发放到该用户家庭,落 grantType=MANUAL 流水) | | `POST /api/admin/coupon/toggle-status` | 启用/停用优惠券模板(admin) | | `POST /api/coupon/list` | 当前家庭可用优惠券(小程序) | | `POST /api/coupon/my` | 我的(家庭)优惠券(小程序) | | `POST /api/coupon/apply` | 下单抵扣(家庭券) | | `POST /api/coupon/claim` | 领取优惠券(发到家庭) | | `POST /api/coupon/checkout-list` | 结算页可兑换未拥有券 | | `POST /api/coupon/available-templates` | 领券中心可取券模板:上架(status 空/ACTIVE)、有效期内、未领完(total_count 空/≤0 视为不限量,否则 used_count`(字段为 `id` 等模板字段),与 `/claim` 去重口径对齐(COUPON-C3) | --- ### 4.33 CF 值分佣(`/api/commission/cf/*`) > CF 值(平台积分)统一分佣体系(F-4 分佣收口,2026-10-25):普通订单双返(当前消费人 F02 + 推荐人两级销售奖),会员/订阅只返推荐人,同家庭互推跳过本人上溯。 > 推荐人返佣收口为 **F03a 直接销售奖 + F03b 间接销售奖**,共同基数 = 本单消费产生的 P 点;旧「团队规模阶梯 `cf_rate_tier`」已废弃(表保留只读,仅等级展示/历史查询使用)。 | 路径 | 说明 | |------|------| | `POST /api/commission/cf/rate` | 我的团队规模 + 两级销售奖档位。返回 `{ totalTeamSize, ratePercent, tierName, teamDepth, teamAnnualPpoints, selfAnnualPpoints, directRatePercent, directTierName, indirectRatePercent, indirectTierName }`;legacy 字段 `ratePercent/tierName` 取 F03a 值供旧端兼容(未入档时 ratePercent=0、tierName="未入档") | | `POST /api/commission/cf/summary` | 我的 CF 钱包汇总,返回 `PlatformBalanceService.getBalance(userId)` 结果 | | `POST /api/commission/cf/list` | 我的 CF 流水(分页),请求 `{ page, size }`,返回 `Page` | **两级销售奖档位(迁移418 种子,管理端可配置):** 共同基数 = 本单消费产生的 P 点(商品:实付元 × 有效 P 点 / 100;非商品:实付元 × `cf.commission.service_rate`(bps) / 10000)。第 1 个非同家庭引荐人拿 F03a,第 2 个拿 F03b,最多两级。 F03a 直接销售奖(`cf_direct_sales_tier`,按**团队 8 层内滚动一年累计 P 点**): | 团队年度 P 点下限 | 档位 | 比例 | |:---:|:---:|:---:| | 100 | 青铜 | 10% | | 500 | 白银 | 25% | | 2000 | 黄金 | 40% | | 5000 | 铂金 | 55% | | 10000 | 钻石 | 70% | F03b 间接销售奖(`cf_indirect_sales_tier`,按**自身滚动一年累计消费 P 点**): | 自身年度 P 点下限 | 档位 | 比例 | |:---:|:---:|:---:| | 100 | 青铜 | 2% | | 500 | 白银 | 5% | | 2000 | 黄金 | 10% | | 5000 | 铂金 | 15% | | 10000 | 钻石 | 20% | > 匹配语义:`enabled=1 AND min_xxx <= 值 ORDER BY min_xxx DESC LIMIT 1`,无匹配档位不发放;比例按 `cf_earn_rule.params_json.max_rate_percent`(F03a=70、F03b=20)封顶。 > 金额由 `cf_earn_rule.formula = floor(base_ppoints * rate_percent / 100)` 计算,发放统一走 `CfBalanceService.earnByRule`(幂等键复用 F-2 `uk_ref_type`),只写 `cf_balance` + `cf_balance_log`。 > 滚动一年口径:消费支付成功时落 `ppoint_consumption_log` 一行(唯一键 `order_type + order_id` 幂等),按 `created_at >= now() - 365d` 聚合(迁移420)。 ### 4.34 CF 成员间转让(`/api/cf/transfer/*`) | 路径 | 说明 | |------|------| | `POST /api/cf/transfer/send` | 成员间转让 CF,请求 `{ toUserId, amount }`。仅限同一家庭(校验 familyId 一致);amount 必须为正数。转出扣减 + 转入增加在同一事务 | | `POST /api/cf/transfer/list` | 转让记录(分页,按当前用户家庭过滤),请求 `{ page, size }`,返回 `Page` | ### 4.35 CF 返佣阶梯配置(admin,`/api/admin/cf-rate-tier/*`) | 路径 | 说明 | |------|------| | `POST /api/admin/cf-rate-tier/list` | 阶梯列表(按 sort_order 升序),返回 `List` | | `POST /api/admin/cf-rate-tier/save` | 新增/更新阶梯,请求体为 `CfRateTier`(含 id 则更新,否则新增,默认 enabled=1),返回 "已保存" | | `POST /api/admin/cf-rate-tier/delete` | 删除阶梯,请求 `{ id }`,返回 "已删除" | **`CfRateTier` 字段:** `id` / `tierName`(档位名)/ `minTeamSize`(团队规模下限含)/ `ratePercent`(返佣比例%)/ `sortOrder`(排序,越大越高)/ `enabled`(1启用/0停用)/ `createdAt` / `updatedAt`。 > 注意:家庭券接口为 `POST /api/coupon/my` 与 `POST /api/coupon/list`(见 4.32),不存在 `/api/coupon/family/list` 路由。 ### 4.36 健康报告异步采集(`/api/health/report/*`, `/api/report-parser/*`) 健康报告上传后进入**异步采集流程**:后端解析 PDF(先 `pdftotext` 提取文本内联,避免依赖代理工具执行),指纹判定报告类型 → 已知类型走专用采集脚本,未知类型交本地 opencode 服务(模型 `agnes-ai/agnes-2.5-flash`,在 session body 中显式指定)。前端提示「数据采集中」,完成后自动入库,列表页轮询刷新,无需用户等待。 | 路径 | 说明 | |------|------| | `POST /api/health/report/upload-only` | 生产小程序上传入口:上传 PDF → 落草稿 `health_report_drafts`(`parse_status=collecting`)→ **异步触发采集** → 返回草稿 ID(前端留意 `data.draftId` 后轮询) | | `POST /api/report-parser/upload` | 专用采集接口:同 async 流程,统一进异步采集 | | `POST /api/health/report/collect/status` | 查询单草稿采集状态,请求 `{ draftId }`,返回 `{ status, parseError }`(`collecting`/`completed`/`failed`) | | `POST /api/health/report/collecting-list` | 当前用户「采集中」草稿列表,返回 `List`(前端轮询用) | **采集流程状态机:** - `collecting`:指纹判定 + 异步解析中 - `completed`:解析成功 + 已自动入库(指标/菌群/疾病风险/食材) - `failed`:解析失败,`parse_error` 记录原因 **专属采集脚本路径(在 `HealthReportDraftService`):** `getDraftById` / `markCollecting` / `updateParseMethod` / `markCollectCompleted` / `markCollectFailed` / `listCollectingByUser`。 ### 4.36.1 报告归属管理(`/api/health/report/*`) 报告归属由 `health_reports.subject_id` 字段表示(关联家庭成员 ID,可为 null)。无归属的报告**不参与方案生成**,但可在管理端或小程序手动绑定/重新分配。 | 路径 | 说明 | |------|------| | `POST /api/health/report/reassign` | 重新分配报告归属(含维度重算),请求 `{ reportId, subjectId? }`,`subjectId` 为 null 表示解绑 | | `POST /api/health/report/unlinked` | 获取家庭内所有未绑定归属的报告,请求 `{ familyId }`,返回 `List` | | `POST /api/health/report/bind` | 绑定报告到成员(仅绑定,不可解绑),请求 `{ reportId, subjectId }` | **`/api/health/report/reassign` 请求体:** ```json { "reportId": 123, "subjectId": 456 // 可为 null 表示解除绑定 } ``` **响应:** `Result`,`"归属已更新"` **归属变更触发联动:** - 旧归属成员(若有):五维能量重算 + 食材推荐指数刷新 - 新归属成员(若有):五维能量重算 + 食材推荐指数刷新 - 方案生成:`subject_id IS NULL` 的报告自动跳过(`triggerAutoPlanAfterConfirm` 已有此逻辑) **权限检查:** 通过 `familyId`(JWT 注入)验证报告属于当前家庭,跨家庭操作抛出 `RuntimeException("报告不属于该家庭")`。 --- ### 4.36.2 舌诊 AI 集成(`/api/health/report/*`) 舌诊功能已集成 `aikanshe.com`(愛看舌)开放 API,**作为首选方案**;当 aikanshe.com 不可用时回退至 LangGraph Python 服务。调用流程如下: **请求路径**:`POST /api/health/report/parse-preview`(`type=tongue`) **请求体示例**: ```json { "type": "tongue", // 必填:指定舌诊流程 "memberId": 12345, // 可选:家庭成员 ID "filePath": "/data/...",// 可选:图片路径(auto模式会先检测} } ``` **响应结构**: ```json { "code": 200, "message": "success", "data": { "recordId": 67890, // 舌诊记录 ID "imageUrl": "http://...", // 图片访问 URL "overallAssessment": "舌象基本正常...", // 整体评估 "indicators": [ // 7类舌诊指标 {"code": "tongue_color", "value": "淡红"}, {"code": "coating_color", "value": "薄白"}, {"code": "coating_texture", "value": "润"}, {"code": "fissure", "value": "无"}, {"code": "teeth_mark", "value": "无齿痕"}, {"code": "sublingual_vein", "value": "无点刺"}, {"code": "constitution", "value": "平和质"} ], "createTime": "2026-10-08T..." } } ``` **优先级方案**(aikanshe.com 优先,LangGraph 兜底): 1. **首选**:调用 `aikanshe.com` API(`POST /agency/quanxi`,基于 AI 视觉模型) 2. **兜底**:若 aikanshe.com 超时/失败,自动回退至 LangGraph Python 服务(`/api/v1/tongue/diagnose`) 3. **最终兜底**:两者均失败时返回默认 mock 数据,确保业务不中断 **置信度阈值**:默认 `0.2`,可通过 `application.yml` 的 `aikanshe.confidence` 配置。 **相关配置**: ```yaml aikanshe: appkey: ${AIKANSHE_APPKEY:} base-url: ${AIKANSHE_BASE_URL:https://api.aikanshe.com} token: ${AIKANSHE_TOKEN:} confidence: ${AIKANSHE_CONFIDENCE:0.2} lang: ${AIKANSHE_LANG:zh-CN} ``` **已废弃接口(保留,待清理)**: - `POST /api/health/report/upload-only`(PDF流程入口,已被 `/api/health/report/parse-preview?type=tongue` 部分取代) ### 4.37 报告反馈与 AI 解答(`/api/health/feedback/*`) 用户对健康报告提交疑问/建议,后端**持久化**到 `report_feedback` 表并调用 AI 解答(经 `AiGateway`,免费版每日 1 次,付费版不限)。反馈记录含 `aiAnswer`/`aiStatus`,前端可据此展示 AI 解答结果。 | 路径 | 说明 | |------|------| | `POST /api/health/feedback/submit` | 提交报告反馈(持久化 + AI 解答),请求 `{ reportId?, draftId?, memberId?, type, content }`,返回 `Result` | | `POST /api/health/feedback/list` | 我的报告反馈列表(含 AI 解答),请求 `{ reportId? }`,返回 `Result>`(按创建时间倒序,最多 50 条) | **`/api/health/feedback/submit` 请求体:** ```json { "reportId": 123, // 可选,关联健康报告ID "draftId": null, // 可选,关联解析草稿ID(reportId 与 draftId 二选一,均可为空) "memberId": 456, // 可选,关联家庭成员ID(默认当前成员) "type": "question", // question=疑问 / suggestion=建议,其他值默认 question "content": "这份报告里菌群多样性偏低是什么意思?" } ``` **响应示例(`Result`,`data` 字段):** ```json { "code": 200, "message": "ok", "data": { "id": 1, "userId": 1001, "memberId": 456, "reportId": 123, "draftId": null, "feedbackType": "question", "content": "这份报告里菌群多样性偏低是什么意思?", "aiAnswer": "菌群多样性反映肠道微生物的丰富程度……", "aiStatus": "answered", "conversationId": "conv_abc123", "createdAt": "2026-09-22 14:30:00" } } ``` - `aiStatus` 取值:`pending`(待解答)/ `answered`(AI 已解答)/ `failed`(AI 调用失败,仍会落库,`aiAnswer` 为空)。 - 免费版每日仅限 1 次反馈解答(Redis 计数 `report:feedback:free:{userId}:{date}`,`memberLevel=FREE` 或为空时生效),超限返回 `Result.error("免费版每日仅限 1 次反馈解答,升级会员解锁更多")`;Redis 异常时 fail-open 放行。 - `content` 必填且不超过 500 字。 --- ### 4.38 认知维度(`/api/cognitive/*`) 六维认知体系:感知(perception) / 专注(focus) / 记忆(memory) / 逻辑(logic) / 空间(spatial) / 加工速度(processingSpeed)。 | 路径 | 说明 | |------|------| | `POST /api/cognitive/child` | 获取孩子最新六维认知得分 | | `POST /api/cognitive/family` | 获取家庭所有孩子最新六维认知得分 | | `POST /api/cognitive/update` | 更新认知维度得分(由规划师录入,需已有 completed 行) | | `POST /api/cognitive/self-test` | 提交认知自测结果(自动创建档案,写入六维认知档案,综合分自动计算) | | `POST /api/cognitive/norms` | 获取认知维度百分位常模 | | `POST /api/cognitive/trend` | 获取认知维度历史趋势 | | `POST /api/cognitive/recommendations` | 获取认知训练建议(按薄弱维度生成) | | `POST /api/cognitive/recommendations/create-task` | 从建议创建每日任务 | **`/api/cognitive/self-test` 请求体:** ```json { "memberId": 12345, "dimension": "memoryScore", "score": 85 } ``` dimension 取值:`memoryScore` / `logicScore` / `perceptionScore` / `spatialScore` / `processingSpeedScore` score 范围:0-100(整数) **响应:** `Result`,成功返回 "提交成功",失败返回错误原因。 **行为:** 若该孩子尚无任何 `status=completed` 的 DanAssessmentResult 行,自动创建(source='self_test'),写入对应维度分,重新计算综合分(7 维度非空均值)。 **已记录认知自测 API 端点 — 禁止重复注册。** ### 4.39 珍珠图/能力图(`/api/pearl/*`, `/api/ability/*`) 关系三图体系:章鱼图(谁帮过我)→ 珍珠图(我有什么)→ 能力图(谁会什么)。珍珠图聚合 25 类社会连接(12 必备内圈 + 13 理想外圈),附带互动日志、价值分级、定期提醒。 | 路径 | 说明 | |------|------| | `POST /api/pearl/resources` | 珍珠图聚合:25 组(ESSENTIAL 内圈 + IDEAL 外圈)+ legacyGroups(历史资源) | | `POST /api/pearl/connection-types` | 获取 25 类连接类型列表(code/label/category/suggestIntervalMonth) | | `POST /api/pearl/item/add` | 登记珍珠图资源(connectionType 必填,type 可选,name 必填,contactId 可选) | | `POST /api/pearl/item/delete` | 删除登记的珍珠图资源(itemId) | | `POST /api/pearl/item/update-priority` | 手动覆盖优先级(itemId/priority 1-3),同步 valueScore | | `POST /api/pearl/interaction/add` | 记录互动(itemId/interactionType/content),更新价值分并关闭 PENDING 提醒 | | `POST /api/pearl/interaction/list` | 互动历史分页(itemId/page/size) | | `POST /api/pearl/reminder/list` | 待办提醒列表(PENDING) | | `POST /api/ability/map` | 能力图:SKILL 帮助记录按联系人聚合为能力节点 | **`/api/pearl/resources` 响应结构:** ```json { "code": 200, "data": { "groups": [ { "type": "MEDICAL", "typeName": "医疗", "category": "ESSENTIAL", "ring": "inner", "items": [ { "id": 1, "name": "社区王医生", "description": "儿科主治", "avatar": null, "valueScore": 72, "priority": 1, "contactId": 5, "lastInteractionAt": "2026-08-15T10:30:00", "interactionCount": 8, "daysSinceLast": 26 } ] } // ... 共 25 组,为空时 items=[] ], "legacyGroups": [ { "type": "PERSON", "typeName": "人脉", "items": [...] }, { "type": "SKILL", "typeName": "技能", "items": [...] }, { "type": "INFO", "typeName": "信息", "items": [...] }, { "type": "PLACE", "typeName": "场所", "items": [...] } ] } } ``` **价值分算法**(`/api/pearl/interaction/add` 后自动重算): - 类型基础权重:ESSENTIAL=30,IDEAL=20 - 近 90 天互动次数 × 5,上限 30 - 最近互动时效性:≤30天=+20,≤90天=+10,≤180天=+5 - 互动总量 × 2,上限 20 - 总分 0-100,`priority=1`(高/大珍珠)→ valueScore≥70,`priority=2`(中)→ 40-69,`priority=3`(低/小珍珠)→ <40 **`/api/ability/map` 响应结构:** `data` 为数组,元素结构同 SKILL 组 items(contactId/name/avatar/relationshipType/skillCount/skills/lastHelpedAt)。 **珍珠图前端组件:** `PearlDiagram.vue`(Canvas 2D,两层同心圆,内圈 radiusPct=0.28 / 外圈 0.55,珍珠半径按 priority/valueScore 映射 8-26px)。 **珍珠图定时提醒:** `PearlReminderScheduledTask` 每日 08:00 执行,遍历 connection_type 非空资源,daysSinceLast ≥ suggestIntervalMonth×30 时生成 PENDING 提醒。 **珍珠图交互流程:** 打开页 → 加载 resources → 渲染两层同心圆 → 点击珍珠弹窗(联系/优先级/删除)→ 记录互动 → 重算价值分 → 逾期角标红点。 **`/api/pearl/interaction/add` 请求体:** `{ "itemId": 1, "interactionType": "WECHAT", "content": "咨询过敏问题" }` interactionType 取值:`PHONE` / `WECHAT` / `MEETING` / `OTHER`。 **`/api/pearl/item/update-priority` 请求体:** `{ "itemId": 1, "priority": 1 }`(1=高/大珍珠,2=中,3=低/小珍珠)。 **珍珠图前端页面:** - 珍珠图:`pages/action-detail/index.vue`(内嵌 `PearlDiagram.vue` 组件) - 登记资源:`pages/action-detail/pearl-add-resource.vue`(25 类类型分组选择器) - 互动提醒:`pages/pearl-reminders/index.vue`(PENDING 提醒列表) **前端 API(`utils/api.js`):** - `getPearlResources()` - `getConnectionTypes()` - `addPearlResource(data)` (data.connectionType 必填) - `deletePearlResource(data)` - `addPearlInteraction(data)` - `getPearlInteractionList(data)` - `getPearlReminders()` - `updatePearlPriority(data)` **已记录珍珠图/能力图 API 端点 — 禁止重复注册。** ### 4.40 关键人拓展章鱼图(`/api/octopus/*`) **替代旧章鱼图(help_logs)**。核心:成效记录 → 关键人画像 → 高频词分析 → 拓展方向。 | 路径 | 说明 | |------|------| | `POST /api/octopus/tentacles` | ⚠️ **已废弃(410)** 旧章鱼图:触手榜 | | `POST /api/octopus/add` | ⚠️ **已废弃(410)** 旧章鱼图:新增帮助记录 | | `POST /api/octopus/delete` | ⚠️ **已废弃(410)** 旧章鱼图:删除帮助记录 | | `POST /api/octopus/records` | ⚠️ **已废弃(410)** 旧章鱼图:帮助记录明细 | | `POST /api/octopus/effect/list` | 成效记录分页列表(支持 effect_type 筛选) | | `POST /api/octopus/effect/add` | 新增成效(member_order_id + effect_type bitmask + amount + desc) | | `POST /api/octopus/effect/delete` | 删除成效(级联关系) | | `POST /api/octopus/key-person/add` | 新增关键人(姓名/单位/部门/职务/初识场合/如何认识) | | `POST /api/octopus/key-person/update` | 修改关键人画像 | | `POST /api/octopus/key-person/delete` | 删除关键人(级联关系) | | `POST /api/octopus/effect/key-persons` | 某成效的关键人列表 | | `POST /api/octopus/analysis/keywords` | 高频词统计(单位/部门/职务/场合/路径) | | `POST /api/octopus/analysis/summary` | 高频词提炼 ≤8 个拓展方向 | | `POST /api/octopus/expansion/suggest` | 基于分析推荐关键人画像模板(预填) | > **已记录关键人拓展 API 端点 — 禁止重复注册。** ### 4.41 DOA 个人目标(家庭打卡个人目标模块) | 路径 | 说明 | |------|------| | `POST /api/doa/stage/create` | 创建阶段(memberId?+title+durationWeeks+goals[1-3]) | | `POST /api/doa/overview` | 家庭总览:各成员当前 active 阶段 | | `POST /api/doa/stage/detail` | 阶段详情:stage + goals + weeks | | `POST /api/doa/stage/cancel` | 取消阶段(仅 active 可取消) | | `POST /api/doa/week/plan` | 提交周计划(A表周行动宣告) | | `POST /api/doa/week/review` | 提交周复盘(B表五问) | | `POST /api/doa/week/list` | 周记录列表(按阶段查周记录) | > **已记录 DOA 个人目标 API 端点 — 禁止重复注册。** ## 4.42 引导弹窗配置化管理 | 路径 | 说明 | |------|------| | `POST /api/guide/popup/list` | 查询当前用户应展示的激活弹窗列表 | | `POST /api/guide/popup/trigger` | 上报触发行为,返回匹配弹窗 | | `POST /api/guide/popup/consume` | 消费弹窗(点击/关闭上报,防止重复弹出) | | `POST /api/admin/guide/popup/list` | 管理端:列表(分页筛选 status/slug) | | `POST /api/admin/guide/popup/create` | 管理端:创建弹窗(含触发条件+步骤) | | `POST /api/admin/guide/popup/update` | 管理端:更新弹窗(全量覆盖步骤和条件) | | `POST /api/admin/guide/popup/status` | 管理端:启用/禁用(toggle) | | `POST /api/admin/guide/popup/delete` | 管理端:软删除 | | `POST /api/admin/guide/popup/stats` | 管理端:数据看板(曝光/点击/完成率/分时段趋势) | > **设计文档**:`docs/superpowers/specs/2026-09-22-guide-popup-config-design.md` > **实施计划**:`docs/superpowers/plans/2026-09-22-guide-popup-config.md` > **说明**:本功能将小程序弹窗引导页从硬编码改为配置化管理,支持管理员在后台配置弹出条件与内容。旧方案(sys_config onboarding_guide_config)保留,新方案通过 4 张新表 + 9 接口实现。 --- ## 六、待清理的废弃接口 | Controller | 废弃接口 | 替代方案 | 当前状态 | 清理条件 | |------------|----------|----------|----------|----------| |------------|----------|----------|----------|----------| | `FamilyController` | `/invite-code`, `/invite-code/generate` | `/api/family/invite/generate` | @Deprecated + 410 ✓ | 确认前端无调用 | | `FamilyUserController` | `/switch-mode`, `/switch-to-child` | `/api/family/member/switch` | @Deprecated + 410 ✓ | 确认前端无调用 | | `FamilyUserController` | `/switch-back-to-parent`, `/switch-back-verify` | `/api/family/member/switch` | 已修复:@Deprecated + 410 | 确认前端无调用 | | `ButlerController` | `/service-records`, `/commissions` | — | @Deprecated + 410 ✓ | 可删除 | | `TeacherController` | 全部 `/api/teacher/*` 方法 | `/api/guide/families/*` | @Deprecated + UnsupportedOperationException | 确认规划师端无调用 | | `DanAssessmentController` | `/current` | — | 需验证 | 无前端调用方 | | `NoticeController` | `GET /list`(违反 POST 规范) | `/api/admin/notices/list` | 已修复:@Deprecated + 410 | 可删除 | | `OctopusController` | `/tentacles`, `/add`, `/delete`, `/records` | `/api/octopus/effect/*`, `/api/octopus/key-person/*` | @Deprecated + 410 ✓(2026-09-10 替换为关键人拓展) | 确认前端无调用(旧页面已删除) | --- ### 4.37 情绪识别(`/api/mind/emotion/*`) 基于 DeepFace 的人脸情绪识别,通过 LangGraph `/api/v1/emotion/recognize` 调用。 | 路径 | 说明 | |------|------| | `POST /api/mind/emotion/analyze` | 上传照片进行情绪识别(multipart 文件) | | `POST /api/mind/emotion/analyze-url` | 通过 URL 进行情绪识别(兼容旧调用方) | **POST `/api/mind/emotion/analyze`** 请求:multipart/form-data,字段 `file`(JPEG/PNG,最大 10MB) 返回: ```json { "code": 200, "data": { "dominant_emotion": "happy", "dominant_label_zh": "开心", "emotions": [ { "emotion": "happy", "confidence": 0.85 }, { "emotion": "neutral", "confidence": 0.10 }, { "emotion": "sad", "confidence": 0.03 }, { "emotion": "surprise", "confidence": 0.01 }, { "emotion": "angry", "confidence": 0.005 }, { "emotion": "fear", "confidence": 0.003 }, { "emotion": "disgust", "confidence": 0.002 } ], "all_emotions": { "happy": 0.85, "neutral": 0.10, "sad": 0.03, "surprise": 0.01, "angry": 0.005, "fear": 0.003, "disgust": 0.002 } } } ``` **POST `/api/mind/emotion/analyze-url`** 请求体: ```json { "image_url": "https://cdn.example.com/photo.jpg" } ``` 返回:同上。 --- ### 4.43 商品赠送会员(`/api/membership/gift-membership-progress`) 家庭维度累计「赠会员商品」原价实付金额,达到阈值后自动赠送会员。 | 路径 | 说明 | |------|------| | `POST /api/membership/gift-membership-progress` | 获取当前家庭的赠会员累计进度 | **POST `/api/membership/gift-membership-progress`** 请求:无(familyId 由 JWT userId 反查) 返回: ```json { "code": 200, "data": { "familyId": 12, "accumulatedAmount": 158000, "granted": 0, "grantedLevel": null, "grantedOrderNo": null, "grantedAt": null, "threshold": 198000, "remaining": 40000 } } ``` | 字段 | 类型 | 说明 | |------|------|------| | `accumulatedAmount` | Integer | 累计赠会员商品原价金额(分),仅统计无积分/无券/无折扣的实付订单 | | `granted` | Integer | 1=已赠送 0=未赠送 | | `grantedLevel` | String | 已赠送的会员等级(当前全局配置默认 `FAMILY`) | | `threshold` | Integer | 阈值(分),由 `sys_config.gift_membership_threshold` 动态填入 | | `remaining` | Integer | 还差多少达标(分)= `max(0, threshold - accumulatedAmount)`,已达标为 `0` | > **注意**:`threshold`、`remaining` 为 `@TableField(exist = false)` 非数据库列,仅接口返回用。 > **无家庭时**:接口返回空对象,`threshold`/`remaining`/`accumulatedAmount` 均为 `null`(此时不存在赠会员累计口径),前端据此隐藏进度条。 > 阈值单位为分(1980 元 = 198000 分),实际生效值以 `sys_config` 表中 `gift_membership_threshold` 为准(`insertSysConfigSeed` 仅在缺失时插入,不覆盖已有配置)。 --- ### 4.44 统一服务角色申请(`/api/service-role/*`) 多角色(规划师/营养师/管家/供应商/内容管理员/活动策划)统一申请入口,一次提交可含多个角色,管理端审批通过后回写 `users.roles` 并按角色初始化附属记录。 | 路径 | 说明 | |------|------| | `POST /api/service-role/apply` | 提交服务角色申请(`roles` 必填) | | `POST /api/service-role/my-apply` | 查询我的最新申请记录 | | `POST /api/service-role/upload-image` | 上传身份证/资质证书图片(multipart,字段名 `file`) | | `POST /api/service-role/admin/list` | 管理端:申请列表(`status` 查询参数,默认 `0`) | | `POST /api/service-role/admin/approve` | 管理端:批准(`{ id }`) | | `POST /api/service-role/admin/reject` | 管理端:驳回(`{ id, reason }`) | > **权限**:`admin/list`、`admin/approve`、`admin/reject` 三个端点校验 `@RequestAttribute("role")`,仅 `role=admin` 或 `role=operator` 可访问(与 `AdminHealthPlanController` 审核类端点口径一致),其他角色返回「无权限」。前端菜单由 `perm: 'audit:service-role'` 控制。 **状态语义**(`service_role_applications.status`) | 值 | 含义 | |----|------| | `0` | 待审核(同一 userId 仅允许一条待审,重复提交返回「您已提交过申请,请等待审核」) | | `1` | 已通过 | | `2` | 已驳回(原因存 `reject_reason`) | **POST `/api/service-role/apply`** 请求体(`ServiceRoleApplyDTO`) | 字段 | 类型 | 说明 | |------|------|------| | `roles` | List\ | 申请角色,`@NotEmpty` 至少一个 | | `idCard` | String | 身份证号 | | `idCardImages` | List\ | 身份证图片 URL 数组(落库转 JSON 字符串) | | `certificateImages` | List\ | 资质证书图片 URL 数组 | | `province` / `city` / `district` / `street` | String | 四级地址 | | `addressDetail` | String | 详细地址 | **审批通过后的角色落地规则**(`ServiceRoleApplicationServiceImpl.approve`) | 申请角色 | 副作用 | |----------|--------| | `planner` | `guides` 新增一行(`status=active`)+ `users.roles` 追加 `teacher` | | `nutritionist` | `users.nutritionist_status=approved` + `roles` 追加 `nutritionist` | | `butler` | `users.butler_status=approved` + `butler_profiles` 新增一行 + `roles` 追加 `butler` | | `vendor` | `users.vendor_status=approved` + `roles` 追加 `supplier` | | `article_admin` | `users.article_admin_status=approved` + `roles` 追加 `knowledge_admin` | | `activity_provider` | `users.activity_provider_status=approved` + `roles` 追加 `activity_manager` | > **注意**:approve/reject 仅对 `status=0` 的记录生效,已处理返回「该申请已处理」。 > `upload-image` 对图片类型文件先走 `wechatService.checkImage()` 内容安全检测,未通过直接拒绝。 --- ### 4.45 规划师解除家庭绑定(`POST /api/guide/families/unbind`) | 路径 | 说明 | |------|------| | `POST /api/guide/families/unbind` | 规划师解除自己名下的家庭绑定(请求体 `{ id }`,即 `guide_families.id`) | - 角色校验:仅 `role=teacher` 可调用,否则返回 `Access denied`。 - 归属校验:`GuideFamilyService.unbindByGuide()` 按 `id + guide_id + status='binding'` 定位,只能解除属于自己的绑定。 - 副作用:将 `guide_families.status` 置为 `cancelled`。 - 家长侧解绑为独立接口 `POST /api/bind/unbind`(按 familyId 定位,并通知规划师)。 > **注意**:前端 `unbindFamily(id)` 需传 `guide_families.id`(绑定记录 ID),不是 `familyId`。 --- ### 4.46 规划师方案审核(`/api/health/plan/pending-review/*`) | 路径 | 说明 | |------|------| | `POST /api/health/plan/pending-review/list` | 待审核方案列表(`teacher` 取当前登录人;`admin` 可传 `teacherId` 过滤,不传则返回该家庭全部) | | `POST /api/health/plan/pending-review/update` | 编辑方案内容(仅 `status` 为 `draft`/`pending_review` 允许) | | `POST /api/health/plan/pending-review/approve` | 审核通过并发布 | | `POST /api/health/plan/pending-review/reject` | 驳回 | - 角色校验:仅 `role=teacher` 或 `role=admin`,且必须携带 `familyId`,否则返回 `familyId不能为空`。 - **未分配池规则**:`listPendingReviewPlans()` 在 `teacherId` 非空时按 `(teacher_id = ? OR teacher_id IS NULL)` 过滤 —— 规划师既能看到分配给自己的方案,也能看到本家庭内尚未指定规划师的方案(`health_plans.teacher_id` 为 `NULL`),避免用户未选规划师时保存的 `pending_review` 方案成为无人可见的孤儿记录。 - 入选范围限定 `status IN ('draft','pending_review')`,按 `created_at` 倒序。 > **注意**:`health_plans.teacher_id` 列由迁移 251 建立(`DatabaseInitializer`),`schema.sql` 已同步。 > 前端 `getPendingReviewPlans()`(`utils/api.js`)目前尚无页面消费,规划师审核页未接通;新审核工作台改用 `4.47` 的 `plans/pending` 端点,本节 4 个端点保留但不再被前端调用。 > ⚠️ 2026-10-04:`/pending-review/approve` 响应由 `Result` 改为 `Result`(新增 `taskGenerated` / `taskCount` / `message` 字段)。该端点无前端调用点(`health-plan-summary.vue` 仅有未调用的死导入),变更安全。 > ✅ 2026-10-04:`/{familyId}/overview`、`/{familyId}/children/{memberId}/tasks`、`/{familyId}/children/{memberId}/pending-review`、`/{familyId}/tasks/{taskId}/review`、`/{familyId}/tasks/batch-review`、`/{familyId}/tasks/{taskId}`、`/{familyId}/tasks/{taskId}/delete` 共 7 个端点已接入 `guide_families` 绑定校验,修复跨家庭越权。 --- ### 4.47 规划师审核工作台(`/api/guide/families/*`) 规划师侧审核工作台的统一入口,授权源为 `guide_families.status='binding'`(与 `GuideFamilyService.isBound(guideId, familyId)` 同源),不使用 `users.teacher_family_ids`。 | 端点 | 权限 | |---|---| | `POST /api/guide/families/my-families` | teacher / admin | | `POST /api/guide/families/{familyId}/members` | teacher 需 isBound;admin 放行 | | `POST /api/guide/families/{familyId}/plans/pending` | teacher 需 isBound;admin 可传 teacherId 过滤 | | `POST /api/guide/families/{familyId}/plans/{planId}/update` | 同上 + planId IDOR 反查 | | `POST /api/guide/families/{familyId}/plans/{planId}/approve` | 同上 + planId IDOR 反查;返回 `PlanApproveResultDTO` | | `POST /api/guide/families/{familyId}/plans/{planId}/reject` | 同上 + planId IDOR 反查 | - **`my-families`** 不走 `checkFamilyAccess(role, userId, null)` —— 该路径下 `familyId == null` 会把 teacher 直接拒掉,故改为显式角色判断 + 按 `guide_id` 查本人绑定家庭,返回体含 `familyName`(取自 `Family.name`,注意字段名不是 `familyName`)。 - **`members`** 返回该家庭可审核成员,供工作台「成员」选择器使用。 - **未分配池规则**:`plans/pending` 复用 `listPendingReviewPlans()`,`teacherId` 非空时按 `(teacher_id = ? OR teacher_id IS NULL)` 过滤;teacher 无需传该参数,admin 可显式传以过滤。 - **IDOR 防护**:`{planId}` 类端点先 `checkFamilyAccess(role, userId, familyId)`,再用 `getPlanForFamily(planId, familyId)` 按 familyId 反查,返回 `null` 即 403。控制器内 `resolvePlan` 的第二次 recheck 仅为纵深防御,`getPlanForFamily` 已按 familyId 过滤,**该 recheck 永不单独拦截请求**,不可依赖它替代首道校验。 - **`approve` 返回 `PlanApproveResultDTO`**:`planId` / `status` / `taskGenerated` / `taskCount` / `message`。任务生成器 `generateDailyTasksFromPlan` 返回 `void`,故实现改为生成后按 `source_type='health_plan' AND source_id=planId` 统计实际条数;生成异常或计数为 0 时方案仍发布并返回 `taskGenerated=false`,属**部分成功**,前端须与失败区分提示。 > **注意**:配套前端为 `pages/teacher/review-workbench.vue`、`review-family.vue`、`plan-detail.vue`,入口在 `pages/teacher/families.vue`。 --- ### 4.47.1 成长规划师咨询消息(`/api/guide/messages/*`) 家长(家庭主账号)与成长规划师之间的咨询消息接口。 | 端点 | 权限 | 说明 | |---|---|---| | `POST /api/guide/messages/send` | 家长(需 `familyId`) | 发送咨询,请求体 `{ teacherId, content }` | | `POST /api/guide/messages/{messageId}/reply` | 仅 `role=teacher` + 认证闸门 | 规划师回复,请求体 `{ replyContent }` | | `POST /api/guide/messages/list` | 仅 `role=teacher` | 规划师获取咨询列表(分页 `{ page, size }`),响应条目在 `content` 尾部追加 `[家庭主账号:<昵称>]` | | `POST /api/guide/messages/unread-count` | 仅 `role=teacher` | 规划师未读消息数 | | `POST /api/guide/messages/my` | 家长(按自身 `parentId` 查询,无 role 限制) | 家庭主账号获取自己的咨询记录,响应 `TeacherMessage` 列表 | - **`POST /api/guide/messages/my`**:家庭主账号获取自己的咨询记录。响应 `TeacherMessage` 列表,每条含 `teacherName`(规划师昵称,按 `teacherId` 从 `users` 表回填,查不到时留空由前端兜底「规划师」)。不新增路由、不改鉴权(`/my` 无 role 限制,按 `parentId` 查自己的记录)。 > 文档更新:2026-11-01(`/my` 新增 `teacherName` 回显,见 `specs/2026-11-01-home-greet-shortcut-and-my-messages-design.md`)。 --- ### 4.48 指标统一层(`/api/indicator/*` + `/api/admin/indicator/*`) 指标变化曲线 / 指标速查 / 指标关联的统一读接口与管理端接口。读接口 `subjectId` 必填且做家庭归属校验(`family_members.id = subjectId AND family_id = 当前用户家庭`,由 `JwtInterceptor` 注入 `@RequestAttribute("familyId")`);管理端接口校验 `@RequestAttribute("role")`,仅 `role=admin` 可访问,否则「无权操作」。 **视图接口(`IndicatorController`,`/api/indicator`)** | 端点 | 请求要点 | 用途 | |---|---|---| | `POST /api/indicator/trend` | `{subjectId, definitionIds:[...]}` | 指标曲线(按 definitionId 分组、report_date 升序点序列) | | `POST /api/indicator/change-ranking` | `{subjectId, reportType?, category?, scope:adjacent\|first-vs-latest, orderBy:up\|down\|magnitude, limit}` | 变化幅度排名(含 `overall.improvedCount/worsenedCount/flatCount` 汇总) | | `POST /api/indicator/quick-lookup` | `{subjectId, category?, page, pageSize}` | 指标速查(分类分页 + 类别汇总 + `dimensionScores` 五/七维评分小节) | | `POST /api/indicator/correlation/list` | `{subjectId, definitionId}` | 指标关联查询(含 `subjectEvidence` 成员级配对证据) | **管理端接口(`/api/admin/indicator/correlation/*`、`/api/admin/indicator/mapping/*`)** | 端点 | 请求要点 | 用途 | |---|---|---| | `POST /api/admin/indicator/correlation/save` | `{definitionIdA, definitionIdB, direction, manualStrength, description, manualLocked, sourceNote}` | 关联对定义/编辑(写入前按 `indicator_definitions.code` 字典序强制对序,保证唯一键对称) | | `POST /api/admin/indicator/correlation/list` | `{page, size}` | 关联对分页(按 `updated_at` 倒序,含校准状态) | | `POST /api/admin/indicator/correlation/calibrate` | `{pairId?}` | 手动触发校准(返回「校准完成,共更新 N 对」) | | `POST /api/admin/indicator/mapping/import` | `{sourceType, seeds:[{rawName, definitionId?}]}` | 映射种子批量导入(返回「导入完成,共 N 条」) | | `POST /api/admin/indicator/mapping/list` | `{page, size, status?, sourceType?}` | 映射规则分页/review(`status=0` 为 ai_fallback 待确认) | > **关联校准**:`save` 时 `effective_strength = manual_strength`;每日 03:00 `IndicatorCalibrationJob`(`@Scheduled(cron = "0 0 3 * * ?")`)对 `status=active` 且 `manual_locked=0` 的关联对按 subject 分组做 Spearman + Fisher z 聚合,有效样本 `>=30` 才用 `computed_strength` 覆盖 `manual_strength`。`correlation/calibrate` 手动触发同一逻辑(`pairId` 传入时仍走全量逐对幂等)。 > **统一指标层写入(单一写者)**:报告入库 / 报告编辑 / 食材推荐刷新三处流程由 `NormalizeIndicatorPipeline` 幂等写入 `indicator_values`(唯一键 `source_type + source_id + definition_id`,`upsertMapping` ON DUPLICATE KEY)。本层读接口只读 `indicator_values`,不写时序;五维/七维评分在速查接口按原表 join,不进时序。 > **关联表**:`indicator_correlations`(迁移 360)、`indicator_mapping_rules`(迁移 359)。 *文档更新:2026-10-09(指标统一层新增,见 `specs/2026-10-09-indicator-change-curve-design.md`)* > **⚠️ `/api/health/indicator/list` 已被取代**:老接口返回裸 `List`(无上一期对照、无 summary)。新接口 `/api/health/report/indicators` 返回同一批指标 + `previous` / `change` / `summary` 五档形态数据。`/api/health/indicator/list` **保留不动**(cfc-web `views/nutritionist/HealthIndicators.vue` 仍在调),新接入一律走 `/api/health/report/indicators`。 --- ### 4.48.1 指标多期展示 · 后端接口(`/api/health/report/indicators`、`/api/indicator/periods`、`/api/admin/indicator/backfill`) > 本节为 §4.48 指标统一层的多期展示增量。**所有 `change` 计算统一由 `IndicatorChangeService.computeChange` 单点产出**(trendCode 五档:`improved` / `worsened` / `flat` / `first_record` / `pending_backfill`),读路径一律过滤 `indicator_definitions.status=1`,拿不到 active definition 的观测**直接剔除,不降级为「指标」+id 假名称**(QP17)。 **① `POST /api/health/report/indicators`** — 报告指标明细(含上一期对照,D12 报告详情页接入点) | 端点 | 权限 | 请求 | 返回 | |---|---|---|---| | `POST /api/health/report/indicators` | 登录(JWT;管理员/本人/被授权成员,校验逻辑同 `/api/health/report/detail`) | `{reportId, withPrevious?}` | `{reportId, reportType, subjectId, reportDate, dateSource, olderReportCount, summary, items}` | - **入参**:`reportId`(必填);`withPrevious`(选填,默认 `true`,`false` 时跳过 previous/change 计算,供编辑模式快速返回单期)。 - **权限**:无 `subjectId` 的报告(历史未绑定成员)仅**本人上传者或家庭管理员**可见;有 `subjectId` 时走 `ReportMemberPermissionService.canViewMemberReports`。 - **items[]** 每项含:指标原字段(`indicatorId`/`definitionId`/`category`/`indicatorName`/`indicatorValue`/`numericValue`/`unit`/`refRange`/`status`)+ 字典字段(`goodDirection`/`valueType`)+ `previous`(上一期观测,无则 null)+ `change`(`{delta, deltaPct, movement, trendLabel, trendCode, skipGaps, unitMismatch, comparable}`,首期时为 `first_record`,报告日期未知时为 `pending_backfill`)。 - **summary**:`totalCount` / `abnormalCount`(`status` 非「正常」)/ `abnormalDelta`(预留字段,当前固定 `0`,单报告视角无法直接算)/ `changedCount`(`trendCode` 为 improved/worsened 的条数)/ `comparableCount` / `firstRecordCount` / `pendingCount` / `dataIncomplete`(存在 pending_backfill 时为 true,驱动残缺横幅组件)。 - **与 `withPrevious=false`**:返回单期 items(每项含 `indicatorId/category/indicatorName/indicatorValue/unit/refRange/status`,**不含** `definitionId/previous/change/goodDirection/valueType`)。 - **`olderReportCount`**:该成员在本报告之前(同 sourceType)的历史报告数,用于 `first_record` 档区分「真的首期」与「更早报告未回填」。 - **N+1 约束**:42 项指标一次请求的 SQL ≤ 5 次(指标行 1 + 上期批量解析 1 + 字典/定义 1 + 报告 1);`IndicatorMappingService.resolveBatch` 用 `indicator_mapping_rules` JOIN `indicator_definitions` 单查询批量解析,**严禁逐指标查库**。 **② `POST /api/indicator/periods`** — 期次列表(双期对比页 picker 数据源) | 端点 | 权限 | 请求 | 返回 | |---|---|---|---| | `POST /api/indicator/periods` | 登录(JWT;`subjectId` 须属于当前家庭) | `{subjectId, sourceType?}` | `{subjectId, sourceType, items}` | - **items[]**:`{reportDate, reportId, dateSource, indicatorCount}`,按 `reportDate` 倒序;`reportDate=null`(日期未知期)**恒定排最后**,picker 显示「日期未知」。 - **dateSource**:该期所有观测中**最差**的一个(`unknown < confirm < upload < real`),诚实口径——只要有一项来源是上传推断,整期就标 `upload`。 - **indicatorCount**:该期有有效值(`value` 非空)的指标数,picker 显示「2026-10-08(42 项)」。 - **不分页**(单人单类型期次数实测 < 30);只读幂等。 **③ `POST /api/admin/indicator/backfill`** — 存量时序回填(admin) | 端点 | 权限 | 请求 | 返回 | |---|---|---|---| | `POST /api/admin/indicator/backfill` | admin(`@RequestAttribute("role")` 校验,非 admin 返回「权限不足」) | `{subjectId?, reportId?, limit?, dryRun?, fixReportDates?}` | `{scanned, eligible, written, skippedNoSubject, skippedNoPayload, failed, dryRun, fixReportDates, dateFixable, dateFixed, details[]}` | - **用途**:把 confirm 链路补写之前的历史报告补进 `indicator_values`(P0-3 数据面补全),并可顺带用 `ReportDateExtractor` 修复被 `new Date()` 污染的历史报告日期。 - **入参**:`subjectId`(选填,指定成员;不传扫全部 `status=active` 报告);`reportId`(选填,只回填指定报告,灰度验证用);`limit`(选填,默认 200,上限 500);`dryRun`(默认 `true`);`fixReportDates`(默认 `false`)。 - **`dryRun=true`**:预演只统计不写入,返回 `eligible`/`dateFixable` 计数,且**不触发 `mappingService.resolve`**(避免惰性建定义这类副作用);**首次执行必须 dryRun,核对无误后传 `false`**。 - **顺序红线**:先修日期、再补时序(污染日期若固化进 `indicator_values` 再修就晚了)。日期修复判定条件:能提取到真实日期 且 `report_date != extracted` 且 `report_date >= created_at` 当天(`new Date()` 污染特征签名);修后需连带 `refreshMemberIndicatorCurrent`。 - **幂等**:`NormalizeIndicatorPipeline` 按唯一键 `source_type + source_id + definition_id` upsert,已覆盖报告返回 `already_covered`,重复执行不产生重复时序。 **④ 升级改造的既有端点(`IndicatorController`)** | 端点 | 变更 | |---|---| | `POST /api/indicator/trend` | 新增可选入参 `{sourceType, fromDate, toDate, granularity: raw\|month, sourceKind: all\|report\|device}`;出参新增 `refLow/refHigh/goodDirection/valueType/pointsCount/totalReportCount/olderReportCount/chartMode/unitMismatch/latest/change/points/missingPeriods`。**破坏性:无** | | `POST /api/indicator/change-ranking` | 新增可选入参 `{scope: pair, fromReportDate, toReportDate, filter: improved\|worsened\|flat\|incomparable\|all, page, pageSize}`;`scope=pair` 缺 `fromReportDate`/`toReportDate` 返回明确错误;**`magnitude` 排序口径从 `\|Δ\|` 改为 `\|Δ%\|` 降序**(视觉有变化,属预期修正,见 §4.48 变更汇总);`deltaPct=null` 的项恒定排最后且不参与 improved/worsened 筛选(Q14)。**破坏性:无** | | `POST /api/indicator/quick-lookup` | 新增可选入参 `{withSparkline, sparkLimit}`;出参每项新增 `previous`/`change`/`latestDateSource`/`latestReportId`/`goodDirection`/`valueType`/`sparkline`;**修复 `reportDate=null` 观测被当作最新值的 bug**。**破坏性:无** | | `POST /api/indicator/source-types` | 出参新增 `periodCount` / `latestReportDate`(曲线页类型筛选用,原只返回类型名)。**破坏性:无** | > **`chartMode` 判定表**(后端单点定义,`IndicatorChangeService.chartModeOf`):`pointsCount=0 → empty`;`=1 → single`;`=2 → pair`;`<20 → line`;`≥20 → line_aggregated`(`granularity=month` 时强制 `line_aggregated`)。 > **SQL 预算(严禁 N+1,均有单测锁死)**:`/report/indicators` 42 项指标 → 服务层恒 3 次协作调用(控制器另加 2 次,合计 ≤ 5);`/quick-lookup` 与 `/source-types` 均恒 1 次 `indicator_values` 查询(上一期与 `periodCount` 全部内存计算)。 > **`IndicatorService.listDefinitions`**:`status` 传 null 时**默认按 `status=1` 过滤**(防止 P1-6 自动创建的 `status=0` definition 冒进管理端字典列表,QP17)。 > **`missingPeriods`**:该成员有报告、但本指标无有效值的期次,`IndicatorTimelineService` 固定 2 次查询算完(原 1+缺失期数,会 N+1)。 > **老接口兼容矩阵**见 `specs/2026-10-25-indicator-timeline-display-design.md` §4.7。 *文档更新:2026-10-27(指标多期展示 T02 后端接口,见 `specs/2026-10-25-indicator-timeline-display-design.md`)* --- ### 4.48.2 指标收藏 · 后端接口(`/api/indicator/favorite/*`) > 本节为 T04「关注指标收藏」后端化(方案A)增量。收藏从 localStorage(方案B)升级为后端持久化,可跨设备同步、跨端一致。**全部 `@PostMapping`**;`JWT Bearer` 鉴权;`@Resource` 注入;`userId`/`familyId` 由 `JwtInterceptor` 注入 `@RequestAttribute`,服务端不信任前端传值。 **① `POST /api/indicator/favorite/toggle`** — 收藏/取消(存在则删、不存在则插,幂等于单次调用) | 端点 | 权限 | 请求 | 返回 | |---|---|---|---| | `POST /api/indicator/favorite/toggle` | 登录(JWT;已加入 `LOGIN_REQUIRED_PATHS`,无 token 直接 401;`memberId` 须属于当前家庭) | `{memberId, sourceType, definitionId}` | `{definitionId, added}` | - **入参**:`memberId`/`definitionId`(必填 Long)、`sourceType`(必填 String,非空)。 - **返回**:`added=true` 本次加入收藏(新插入);`added=false` 本次取消收藏(已删除)。 - **错误码**:`400`(缺参/成员不属家庭/definitionId 非法)、`401`(未登录)。 **② `POST /api/indicator/favorite/list`** — 查已收藏集合 | 端点 | 权限 | 请求 | 返回 | |---|---|---|---| | `POST /api/indicator/favorite/list` | 登录(JWT;`memberId` 须属于当前家庭) | `{memberId, sourceType}` | `{memberId, sourceType, definitionIds}` | - **`definitionIds`**:该成员×该报告类型下已收藏的指标定义 ID 列表(升序,按 `id`);空集合返回 `[]`。 - 查询范围限定 `user_id = userId AND family_id = familyId AND member_id AND source_type`(家庭/用户隔离)。 **③ `POST /api/indicator/favorite/remove`** — 移除收藏(显式删除) | 端点 | 权限 | 请求 | 返回 | |---|---|---|---| | `POST /api/indicator/favorite/remove` | 登录(JWT;`memberId` 须属于当前家庭) | `{memberId, sourceType, definitionId}` | `{definitionId, removed}` | - **`removed=true`**:该条收藏已删除(不存在则视作已删除,幂等)。供前端「清空对比/切换成员」等需显式删除而非 toggle 语义的场景使用。 *文档更新:2026-10-30(T04 指标收藏后端化,见 `specs/2026-10-30-indicator-favorite-backend-design.md`)* --- ### 4.49 健康方案删除(`/api/health/plan/delete`、`/api/admin/health-plan/delete`) 已生成的健康方案可删除,**级联删除其产生的全部任务**(含每日实例)。 | 端点 | 权限 | 请求 | 返回 | |---|---|---|---| | `POST /api/health/plan/delete` | 登录用户(JWT) | `{planId}` | `{planId, taskCount}` | | `POST /api/admin/health-plan/delete` | admin/teacher/butler/nutritionist/operator | `{planId}` | `{planId, taskCount}` | - **级联范围**:`health_plans` 方案行 + `tasks` 中 `source_type='health_plan' AND source_id=planId` 的直接任务(周期模板/一次性任务/购买锚点)+ `tasks` 中 `template_id IN 直接任务ID` 的每日实例(`RepeatTaskGenerator` 生成,只透传 `template_id` 不带 `source_id`,必须二次删除,否则任务删后次日仍被再生产)。 - **保留不删**:`points_log`(积分流水,已完成任务的积分保留不撤回)、`task_executions` / `growth_task_log`(执行历史)。 - **事务**:`HealthPlanServiceImpl.deletePlanCascade` 标注 `@Transactional(rollbackFor = Exception.class)`,任一步失败整体回滚,不留无方案的孤儿任务。 - **IDOR**:用户端接口从 `@RequestAttribute("familyId")` 取家庭做归属校验,方案不属于该家庭返回「无权删除该方案」;管理端传 `familyId=null` 跳过归属校验。 - **状态**:任何状态(draft/pending_review/published/rejected)均可删除;前端以确认弹窗兜底提醒。 *文档更新:2026-10-17(健康方案级联删除,见 `specs/2026-10-17-health-plan-deletion-design.md`)* ### 4.50 活动海报模板(`/api/admin/poster-template/*` + `/api/poster-template/*`) Web 管理端定义活动海报模板(迁移381 `activity_poster_templates` 表),小程序端活动详情页「生成海报」拉取可用模板渲染画布。管理端接口校验 `@RequestAttribute("role")`,仅 `admin`/`operator` 可访问;`templateKey` 限定 `gradient`(渐变)/`minimal`(深色)/`card`(白底卡片),`name` 全局唯一。 | 端点 | 权限 | 用途 | |---|---|---| | `POST /api/admin/poster-template/list` | admin/operator | 模板列表(含停用,按 sort_order + id 升序) | | `POST /api/admin/poster-template/save` | admin/operator | 新增/更新(body 为实体字段:`id/name/templateKey/mainColor/darkColor/lightColor/bgColors/qrSize/sortOrder/status`;`id` 为空为新增;色板须为合法 JSON 数组,空则按主色生成,`qrSize` 默认 100) | | `POST /api/admin/poster-template/delete` | admin/operator | 删除(body: `{id}`) | | `POST /api/poster-template/list` | 登录 | 小程序端可用模板(`status=1`;生成海报场景本身要求登录) | *文档更新:2026-10-17(活动海报模板管理,见 `specs/2026-10-17-activity-poster-template-design.md`)* --- ### 4.50 会员权益对比表(`/api/membership/compare`) 购买页(`pages/membership/upgrade.vue`)与会员中心(`pages/membership/index.vue`)的权益对比表数据源。**全部权益值由后端聚合实际逻辑后返回展示文本**,前端不再硬编码权益文案,避免出现「文案说不限量 / 实际 20 个」这类偏差。 | 路径 | 说明 | |------|------| | `POST /api/membership/compare` | 返回 `{columns, rows}`,无需请求参数 | **返回值结构** ```json { "code": 200, "data": { "columns": ["FREE", "BASIC", "FAMILY", "PREMIUM"], "rows": [ { "name": "商品折扣", "free": "普通价格", "basic": "—", "family": "9折", "premium": "8折" }, { "name": "家庭成员上限", "free": "1人", "basic": "3人", "family": "6人", "premium": "10人" }, { "name": "每日任务上限", "free": "5个/日", "basic": "3个/日", "family": "20个/日", "premium": "50个/日" } ] } } ``` **行值口径(事实源)** | 权益行 | 数据来源 | |--------|----------| | 商品折扣 | `sys_config.member_discount_family` / `member_discount_premium`(bps:9000→9折、8000→8折),BASIC 无折扣配置显示 `—` | | 家庭成员上限 | `membership_levels.max_children`(0/空视为无该权益 → `—`) | | 每日任务上限 | `membership_levels.max_tasks_per_day` | | AI健康顾问 / 报告解读 / 测评预约 / 食材图片解析 | `BasicQuotaService.QUOTA_LIMITS` 月度配额常量(BASIC 限次)+ `canUseFeature` 判定(FAMILY/PREMIUM 不限) | | 优惠券权益 | 普通券面向所有登录用户(`discount_purchase` feature,BASIC 起可用);会员专享券仅 FAMILY/PREMIUM(`CouponService.canUseMemberCoupon`) | | 会员专属活动 | `member_activities` feature,BASIC 及以下为部分活动 | | 邀请佣金 / 消费返佣 | `referral_commission` / `purchase_commission` feature | | 专属管家、规划师咨询 | `membership_levels.priority_support` / `teacher_consultation` 对应的产品权益设定 | > **列口径**:仅 `FREE/BASIC/FAMILY/PREMIUM` 四列。`LIFETIME` 为赠送/特殊发放、不可购买,不作为列展示;`PROVIDER` 是**服务商角色**,不属于会员权益体系(`level_order=1` 与 FREE 同级,`features` 为 NULL),不参与本表。 > **`/api/membership/plans` 同步变更**:除 `LIFETIME` 外再过滤 `PROVIDER`,并附加 `maxChildren` / `maxTasksPerDay` 两个字段,前端档位卡片可直接引用。 *文档更新:2026-10-18(会员权益对比表动态化 + PROVIDER 退出会员权益体系)* --- *文档最后更新:2026-10-04* --- ### 5.0 减脂专题(`/api/fatloss/*`) > 减脂专题(`xiaofat-miniprogram`)新增接口,与主系统用户体系共享(JWT 认证,需登录)。 > 所有接口统一 `@PostMapping`,响应格式 `Result`。 | 端点 | 权限 | 说明 | |---|---|---| | `POST /api/fatloss/weight/list` | 登录 | 体重记录列表(params: `memberId/startDate/endDate`,memberId 优先取 params,fallback currentMemberId) | | `POST /api/fatloss/weight/create` | 登录 | 创建体重记录(body: `memberId/weight/bodyFatPercent/waistCircumference/measureDate/remark`) | | `POST /api/fatloss/weight/update` | 登录 | 更新体重记录(body: `id/weight/bodyFatPercent/waistCircumference`) | | `POST /api/fatloss/weight/delete` | 登录 | 删除体重记录(body: `id`) | | `POST /api/fatloss/diet/list` | 登录 | 饮食记录列表(params: `memberId/startDate/endDate`) | | `POST /api/fatloss/diet/create` | 登录 | 创建饮食记录(body: `memberId/mealType/foodName/foodWeight/calories/protein/fat/carbs/eatDate/remark`) | | `POST /api/fatloss/diet/delete` | 登录 | 删除饮食记录(body: `id`) | | `POST /api/fatloss/diet/calories-summary` | 登录 | 当日总热量(params: `memberId/date`,date 默认今日) | | `POST /api/fatloss/exercise/list` | 登录 | 运动记录列表(params: `memberId/startDate/endDate`) | | `POST /api/fatloss/exercise/create` | 登录 | 创建运动记录(body: `memberId/exerciseType/durationMin/distanceKm/caloriesBurned/exerciseDate/remark`) | | `POST /api/fatloss/exercise/delete` | 登录 | 删除运动记录(body: `id`) | | `POST /api/fatloss/goal/active` | 登录 | 获取当前活跃目标(params: `memberId`,返回最新 `status=active` 的目标) | | `POST /api/fatloss/goal/create` | 登录 | 创建目标(body: `memberId/startWeight/targetWeight/targetBodyFat/targetDate/startDate`,targetWeight/startWeight/startDate 必填) | | `POST /api/fatloss/goal/update` | 登录 | 更新目标(body: `id/targetWeight/targetBodyFat/targetDate`) | | `POST /api/fatloss/goal/complete` | 登录 | 完成目标(body: `id`,status → `completed`) | | `POST /api/fatloss/goal/abandon` | 登录 | 放弃目标(body: `id`,status → `abandoned`) | **新增表:** `fat_weight_record`、`fat_diet_record`、`fat_exercise_record`、`fat_goal`(迁移 355–358,DatabaseInitializer) *文档更新:2026-10-09(减脂专题新增)* --- ### 4.53 限时特价(扫码闪购) > 设计规格:`specs/2026-10-20-limited-time-special-price-design.md`; > 新建 `special_price_deals` 表(迁移 389)+ `product_orders.special_deal_id` 列(迁移 390)。 **用户端(公开接口,无需登录)** | 路径 | 说明 | |---|---| | `POST /api/special-price/info` | 查询活动详情(白名单,JwtInterceptor 公开路径;`userPurchased`/`userRemaining` 恒 0,每人限购由下单接口兜底) | 请求体:`{ "dealId": 1 }` 响应 `data` 字段: ```json { "id": 1, "productId": 10, "productName": "示例商品", "name": "开售闪购", "specialPrice": 500, "productPrice": 990, "startTime": "2026-10-20 12:00:00", "endTime": "2026-10-21 12:00:00", "totalQuantity": 100, "soldQuantity": 23, "remainingQuantity": 77, "perUserLimit": 2, "allowCoupon": 0, "runStatus": "ACTIVE", "userPurchased": 0, "userRemaining": 0 } ``` `runStatus` 枚举:`ACTIVE`(进行中)/`NOT_STARTED`(未开始)/`SOLD_OUT`(已售罄)/`ENDED`(已结束)/`DISABLED`(已停用)/`DRAFT`(草稿)/`INVALID`(活动不存在)。白名单路径不写 request attribute,每人限购由下单接口最终校验。 **下单(特价路径)** `POST /api/product/order/create` 请求体新增 `specialDealId`(Long,可选)。若 `specialDealId != null` 走特价下单分支: 1. 校验活动存在、`status=active`、`NOW() ∈ [start_time, end_time]`、商品一致 2. 每人限购校验(`countByDeal` 累计 < `perUserLimit`) 3. `allowCoupon=0` 且 `couponId` 非空 → 拒绝 4. 原子扣量 `consumeQuota(id, qty)`(失败按 runStatus 返回明确原因) 5. `insert` 订单行 `special_deal_id = dealId`,`unit_price = specialPrice` 6. 回滚挂载点:`cancel`(用户取消)、`approveRefund`(退款完成)、`OrderTimeoutCancelTask`(超时取消),三处调用 `releaseQuota`(`GREATEST(sold_quantity - qty, 0)` 防负) **供应商改价拒绝**:`reviewUpdate` 开头若 `order.specialDealId != null` 直接 `Result.error("特价订单不允许修改价格")`。 **管理端(admin 角色)** | 路径 | 权限 | 说明 | |---|---|---| | `POST /api/admin/special-price/list` | admin | 分页列表(body: `{page,size,keyword,runStatus}`;`runStatus` 可传 `ACTIVE/NOT_STARTED/SOLD_OUT/ENDED` 或 `draft/disabled`,空=全部;响应 `data.records[]` + `data.total`) | | `POST /api/admin/special-price/create` | admin | 新建(body: `{productId,name,specialPrice,startTime,endTime,totalQuantity,perUserLimit,allowCoupon}`;`specialPrice`/`productPrice`/`totalQuantity` 单位均为分;同商品互斥 active) | | `POST /api/admin/special-price/update` | admin | 修改(body 同 create + `id`;仅 `draft/disabled` 可修改) | | `POST /api/admin/special-price/status` | admin | 启停(body: `{id, action}`;`action ∈ {enable, disable}`;启用时再次校验互斥) | | `POST /api/admin/special-price/delete` | admin | 删除(body: `{id}`;已有特价订单时拒绝) | | `POST /api/admin/special-price/qrcode` | admin | 生成小程序码(body: `{id}`;`scene=id={productId}&sp={dealId}`,落地页 `product-detail`;返回 `data:{qrCodeBase64, scene, note?}`;测试模式 note 提示无法生成真实码) | *文档更新:2026-10-20(限时特价/扫码闪购新增)* ### 4.54 活动报名情况(`POST /api/activity/registration-roster`) > 活动详情页「报名情况」区块数据源。面向所有访客(含游客,`/api/**` 下无需登录),返回脱敏的报名统计与最近已通过报名人,用于社交证明与名额紧迫感展示。姓名经 `DataMaskUtil.maskName` 脱敏(如"张小明"→"张某"),**不返回手机号**。 **请求体:** `{ "activityId": 1 }`(兼容 `id`) **响应 `data` 字段:** ```json { "activityId": 1, "maxParticipants": 50, "approvedCount": 12, "pendingCount": 3, "remaining": 38, "registrants": [ { "name": "张某", "registeredAt": "2026-10-22T10:00:00" } ] } ``` | 字段 | 说明 | |---|---| | `maxParticipants` | 人数上限(0 = 不限制) | | `approvedCount` | 已通过报名人数 | | `pendingCount` | 待审核报名人数 | | `remaining` | 剩余名额 = `max - approved`(不限制时为 `null`,下限 0) | | `registrants` | 最多 20 条已通过报名人(按报名时间倒序,姓名脱敏) | 前端使用:`activity-detail.vue` 加载详情后并行拉取;报名成功/取消报名后重新拉取。区块仅当 `activity.requireRegistration === 1` 时展示,含统计列、进度条(限人数时)与最近报名人列表。 *文档更新:2026-10-22(活动详情页新增报名情况)* ### 4.55 活动报名/签到/问卷(本次回归补登记) > 以下接口在 2026-10-23 的 T15/T16/T17 任务链路中新增或变更,此前未在本文档登记,此处统一补录。 #### 4.55.1 活动报名(`ActivityController`,前缀 `/api/activity`) | 路径 | 说明 | |---|---| | `POST /api/activity/register` | 活动报名(body: `activityId`;批量报名历史字段 `registrants`)。活动 `requireReview=1`(默认免审)→ 报名记录为 `pending` 不占名额;免审即 `approved` 并原子占用名额 | #### 4.55.2 活动签到与签到审核(`ActivityController`) | 路径 | 说明 | |---|---| | `POST /api/activity/checkin` | 活动签到(body: `activityId`)。活动级 `requireCheckinReview=1` 时签到进入 `pending_review`,审核通过才发奖励;免审直接发放 | | `POST /api/activity/checkin-review` | 签到审核(仅活动管理员/平台管理员)。通过发放积分/能量/CF,驳回不发放 | #### 4.55.3 管理端签到(`AdminActivityCheckinController`,前缀 `/api/admin/activity-checkins`) | 路径 | 说明 | |---|---| | `POST /api/admin/activity-checkins/list` | 按活动分页查询签到记录 | | `POST /api/admin/activity-checkins/add` | 手动补录签到(幂等,桥接触发课时入账) | | `POST /api/admin/activity-checkins/review` | 修改签到审核状态(通过发放课时/奖励) | **与规划师课时桥接**:签到审核通过会触发课时发放,与 `AdminPlannerOfflineCourseController` 的活动↔类别映射联动(见第五章)。 #### 4.55.4 家庭关系问卷(`RelationshipQuestionnaireController`,前缀 `/api/family/questionnaire`) | 路径 | 说明 | |---|---| | `POST /api/family/questionnaire/generate` | AI 生成关系问卷快照(body: `memberId`;`respondentId` 由后端按登录用户覆盖,校验调用人与目标成员同家庭),返回题目集与 `snapshotId` | | `POST /api/family/questionnaire/submit` | 提交答卷(body: `snapshotId` + 答案),写入响应记录 | | `POST /api/family/questionnaire/snapshot/{memberId}` | 查询某成员当前快照 | | `POST /api/family/questionnaire/history/{memberId}` | 某成员问卷历史提交记录 | #### 4.55.5 报名名册数据口径(2026-10-23 本次回归涉及) `POST /api/activity/registration-roster` 的 `approvedCount` 现直接读 `activities.approved_count`(迁移回填),不再实时统计;剩余名额 = `max - approved`,下单/退款链路(`register`/`cancel`/`OrderTimeoutCancelTask`)通过原子 SQL 维护该计数,防超卖。 #### 4.55.6 审核中心统计口径变更(2026-10-23) `ReviewCenterController` 的 `guidePending` / `nutritionistPending` 统计新增 `real_name IS NOT NULL` 过滤,与列表接口口径一致(commit `f61547a21`);前端 `ArticleAudit.vue` 驳回原因字段由 `row.reason` 修正为 `row.rejectReason`。 *文档更新:2026-10-23(T15/T16/T17 回归补登记:活动报名、签到与签到审核、管理端签到、家庭关系问卷、名册口径、审核中心统计口径)* --- ### 4.56 活动管理团队(`ActivityTeamController`,前缀 `/api/activity/team`)—— T35 > **T35 规格 §2.2/§5**:新增「活动管理团队」类型,`activities.team_id` 关联 `activity_team`;团队成员可协作运营团队名下活动,复用服务指派机制(`service_type=ACTIVITY_SUPPORT`,见 T34)。团队数据独立于 supply_system(供应体系),避免活动运营人员误入商品分销同体系判定。 **权限规则:** - 平台活动角色(admin / activity_admin / activity_manager)可管理全部团队; - 团队团长/运营主管(owner/manager)可管理本团队; - 普通在团成员可查看与协作(编辑活动/报名审核/签到等)。 | 路径 | 说明 | |---|---| | `POST /api/activity/team/create` | 创建活动团队(body: `{name, description?}`,管理员可指定 `ownerId`;普通用户以自己 userId 建团,自动成为团长 owner) | | `POST /api/activity/team/list` | 我的团队列表(管理员返回全部;普通用户返回在团团队),含 id/name/description/ownerId/status | | `POST /api/activity/team/detail` | 团队详情(body: `{teamId}`),返回 team + members | | `POST /api/activity/team/member/list` | 团队成员列表(body: `{teamId}`) | | `POST /api/activity/team/member/add` | 添加成员(body: `{teamId, userId, role?}`,role 可选:owner/manager/operator,默认 operator;幂等:已在团则更新角色) | | `POST /api/activity/team/member/remove` | 移出成员(body: `{teamId, userId}`,团长不可被移出;移出时级联撤销该成员在团内的 ACTIVITY_SUPPORT 指派) | | `POST /api/activity/team/assign` | 服务指派(ACTIVITY_SUPPORT,body: `{teamId, targetType, targetId, assigneeId, role, remark?}`,targetType: ACTIVITY/ACTIVITY_TEAM,role: lead/support/checkin/content;被指派人须为团队在团成员) | | `POST /api/activity/team/assign/list` | 服务指派列表(body: `{teamId, targetType?, targetId?}`) | | `POST /api/activity/team/assign/revoke` | 撤销服务指派(body: `{id, teamId?}`) | | `POST /api/activity/team/activities` | 团队名下活动列表(body: `{teamId}`) | **ActivityController 权限扩展(T35 协作判定):** - `/api/activity/create`:团队活动(`teamId` 非空)须由团队在团成员创建; - `/api/activity/update`:团队活动的编辑权限扩展到团队在团成员 + 该活动(或其团队)的 ACTIVITY_SUPPORT 被指派人。 *文档更新:2026-11-01(T35 活动管理团队:活动团队创建/成员管理/服务指派、ActivityController 协作权限扩展)* --- ## 4.48 组织管理(机构/企业组织) 平台角色(admin/operator/finance)可管理所有组织并查看全量统计;组织管理员(`users.roles` 含 `org_admin`)登录管理端后默认跳转自身组织统计大屏,仅能访问本组织数据。实现方案见规格 `docs/superpowers/specs/2026-10-22-org-admin-management-design.md`。 **数据模型:** - `organizations`:组织主表(name/type/code/admin_user_id/contact_*/description/status) - `org_members`:组织成员关联表(org_id/user_id/member_type/role_in_org/status),唯一键 `(org_id, user_id)` **权限规则:** - 平台角色(admin/operator/finance)→ 全量访问 - `org_admin` → 仅能访问自身组织(`organizations.admin_user_id = userId` 且 `status='active'`),返回 403「无权访问该组织」/「组织已停用」 - 组织管理员身份登录复用现有 `/api/admin/auth/login` 流(`AdminRoles.RESIDENT_ROLES` 新增 `org_admin`) ### 接口列表(13 个,全部 `@PostMapping`) | 路径 | 说明 | |---|---| | `/api/admin/org/list` | 组织列表(分页/筛选;org_admin 只返回自己组织) | | `/api/admin/org/create` | 创建组织(同步创建管理员账号;仅平台角色) | | `/api/admin/org/update` | 更新组织信息 | | `/api/admin/org/disable` | 停用/启用组织(仅平台角色) | | `/api/admin/org/detail` | 组织详情 | | `/api/admin/org/my-org` | 组织管理员获取自身组织(登录跳转目标) | | `/api/admin/org/user/search` | 按手机号/昵称搜索平台用户(添加成员下拉选人) | | `/api/admin/org/member/list` | 成员列表(含家庭数/订单数/消费额摘要) | | `/api/admin/org/member/add` | 添加成员(软删成员复活为 active 并更新类型) | | `/api/admin/org/member/remove` | 移除成员(软删 status=inactive) | | `/api/admin/org/member/change-type` | 变更成员类型(planner ↔ user,复活 inactive) | | `/api/admin/org/stats/overview` | 总览卡片(成员/规划师/家庭/订单/消费) | | `/api/admin/org/stats/families` | 家庭维度(活跃家庭/任务完成率/打卡率) | | `/api/admin/org/stats/active` | 活跃度趋势(任务完成/打卡/报告上传,按天) | | `/api/admin/org/stats/revenue` | 营收(订单金额/会员订阅/佣金,按天) | | `/api/admin/org/stats/trend` | 趋势图(活跃成员数/订单数/收入,按天) | **请求参数:** 成员/统计接口统一从 body 取 `orgId`;stats 可选 `startDate`/`endDate`(`yyyy-MM-dd`,默认近 30 天)。 **统计口径:** - 成员范围:`org_members` 中 `status='active'` 的 `user_id` 集合 - 家庭数:`families.creator_id IN (成员userId)` 去重计数 - 订单:`product_orders.buyer_id IN (成员userId) AND status IN ('PAID','COMPLETED')` - 消费:上述订单 `total_amount` 求和(单位分) - 打卡:`health_checkins.user_id` + `daily_checkin`(经 `family_members.user_id` 关联)+ `emotion_checkin` - 报告:`dan_report_uploads.uploader_user_id` - 佣金:`commission_records.referrer_id`(受益人)`commission_amount` 求和 - 订阅:`member_subscription_order.status='paid'` **前端入口:** cfc-web 新增 `/org` 路由四页面(List/Form/Detail/Dashboard);菜单「组织管理」挂在系统配置分组下。组织管理员登录后自动跳 `/org/:id/dashboard`,不展示组织列表页。 *文档更新:2026-10-22(组织管理功能上线)* ## 五、规划师培训认证(`/api/planner/*`, `/api/admin/planner-*`, `/api/xiaoe/*`) > 设计文档:`docs/superpowers/specs/2026-10-22-planner-learning-assessment-gate-design.md` > 核心链路:身份审核通过 → Guide.status=pending_cert + 按类别初始化 `planner_certifications` > → 线上课时(小鹅通 webhook/手动同步)+ 线下课时(activities 打卡桥接)→ 考核通过 → > 类别 `certified`(基础类别 planner 通过时同步翻 `Guide.status=active`)→ 接单闸门放行。 ### 5.1 规划师端(`/api/planner/*`) | 路径 | 说明 | |------|------| | `POST /api/planner/certification/my` | 我的全部类别认证状态(线上/线下课时拆分、考核分、证书号、是否可考) | | `POST /api/planner/courses` | 某类别线上课程列表(含小鹅通跳转/H5 链接;body: `categoryCode`) | | `POST /api/planner/training-activities` | 可报名/签到的培训活动列表(复用 activities,过滤映射表+规划师可见) | | `POST /api/planner/training-activities/check-in` | 扫码/手动签到(body: `qr_token` 或 `activityId`+`admin`)→ 桥接发放课时 | | `POST /api/planner/exam/start` | 开始某类别考核(前置校验课时达标;body: `categoryCode`) | | `POST /api/planner/exam/submit` | 提交判分(body: `attemptId`+答案,写入 exam_score/exam_passed) | | `POST /api/planner/exam/result` | 考核结果 | ### 5.2 管理端(`/api/admin/*`) | 路径 | 说明 | |------|------| | `POST /api/admin/planner-cert-categories/list` | 学习种类列表 | | `POST /api/admin/planner-cert-categories/create` | 创建学习种类(含 required_hours、is_base,首批种子:planner/nutritionist/assessor) | | `POST /api/admin/planner-cert-categories/update` | 更新学习种类 | | `POST /api/admin/planner-cert-categories/delete` | 删除学习种类 | | `POST /api/admin/planner-courses/list` | 线上课程列表 | | `POST /api/admin/planner-courses/create` | 创建线上课程(挂类别) | | `POST /api/admin/planner-courses/update` | 更新线上课程 | | `POST /api/admin/planner-courses/delete` | 删除线上课程 | | `POST /api/admin/planner-offline-course-map/list` | 培训活动↔类别映射列表 | | `POST /api/admin/planner-offline-course-map/create` | 创建映射(activities 建课后关联类别与计课时) | | `POST /api/admin/planner-offline-course-map/update` | 更新映射 | | `POST /api/admin/planner-offline-course-map/delete` | 删除映射 | | `POST /api/admin/activity-checkins/list` | 签到记录查询 | | `POST /api/admin/activity-checkins/add` | 手动补录签到(桥接触发课时入账) | | `POST /api/admin/activity-checkins/review` | 签到审核 | | `POST /api/admin/planner-exam/papers` | 试卷列表(按类别) | | `POST /api/admin/planner-exam/paper/create` | 创建试卷 | | `POST /api/admin/planner-exam/paper/update` | 更新试卷 | | `POST /api/admin/planner-exam/paper/delete` | 删除试卷 | | `POST /api/admin/planner-exam/questions` | 题库列表 | | `POST /api/admin/planner-exam/questions/save` | 保存题目(新建/更新) | | `POST /api/admin/planner-certifications/list` | 认证看板(按类别:课时进度、考核分、证书号、有效期) | | `POST /api/admin/planner-certifications/sync-hours` | 手动同步线上课时(小鹅通 webhook 兜底,body: `userId`+`categoryCode`+课时) | | `POST /api/admin/planner-certifications/sync-hours/batch` | 批量同步线上课时 | | `POST /api/admin/planner-certifications/init-by-user` | 按用户初始化各类别认证记录 | | `POST /api/admin/planner-certifications/backfill` | 存量补登(迁移401 幂等重跑) | ### 5.3 小鹅通回调(`/api/xiaoe/*`) | 路径 | 说明 | |------|------| | `POST /api/xiaoe/webhook` | 线上学习进度/课程完成事件(签名校验、幂等;课时入账 + 认证记录更新) | | `POST /api/xiaoe/webhook/test` | webhook 联调用测试入口 | ### 5.4 接单闸门(`PlannerCertificationGuard`) - 中央守卫 `assertCertified(userId, categoryCode[, baseRequired])`:查询该类别 `planner_certifications.status == certified`(且 `Guide.status == active`)方可放行,否则返回 `Result.error("规划师未完成【类别名】培训认证,暂不可提供该类服务")`(null=放行,非 null=拒绝)。 - 已注入闸门的服务入口(设计 §9 映射表): | 服务动作 | 入口 | 所需类别 | |----------|------|----------| | 上架/修改服务套餐 | `GuideManagementController` package/add、package/{packageId}/update | `planner` | | 家庭绑定(开始服务) | `BindController.accept` | `planner` | | 激活成长方案(下发任务) | `PlanReviewController.activate` | `planner` | | 咨询响应/接单 | `TeacherMessageController.reply` | `planner` | | 订单中心(列表/统计/详情) | `GuideOrderController` /list、/stats、/detail | `planner` | | 测评录入/解读 | `GuideAssessmentController` family/enable | `assessor` | - 待建设入口(当前代码不存在,预留):营养方案/饮食建议(`nutritionist`)。 --- ## 六、CF 值体系统一 · 配置治理接口(F-1 / W0.1) > 口径:`docs/superpowers/plans/2026-10-24-cf-value-unification-plan.md` §3.6.6 / §3.7 W0.1 > 本批只提供 **数据层 + 配置层** 接口,发放/扣减接口在 F-2/F-3 批新增。 ### 6.1 服务配置中心(`/api/admin/service-config`) 唯一配置中心(`service_config` 表):CF 通用运营参数(key 前缀 `cf.`,`product_key` 为空)+ product 侧配置(`product_key` 非空,key 形如 `product.{productKey}.{key}`,W0.2 由 `product_config` 并入)。保存后自动 `CfConfigCache.invalidate()`(本地缓存 TTL 60s)+ `cf.version` 自增。 | 路径 | 说明 | |------|------| | `POST /api/admin/service-config/list` | 配置列表,body 可选 `{"groupKey":"cf.rate","productKey":"gut_health"}`,不传返回全部(`productKey` 为产品维度筛选,W0.2 新增) | | `POST /api/admin/service-config/save` | 保存配置(upsert),body `{"configKey":"cf.rate.points_multiplier","configValue":"1.0","updatedBy":1}`;传 `productKey` 则写入 product 侧配置(自动加 `product.{productKey}.` 前缀并回填 `product_key`,W0.2 新增),可另传 `description` | | `POST /api/admin/service-config/invalidate` | 手动失效 CF 配置缓存(排障用) | 已登记配置项:`cf.rate.points_multiplier`、`cf.rate.repeat_rate_default`、`cf.commission.settle_days`、`cf.invite.valid_days`、`cf.price.default_factor`、`cf.stock.deduction_mode`、`cf.rate.share_read_reward_points`、`cf.commission.service_rate`、`cf.product.gut_health_price`、`cf.product.platform_points_share`、`cf.version`。 ### 6.2 CF 值获取规则(`/api/admin/cf-earn-rule`) 24 条规则(F02–F24,F03 拆 F03a/F03b)配置入口;每次保存/开关写 `cf_earn_rule_history` 并失效配置缓存。 | 路径 | 说明 | |------|------| | `POST /api/admin/cf-earn-rule/list` | 规则列表,body 可选 `{"category":"behavior","enabled":1}` | | `POST /api/admin/cf-earn-rule/save` | 新建/编辑(按 `code` 判重),body 含 `code/name/category/ruleType/amount/formula/paramsJson/dailyLimitPerMember/totalLimitPerMember/cooldownSeconds/dedupRefType/remark/changedBy/changeReason` | | `POST /api/admin/cf-earn-rule/toggle` | 启停,body `{"code":"F07","enabled":0,"changedBy":1,"changeReason":"..."}` | | `POST /api/admin/cf-earn-rule/history` | 变更历史,body 可选 `{"code":"F07","limit":100}` | > 读取封装:`ConfigService.getConfig(key)`(缓存 60s → DB → 兜底常量);product 侧配置读取:`ConfigService.getProductConfig(productKey, key)`(W0.2);规则读取:`CfEarnRuleService.getByCode(code)`。 ### 6.3 产品配置中心(`/api/admin/product-config`,**已废弃 410 Gone**) W0.2 双配置中心合并:`product_config` 已并入 `service_config`(唯一配置中心),product 侧配置通过 `product_key` 关联产品;本控制器写接口统一返回 410 Gone(HTTP 410 + body `code=410`),保留 30 天只读窗口后由运维删表。 | 路径 | 说明 | |------|------| | `POST /api/admin/product-config/list` | 【410 Gone】已废弃,改用 `POST /api/admin/service-config/list`(body 传 `productKey` 筛选) | | `POST /api/admin/product-config/save` | 【410 Gone】已废弃,改用 `POST /api/admin/service-config/save`(body 传 `productKey`) | 时间线:2026-10-24 起写接口下线(product_config 只读保留 30 天回滚用);2026-11-23 起确认无回滚需求后删表并移除本控制器。 ### 6.4 F-6 存量账本迁移(迁移417,无新增接口) 启动时由 `DatabaseInitializer` → `CfLegacyLedgerMigrationService` 一次性执行,无 REST API。迁移只读旧表、不回写旧表: | 来源 | 目标成员 | `cf_balance_log.ref_type` | `ref_id` | 口径 | |------|----------|---------------------------|----------|------| | `family_platform_balance` | 家庭 `families.creator_id` 对应的 `family_members` | `migration_family_pool` | `0` | `available + frozen` 合入新 `available`,`withdrawn`/`exchanged` 原样带入 | | `family_members.system_points > 0` | 当前成员 | `migration_system_points` | `family_members.id` | `system_points` 合入新 `available` | | `user_platform_balance` | 该用户本人对应的 `family_members` | `migration_user_wallet` | `user_platform_balance.id` | `available + frozen` 合入新 `available`,`withdrawn`/`exchanged` 原样带入 | 幂等键为 `(member_id, type='adjust', ref_type, ref_id)`(对齐 F-2 迁移416 升级后的唯一键 `uk_ref_type`);重复启动按三类 `ref_type` 逐笔跳过,余额与流水同事务提交。三套旧账本保留为只读历史,写接口 410 Gone 由 F-8 收口。 ### 6.5 F-4 分佣收口 + F03a/F03b 发放接入(迁移418 / 迁移420,无新增接口) > 口径:计划 §四 / §3.1 / §4.2 / §4.4。发放链路由 `CfCommissionService` 统一承载,无新增 REST 接口;`/api/commission/cf/rate` 返回值扩展两级档位字段(见 4.33)。 **迁移内容:** | 迁移 | 内容 | 幂等键 | |------|------|--------| | 418 | `cf_direct_sales_tier` / `cf_indirect_sales_tier` 各补 5 档种子(青铜/白银/黄金/铂金/钻石,缺行才补、不覆盖)+ `cf_earn_rule.dedup_ref_type` 对齐为规则编码(F03a→'F03a'、F03b→'F03b') | 按 `min_annual_ppoints` / `min_self_ppoints` 判重;UPDATE 带 `WHERE dedup_ref_type <> code` | | 420 | 新建 `ppoint_consumption_log`(滚动一年档位聚合数据源:消费支付成功落一行,`created_at >= now()-365d` 聚合)。注:迁移419 已被 `membership_levels.max_daily_analyze` 占用,本表顺延为 420 | `UNIQUE uk_order (order_type, order_id)` 同一订单重复触发只记一行 | **发放链路(`CfCommissionService.settle` / `settleReferrerOnly`):** ``` 本单 P 点基数(商品: 元×有效P点/100;非商品: 元×service_rate bps/10000) → 落 ppoint_consumption_log(幂等) → referral_tree 按 level 升序上溯,跳过同家庭(D5) → 第1个非同家庭引荐人 → getTeamAnnualPpoints(referrerId, 8) → matchDirectSalesTier → F03a → 第2个非同家庭引荐人 → getSelfAnnualPpoints(referrerId) → matchIndirectSalesTier → F03b → cfBalanceService.earnByRule(memberId, "F03a"/"F03b", orderId, {base_ppoints, rate_percent}) (公式 floor(base_ppoints*rate_percent/100),幂等键复用 F-2 uk_ref_type,只写 cf_balance + cf_balance_log) → F03a 且非幂等命中时 autoCreateGratitudeEffect(感恩记录) ``` - 数量与比例零硬编码:比例来自档位表,上限来自 `cf_earn_rule.params_json.max_rate_percent`,金额由公式计算。 - 与 F-3a 共存:`CfCommissionService` 同时承载两条链路 —— 当前消费人 F02(`CfEarnDispatchService.earnConsumeCf`,按实付金额,商品订单实付与分润基数分离)与推荐人 F03a/F03b(本节);两条链路都只写 `cf_balance` + `cf_balance_log`,`cf_transfer_record` 不再由本服务写入。 - 旧分佣写接口 `CommissionDistService.distribute` 已废弃:抛 `IllegalStateException`(消息含 "410 Gone",经 `GlobalExceptionHandler` 转 `Result.error(400, ...)`),无调用方;`commission_dist_log` 转只读历史表,管理端读取接口(`/api/admin/commission-dist/*`)保留不变。 - `cf_rate_tier` 表保留只读,仅供等级展示(`PromotionTierService` / 管理端档位配置)使用。 *文档更新:2026-10-24(CF 值体系统一 F-1 数据层 + W0.1 配置归口:8 张新表/迁移407-412 + service_config 与 cf_earn_rule 管理端接口);同日补登 W0.2 双配置中心合并(service_config 收编 product_config:迁移413 + product_key 维度 + product-config 接口 410 Gone + 配置中心统一入口);同日补登 F-6 存量迁移(迁移417 + 三套旧账本幂等迁入 cf_balance);2026-10-25 补登 F-4 分佣收口(迁移418/420 + 两级销售奖档位 + /rate 扩展字段 + CommissionDistService.distribute 410 Gone)* ### 6.6 C 端 CF 查询接口(F-7a,只读) > 口径:计划 §七 F-7a。个人 CF 值账本为成员维度(`family_members.id`),登录 `userId → memberId` > 解析与 `CfEarnDispatchService.resolveMemberId`(F-3a)口径一致:`user_id` 匹配 + 按 `id` 升序 > `LIMIT 1`;未找到对应家庭成员时返回 `Result.error(404, "未找到当前用户对应的家庭成员,...")`,不返回空数据。 > > 控制器:`CfBalanceController`,前缀 `/api/cf`(**GET,需登录**;注:本项目接口惯例为 POST,本组 > 只读接口按 F-7a 任务要求采用 GET,若团队统一要求 POST 可一行改回)。 | 路径 | 说明 | |---|---| | `GET /api/cf/balance` | 我的 CF 值快照。按登录用户解析 memberId 后读 `cf_balance`,返回 `CfBalanceView`:`memberId` / `available`(可用) / `frozen`(冻结) / `totalEarned`(累计获得) / `totalWithdrawn`(已提现) / `exchanged`(已兑换消费)。无 `cf_balance` 记录时返回全 0 | | `GET /api/cf/logs?page=1&size=20` | 我的 CF 流水分页(时间倒序),复用 `CfBalanceService.getLogs(memberId, page, size)`,输出转 `CfBalanceLogVO`:`id` / `type`(earn/spend/freeze/unfreeze/withdraw/adjust/skip) / `refType`(规则编码或迁移标识) / `amount`(带符号变动) / `balanceAfter`(变动后可用余额) / `remark` / `createdAt`(ISO 8601)。返回 MyBatis-Plus `Page` 结构(current/size/total/pages/records) | | `GET /api/cf/family-coupons` | 家庭共享优惠券列表(N3 口径)。复用 `CouponService.listMyCoupons(userId)`:按登录用户所属家庭查 `family_coupon` + `coupon` 联合数据(券家庭化全链路已切换至 `family_coupon`,`user_coupon` 为只读历史表),返回 `userCouponId` / `couponId` / `name`(券名) / `type` / `value`(面额,分) / `discountRate` / `minSpend` / `applicableTo` / `status`(AVAILABLE/USED/EXPIRED/DISABLED) / `validFrom` / `validUntil`(有效期) / `receivedAt` / `usedAt` / `orderId` / `productId`,家庭内成员共享可见 | - 实现走统一账本 `CfBalanceService` / `CouponService`,本控制器不直接写库、不提供任何写接口。 - 流水/余额响应结构单测:`cfc-backend/src/test/java/com/etotem/cfc/unit/CfBalanceControllerTest.java`(memberId 解析命中/未命中 + balance/logs 字段映射)。 ### 4.56 管理端-素材库(`/api/admin/file-record/*`) > 控制器:`FileRecordController`(`controller/admin/FileRecordController.java`),前缀 `/api/admin/file-record`。 > 服务层:`FileRecordService`;文件存储复用 `StorageService.storeFileRecord(MultipartFile, category)` > (`JdCloudStorageService` / `LocalDiskStorageService` 实现),统一写入 `file_record` 并推导 `file_type`。 > 权限:全部 `@PostMapping`,控制器内 `@RequestAttribute("role") == "admin"` 守卫(无 token 时为 `anonymous`,直接拒绝)。 > 数据表:`file_record`(唯一键 `uk_hash` 去重)、`file_tag`(`uk_name`)、`file_tag_rel`(`uk_file_tag`),均为既有表,无新增迁移。 **`file_type` 口径**(与 `util/FileTypeResolver.java` 一致): | file_type | 判定 | |-----------|------| | `image` | `content_type LIKE 'image/%'` | | `video` | `content_type LIKE 'video/%'` | | `audio` | `content_type LIKE 'audio/%'` | | `document` | `content_type LIKE 'text/%'` 或 PDF/Office/RTF/ODF 白名单 | | `other` | 其它(含 `application/octet-stream`、content_type 缺失) | > 写入侧(新数据):content_type 缺失或为 `application/octet-stream` 时按扩展名兜底;历史回填见 `docs/migrations/20261025_asset_library_file_type_backfill.sql`(严格按 content_type 分类,不用扩展名兜底)。 **接口清单:** | 路径 | 说明 | |------|------| | `POST /api/admin/file-record/list` | 分页检索。Body:`{ page, pageSize, category, fileType, search, dateFrom, dateTo, tagIds }`;`tagIds` 为数组或逗号分隔字符串,**前置过滤**(先按标签 JOIN 过滤再分页,保证 total 正确)。返回 `{ records, total, page, pageSize }`,records 元素含 `id/hash/url/fileSize/contentType/originalFilename/category/fileType/description/createdAt/updatedAt/tags` | | `POST /api/admin/file-record/upload` | 统一上传入库。multipart 字段:`file`(必填)、`category`(选填,默认 `general`)、`tagIds`(选填,数组或逗号分隔)。存储 → 写 `file_record`(SHA-256 去重,命中返回既有记录并补打本次标签)→ 关联标签。图片(contentType 以 image 开头)先过微信内容安全检测(`WechatServiceInterface.checkImage`,fail-open,与旧 `AdminArticleController#uploadImage` 保持一致)。返回素材记录 map | | `POST /api/admin/file-record/update` | 更新描述/标签。Body:`{ id, description?, tagIds? }`;`tagIds` 传空数组表示清空标签,不传表示不改动 | | `POST /api/admin/file-record/delete` | 删除素材。Body:`{ id }`;先删 OSS 文件(失败仅告警)再删记录及其标签关联 | | `POST /api/admin/file-record/tags` | 标签字典列表(按 sortOrder 排序) | | `POST /api/admin/file-record/tag/create` | 新建标签。Body:`{ name, color?, sortOrder? }`,name 唯一 | | `POST /api/admin/file-record/tag/update` | 更新标签。Body:`{ id, name?, color?, sortOrder? }` | | `POST /api/admin/file-record/tag/delete` | 删除标签。Body:`{ id }`(级联清理 `file_tag_rel`) | | `POST /api/admin/file-record/stats` | 统计。返回 `{ total, totalBytes, byFileType: [{file_type, cnt}], byCategory: [{category, cnt}] }` | **上传响应示例(`data` 为素材记录):** ```json { "id": 1024, "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "url": "https://bucket.endpoint/uploads/general/xxx.png", "fileSize": 102400, "contentType": "image/png", "originalFilename": "cover.png", "category": "articles", "fileType": "image", "description": null, "createdAt": "2026-10-25T12:00:00", "updatedAt": "2026-10-25T12:00:00", "tags": [] } ``` **前端配套:** `cfc-web/src/utils/asset.js`(API 封装 + `resolveAssetUrl`)、`cfc-web/src/components/AssetPicker.vue`(素材选择弹窗,image/video/document 三类)、`cfc-web/src/views/admin/AssetLibrary.vue`(素材库管理页,路由 `/admin/asset-library`)、`cfc-web/src/utils/editorAssetUpload.js`(wangEditor 上传配置公共封装,端点统一指向本组 upload)。 **上传点接入范围与跳过说明(`cfc-web/src` 全量 `el-upload` 排查结果):** | 页面 | 处理 | |------|------| | `ArticleEdit` / `ActivityEdit` / `ProductEdit` | 接入:富文本 `MENU_CONF.uploadImage/uploadVideo.server` 改指本组 upload(`fieldName=file` + Bearer),`customInsert` 统一 `resolveAssetUrl`;新增「从素材库选择图片/视频」按钮 | | `HomeBannerManage` / `ProductSkuEditor` / `GuidePackages` | 接入:封面上传改指本组 upload,并加「从素材库选择」 | | `KnowledgeDialog` | 接入:文档可从素材库选取(`assetType=document`),原有的本地知识文件上传通道保留不变 | | `Activities` / `ArticleManage` / `SupplierProductManage` | 跳过:仅列表展示页,无上传控件 | | `RecommendationRules` / `IndicatorMappings` | 跳过:无文件上传控件(后者为文本批量导入) | | `ProxyReportUpload` / `teacher/ProxyReport` / `ReportParserImport` / `ReportCollectorTrainer` / `ReportUnknownCluster` / `teacher/GrowthRecords` | 跳过:业务报告/JSON 解析包上传,走报告解析专用端点(`/api/admin/report-parser/*` 等),非 jdcloud 媒体资产,纳入素材库会污染 `file_record` 分类语义 | | `ReportTemplateManage` | 跳过:仅表单 tab,无图片字段 | > 旧端点 `POST /api/admin/articles/upload/image|video` **保留可用**(兼容既有调用方),但后台页面已统一改走素材库 upload,以便自动入库与打标签。 **存量回填:** `docs/migrations/20261025_asset_library_file_type_backfill.sql`(备份 → 按 content_type 回填 file_type → 修正列注释 → 校验),不占用 DatabaseInitializer 迁移编号(407-421 为 CF 体系保留)。 *文档更新:2026-10-25(后台素材库:/api/admin/file-record/* 上传入库 + 标签字典 + 统计;file_type 口径统一为 image/video/audio/document/other)* --- ## 4.49 家庭归因核心闭环(`/api/attribution/*` + `/api/survey/attribution/*`) > 阶段二(ATT-S2,B1–B4):「归因分析 → 条件问卷 → 自然语言调整 → 七章干预方案」。 > **E12 合规后置**:全流程由 `app.attribution.enabled`(默认 `false`)开关控制,仅测试/mock 数据环境开启; > 真实用户放行 = 阶段四 D1 完成日。未开启时返回 `Result.error(4001, …)`。 > **合规即服务**:红线 RL01–RL10 由规则层硬注入,AI 只产叙述/调整,不负责合规判定(§1.3)。 ### `POST /api/attribution/analyze` — ③ 归因分析(十节结构报告) - **Body**:`{ familyId?, memberIds[], gutReportIds[], danResultIds[], confirmSurveyRecordId?, planId?, mock?, mockFlags? }` (`mockFlags` 仅 E12 测试环境,可显式注入中间态键) - **Resp** `data`:`AttributionReportVO { id, familyId, status, reportJson, intermediateState, redlineHits, conclusionLocked, createdAt }` - `reportJson`:十节结构(一结论先行/二四人数据速览/三逐人归因/四家庭系统归因/五应对方案/六12周可检验目标/七教练提问序列/八问诊清单/九红线与转介/十一句话收尾) - `intermediateState`:含 `l.reuteriAbnormal/ironLow/hasDan/isFemaleTeen/hasDiabetes` 等键(B1 中间态) - `redlineHits`:RL01–RL08(阶段③④注入);RL09/RL10 属方案阶段⑤ - 状态流转:`ANALYZING`(插入)→ `READY`(回填报告后更新) - **前置**:常模缺失指标 → 「待补」,不猜值(`warnings` 携带说明) ### `POST /api/survey/attribution/confirm-questions` — ② 条件问卷路由 - **Body**:`{ familyId, memberId, attributionId, answeredSummary? }` - **Resp** `data`:`ConfirmQuestionVO { templateId, questions[], hasHighRisk, highRiskQuestionIds }` - `questions[]`:按 对象×年龄×结论 条件下发 `family_attribution` 模板子集(16 条中) - 对象级题目只下发给该对象;家庭级(四人/全家)恒发 - Q11–Q13 携带 `riskLevel=high`;命中 → `hasHighRisk=true`(E4 弱提示,不阻断) ### `POST /api/survey/submit` — ② 提交(B2 扩展:riskHits) - **Body**:`{ userId, memberId, templateId, answersJson, periodStart?, periodEnd?, riskHits? }` - `riskHits`(Q11/Q12/Q13 命中标记)并入 `answersJson._attributionRiskHits` 落库,供 B1 红线检测 - E4:高风险命中不阻断提交,仅标记「建议专业评估」 ### `POST /api/attribution/adjust` — ④ 自然语言调整 - **Body**:`{ attributionId, message, conversationId? }` - **Resp** `data`:`AdjustResult { attributionId, status, answer, fieldDeltas, intermediateState, redlineHits, redlineTerminate, blocking, referral, planInvalidated }` - AI 回答可携带 `[ATTR_DELTA]{"键":新值}` 结构化增量 → 回填 `intermediate_state`(七层 Socratic,PRD §C ④) - RL02 命中 → `redlineTerminate=true`、`blocking=false`(E4 弱提示不阻断)、`referral` 露出转介信息 + 家长确认 - 若已关联方案且 `status=GENERATED` → 作废为 `INVALIDATED`(⑤ 失效重生成) - AI 不可用 → 规则兜底话术,流程不阻断 ### `POST /api/attribution/confirm-adjust` — ④ 确认调整 - **Body**:`{ attributionId }` - **Resp** `data`:`AdjustResult { status=CONFIRMED, conclusionLocked=true }`(结论锁定,B4 前置条件) ### `POST /api/attribution/plan` — ⑤ 七章干预方案 - **Body**:`{ attributionId }` - **前置**:归因结论须 `CONFIRMED`(否则 `Result.error(4000, …)`) - **Resp** `data`:`FamilyPlanVO { planId, planJson, totalDays, status }` - `planJson`:`header` + 7 章(数据速览/主线产品/分人方案/家庭共享/12周节奏/红线与转介/附录) - 人范围:仅 妈妈/女儿/儿子(E6,爸爸链外);每动作带 `level`(P0/P1/daily/P2) + `reviewHook`(复盘钩子) - 儿童补剂动作仅写「医生定」,无剂量数字(RL03);产品品牌级(Enagic 还原水/姜黄、PHGG、菌株名)且带 RL05 合规口径 - 落库 `health_plans`,`plan_type=family_attribution_plan`,`status=GENERATED`(⑤) *文档更新:2026-10-28(家庭归因核心闭环 B1–B4:/api/attribution/analyze、/api/attribution/adjust、/api/attribution/confirm-adjust、/api/attribution/plan、/api/survey/attribution/confirm-questions;/api/survey/submit 扩展 riskHits)* --- ## 4.57 家庭指标聚合方式配置 + 珍珠图/章鱼图家庭汇集 > 五维能量页(身/智/富/行/心)三视角:① 个人看自己各维度;② 家庭管理员看家庭成员各维度;③ 看整个家庭各维度汇总。 > 家庭各维度汇总方式可配置:**SUM=合计 / AVG=平均 / COLLECT=汇集**(如行页面珍珠图、章鱼图)。 > 配置作用域 = **平台级全局一份**(`family_metric_agg_config` 表,按数据项一行一条,迁移433)。 > 默认五维 AVG 保持既有页面数值表现不变(向后兼容);SUM/COLLECT 由后台配置页调整即生效。 **数据表 `family_metric_agg_config`**: | 字段 | 说明 | |------|------| | `metric_key` (UNIQUE) | 数据项唯一标识:`energy.body`/`energy.mind`/`energy.wisdom`/`energy.action`/`energy.wealth`/`energy.overall`/`pearl.resources`/`octopus.effects` | | `metric_name` | 中文名 | | `dimension_code` | 所属维度:body/mind/wisdom/action/wealth/common | | `agg_type` | SUM=合计 / AVG=平均 / COLLECT=汇集 | | `value_type` | NUMBER(数值)/ COLLECTION(集合) | | `display_mode` | number/bar/radar/merge_graph | | `sort_order` / `status` / `remark` | 排序 / active|disabled / 备注 | **配置 CRUD**(`/api/admin/family-metric-agg/*`,供 cfc-web 管理端,`admin` 角色守卫): | 路径 | 说明 | |------|------| | `POST /api/admin/family-metric-agg/list` | 全量配置列表(配置项 < 50,无需分页)。返回 `[ { id, metricKey, metricName, dimensionCode, aggType, valueType, displayMode, sortOrder, status, remark } ]` | | `POST /api/admin/family-metric-agg/save` | 新增/更新(body 带 `id` 则更新,否则新增)。Body:`{ id?, metricKey, metricName, dimensionCode, aggType, valueType, displayMode?, sortOrder?, status?, remark? }` → `true` | | `POST /api/admin/family-metric-agg/delete` | Body `{ id }` → `true` | | `POST /api/admin/family-metric-agg/update-status` | Body `{ id, status }` → `true` | **家庭汇集数据**(供小程序行页面,全 POST): | 路径 | 说明 | |------|------| | `POST /api/pearl/family/resources` | 珍珠图家庭汇集。data:`{ members: [ { memberId, memberName, memberType, colorKey, items: [原珍珠资源项结构 + groupType/groupTypeName/ring] } ] }` | | `POST /api/octopus/effect/family/list` | 章鱼图家庭汇集。data:`{ members: [ { memberId, memberName, memberType, colorKey, effects: [原章鱼成效项结构] } ] }` | - `memberType`:`user`(家庭主账号,memberId=users.id)/ `member`(家庭成员,memberId=family_members.id) - `colorKey`:稳定色值键(0..n-1 索引,同一成员前后一致),供前端按成员着色 - 成员收集与 `EnergyService.doCalculateFamilyEnergy` 一致(家庭主账号 role=parent + `getActualChildren` 去重);个人数据查询复用既有逻辑(`ResourceService.getPearlResources` / `KeyPersonService.listEffects`),不重写业务规则 **五维聚合改造(`EnergyService.doCalculateFamilyEnergy` 第 4 步)**:按 `metric_key`(`energy.body`…)查配置取 `agg_type`: - AVG → 既有平均逻辑(默认,行为不变) - SUM → 合计(sum,不再除以 count) - COLLECT → 数值维度不适用,回落 AVG + 日志记录 - 读不到配置 → 回落 AVG(保证兼容) *文档更新:2026-10-31(家庭数据汇总方式配置(后端):family_metric_agg_config 表 + 配置 CRUD 4 接口 + 珍珠图/章鱼图家庭汇集 2 接口 + 五维聚合读配置)* --- ## 4.58 供应商团队邀请(`/api/admin/supply-team/*`)—— T38 > **需求来源**:廖总 2026-11-02「在我的团队加一个邀请功能,邀请其他用户加入团队」。规格:`docs/superpowers/specs/2026-11-01-team-system-design.md` §3.5 / §4.2 / §4.3(任务 T38)。 > > **语义**:团队成员 = **邀请加入**(与 `referral_tree` 推广返佣是两套独立模型,不在此范围)。凭邀请码加入者成为**团队成员**(非客户),且以邀请人为**上级**(`supply_hierarchy.parent_id=邀请人`,`level=父级level+1`)→ 成员子树 = 该成员自己的团队,与 §0.5「唯一负责人整体结算、平台不拆分」口径一致。 > > **数据模型**:`supply_system_member` 扩列 `invite_code VARCHAR(50)`(个人专属邀请码,迁移444 幂等 `ensureColumn` + 唯一索引 `uk_supply_member_invite_code`;schema.sql 已同步)。 ### 接口列表(3 个,全部 `@PostMapping`) | 路径 | 说明 | |------|------| | `POST /api/admin/supply-team/invite-code` | 生成/获取**我的**邀请码(幂等)。body:`{ systemId, refresh? }`;`refresh=true` 重置旧码(旧码立即失效)。返回 `{ inviteCode, systemId, teamName, shareText }`。**越权防护**:须为该体系的 admin/active 成员 | | `POST /api/admin/supply-team/invite/info` | 邀请码信息(**确认页**展示)。body:`{ inviteCode }` → `{ systemId, teamName, inviterId, inviterNickname, inviterAvatar, memberCount, active }`。仅展示团队名/邀请人昵称头像/成员数,**不泄露其他隐私** | | `POST /api/admin/supply-team/invite/join` | 凭邀请码**加入团队**(挂邀请人为上级)。body:`{ inviteCode }` → 加入后的团队/邀请人信息(同 invite/info 结构) | ### 边界规则 - **同一 `(systemId, userId)` 仅加入一次**:已 active 加入 → 报错「您已在该团队中,无需重复加入」;被移除(`status=inactive`)→ **可再次申请**(自动恢复 active)。 - **自助加入即生效**(默认口径),团队负责人可随时移除;`role=member` **不可自助变更**(沿用 `member/change-role` 管理员权限)。 - **邀请码永久有效 + 唯一**(唯一索引兜底);`refresh` 可重置为新码。 - **邀请不与资金/佣金直接挂钩**:结算走唯一负责人口径(§0.5),平台不拆分团队内部分配。 - 团队 `status != active` 时不可生成邀请码、不可加入。 ### 前端 - `cfc-frontend/pages/vendor/team-system/team-system.vue`:团队成员区新增「邀请成员」按钮 → 弹窗(邀请码 + 复制 + 刷新重置;小程序码可后置)。 - `cfc-frontend/pages/vendor/team-invite/team-invite.vue`(新增,已注册 `pages.json`):受邀确认页——展示邀请人/团队 → 点「加入团队」→ 成功态 → 跳转「我的团队」;支持 `?code=XXX` 带参进入,也可手动输入邀请码。 - `cfc-frontend/utils/api.js`:`getVendorInviteCode` / `getVendorInviteInfo` / `joinVendorTeamByInvite`(各 URL 唯一规范入口)。 *文档更新:2026-11-02(T38 团队邀请功能:supply_system_member 扩 invite_code + 邀请码生成/信息/凭码加入 3 接口 + 前端邀请入口与受邀确认页)*