# 架构与产品评估报告

> **本体对齐**：本报告的 P0 必补项与 [`ontology/`](./ontology/) 的规则条目对齐（如 P0-1→`rule:BR-AUTH-001`、P0-2→`rule:BR-RPT-006`/`rule:BR-LEAD-002`、P0-3→`rule:BR-RPT-002`、P0-4→`rule:BR-RPT-003`）。冲突以本体为准。

日期：2026-07-05

评估范围：
- 文档：`docs/产品设计文档.md`、`docs/P0-研发落地清单.md`、`docs/本期主线-下一步产品优化点.md`、报告成片、宠主预约、公众号协同、品牌与 UI 规范等。
- 后端：`backend/src/main/java/com/petstore` 下的预约、报告、成片、留资、宠物、用户、文件上传、配置与启动逻辑。
- 前端：`frontend/src` 下的首页、预约创建、报告填写、公开报告页、报告分享弹层、留资卡片、线索页、我的页、API 与工具函数。

方法：
- 架构师 agent 从边界、状态机、API 契约、数据模型、隐私安全、发布运维、测试可观测性做只读评估。
- 产品 agent 从定位、角色、主链路、报告分享、宠主预约、留资转化、文案品牌、验收标准做只读评估。
- 主线程补充核对关键代码路径，并执行基础验证命令。

## 总结

当前系统已经有一条可运行的主线：门店预约、开始服务、报告填写、报告发送、公开报告页、成片生成、宠主留资、回访池这些关键环节都已出现实现。真正阻碍进入稳定验收的，不是主流程缺失，而是四类风险仍未收口：

1. 身份与数据边界仍主要依赖前端传参，后端缺统一鉴权上下文。
2. 产品文档与代码状态不同步，部分 P0 已实现但文档仍写待改，部分文档承诺代码还未闭环。
3. 报告、线索、公开 token、上传与配置存在隐私和发布风险。
4. 自动化测试、迁移脚本、发布门禁和埋点统计基线不足，导致回归和上线依赖人工经验。

建议先按 P0 修“边界、隐私、报告不变量、产品口径、验证基线”，再做 P1 的增长与可观测性增强。

## 健康度评估

| 维度 | 结论 | 主要依据 |
| --- | --- | --- |
| 产品定位 | 基本健康但文档滞后 | B 端门店 SaaS + C 端报告/预约/留资闭环已形成；但 `产品设计文档.md` 仍把宠主查看报告写作 v2，与 P0 清单和现有代码冲突。 |
| 主流程 | 中等偏好 | 预约、状态 Tab、开始服务、报告提交、发送弹层、报告页、短片三态都有实现。 |
| 状态机 | 较好但缺测试 | `AppointmentService` 已集中处理 `new -> doing -> done`、取消规则和非法迁移，但文档还保留旧口径，自动化测试缺失。 |
| API 边界 | 中等偏弱 | 前端 API 已集中，但后端多处仍从请求参数读取 `userId/storeId/role` 控制范围。 |
| 数据模型 | 中等偏弱 | `Report`、`ReportLead` 字段逐步补齐，但“一约一报告”、报告 token 生命周期、成片任务队列和迁移脚本仍不完整。 |
| 隐私安全 | 弱 | 公开报告回包和线索接口暴露面偏大；登录、CORS、配置密钥、上传路径需要生产化。 |
| 发布运维 | 弱 | FFmpeg、上传大小、H5 public origin、微信合法域名、生产 profile、迁移步骤缺一份 RC 门禁清单。 |
| 测试与可观测 | 弱 | 后端 `mvn test` 可执行但无测试用例；前端缺依赖和可执行测试；报告打开埋点仍偏日志化。 |

## 当前可以保留的基础

1. 前端角色分流和主流程页面已经成型，`Home.vue`、`Report.vue`、`ReportShareModal.vue`、`reportView.vue` 能串起核心体验。
2. 预约状态机已经从“直接赋值”收口到服务层校验，文档应更新为“待补测试和验收证据”，不是“待重写”。
3. 报告成片字段契约和前后端字段名基本一致，`highlightStatus`、`highlightVideoUrl`、`highlightFailReason` 等字段具备继续扩展的基础。
4. 留资链路已有 token、手机号、同报告同手机号去重、同意状态等基础，不需要推倒重做。
5. P0 清单本身结构可用，适合继续作为验收总入口；问题是状态需要重新冻结。

## P0 必补项

| 编号 | 问题 | Owner | 涉及路径 | 建议 | 验收口径 |
| --- | --- | --- | --- | --- | --- |
| P0-1 | 后端身份与店铺边界未收口 | System Architect、Backend Core、Report Media Backend | `AppointmentController`、`ReportController`、`ReportLeadController`、`PetController`、`StoreController`、`UserController` | 建立统一登录态和角色上下文；所有 boss/staff/customer 接口从上下文派生 `userId/storeId/role`，不再信任请求参数；公开报告 token 接口单独白名单。 | A 店用户访问 B 店预约、报告列表、线索接口返回 403；宠主不能用任意 `userId` 创建/查询他人宠物和预约。 |
| P0-2 | 公开报告与线索隐私暴露面偏大 | System Architect、Product Design、Report Media Backend、Customer Experience FE | `ReportController.get`、`ReportLeadController.leads`、`Leads.vue`、`reportView.vue` | 公开报告回包只返回页面必要字段；线索列表默认不返回完整 openid/unionid，手机号按角色决定展示/脱敏；日志只记录不可逆 token hash。 | `GET /api/report/get?token=` 不含内部 `userId/storeId/reportToken`；员工态不显示完整 openid/unionid；日志不可反推出 token。 |
| P0-3 | 报告“一约一份”和提交不变量缺数据库与服务层兜底 | System Architect、Report Media Backend、Data Model | `ReportService.create`、`Report.java`、`ReportController.create`、`Report.vue` | 增加 `appointment_id` 唯一约束；服务层拒绝同预约重复提交；明确是否允许无预约报告，若不允许则前后端同时拦截。 | 同一 `appointmentId` 第二次提交返回 409 或业务码；非 `doing` 预约不能创建报告；无预约提交符合产品确认后的唯一规则。 |
| P0-4 | 报告前后照片必填口径未落实 | Product Design、Store Miniapp FE、Report Media Backend | `产品设计文档.md`、`Report.vue`、报告创建接口 | 文档、前端、后端统一：服务前、服务后至少各 1 张，过程图可选。 | 0 张 before 或 0 张 after 时提交失败，提示“请至少上传 1 张服务前/服务后照片”。 |
| P0-5 | 产品文档与代码状态冲突 | Product Design、PM Assistant、Docs | `产品设计文档.md`、`P0-研发落地清单.md`、`宠主端预约体验优化-产品说明.md`、`产品全方位优化说明-v1.md` | 冻结本期范围：宠主预约/公开报告/留资/基础埋点属于本期；历史报告汇总、复杂会员储值、完整 CRM 放到 P1/P2；删除“到店不排队”等无容量模型承诺。 | `rg "到店不排队|会员储值 \\| 路线图 P0|宠主通过微信查看服务报告（v2）" docs` 不再命中冲突口径。 |
| P0-6 | 宠主预约登录与草稿策略未闭环 | Product Design、Customer Experience FE、Backend Core | `P0-研发落地清单.md`、`CustAppointmentCreate.vue`、`frontend/README.md` | 二选一冻结策略：进入预约先登录，或提交时登录并恢复 guest 草稿。若选择提交时登录，草稿 key 不能依赖 `userInfo.id`。 | 未登录填写后提交不会产生 `userId: undefined`；登录返回后门店、宠物、服务、时段、备注恢复。 |
| P0-7 | 报告页角色判断会把已登录 customer 当员工态 | Product Design、Customer Experience FE | `reportView.vue`、`auth/session` 工具 | 把 `isStaff` 从“是否登录”改为“角色是 boss/staff”；customer 登录态仍看到留资/预约 CTA。 | customer 登录态打开报告页显示 `ReminderCard` 和“我也要预约”；boss/staff 才显示内部操作。 |
| P0-8 | 配置、密钥与生产 profile 风险 | System Architect、Backend Ops、Frontend Release Ops | `backend/src/main/resources/application.yml`、`application-example.yml`、`.env.*` | 移除真实环境配置和明文敏感项，改为环境变量；已暴露凭据按安全流程轮换；生产 profile 禁用 `ddl-auto:update`、`show-sql`、万能验证码和宽 CORS。 | 仓库不含真实 DB/微信密钥；生产启动只读环境变量；安全扫描不再命中敏感配置。 |
| P0-9 | 上传接口缺路径与类型保护 | System Architect、Backend Core | `FileController`、上传反代配置 | 对上传文件做扩展名、MIME、大小、路径归一化校验；下载/访问路径拒绝 `..`、绝对路径和非媒体类型。 | 构造 `/api/upload/image/../...`、伪装扩展名、超大文件均被拒绝；正常图片/视频仍可上传访问。 |
| P0-10 | 验证基线不足 | Core Flow QA、Report Share QA、System Architect | `backend/src/test`、前端 smoke、CI | 补最小自动化：预约状态机、报告创建、留资去重、公开报告字段、跨店访问拒绝、前端报告打开/分享/预约深链 smoke。 | `mvn test` 至少跑上述服务/控制器测试；前端构建和 smoke 能在本地或 CI 复现。 |

## P1 补强项

| 编号 | 问题 | Owner | 建议 | 验收口径 |
| --- | --- | --- | --- | --- |
| P1-1 | `storeId` 全入口不完整 | Customer Experience FE、Product Design | 支持普通 query、小程序码 `scene`、报告页“我也要预约”、公众号菜单、分享 path/query；写清参数协议。 | 三种入口均可预填门店，并允许用户更换门店。 |
| P1-2 | 留资主档规则待定 | Product Design、Report Media Backend | 明确手机号、openid、unionid 的主键、合并、冲突、解绑、展示和权限策略。 | 同手机号重复提交合并；不同微信同手机号有明确处理；Leads 页面展示符合权限。 |
| P1-3 | 成片任务缺成本与队列治理 | Report Media Backend、Backend Ops | 增加重试次数、频控、任务表或可观测队列、失败分类聚合、FFmpeg 健康检查。 | 同一报告短时间重复生成被限制；失败原因可统计；RC 可检测 FFmpeg/ffprobe。 |
| P1-4 | 埋点从日志升级为可统计事件 | Report Share QA、Progress Digest、Backend Ops | 定义 `report_open`、`report_reopen`、`highlight_success`、`highlight_failed`、`video_play`、`video_save`、`lead_submit`。 | 老板端或日报能看到打开率、成片成功率、留资转化。 |
| P1-5 | 启动期 DDL/修复逻辑不可审计 | System Architect、Data Model、Backend Ops | 把 `PetstoreApplication` 中的临时 DDL/历史 UPDATE 移入迁移脚本，建立版本化迁移。 | 新环境和老环境都通过迁移脚本升级；应用启动不再改表结构。 |
| P1-6 | 共享空态和品牌细节不统一 | Product Design、Customer Experience FE、Store Miniapp FE | `AppPageState` 默认改用 `AppIcon` 或显式 icon prop；清理主视觉 emoji；统一品牌词和按钮文案。 | 主流程空态不出现 emoji 主视觉；品牌名、Slogan、主色使用规范一致。 |
| P1-7 | 公众号配置不是代码能证明的 P0 | PM Assistant、Frontend Release Ops、Product Design | 将公众号菜单、欢迎语、自动回复列为运营配置验收项，和代码页面存在性分开。 | 公众号后台截图/配置单可证明菜单可跳小程序，自动回复路径有效。 |

## P2 方向库

| 编号 | 方向 | 说明 |
| --- | --- | --- |
| P2-1 | 报告 token 作废、重发、有效期 | 支持老板重新生成分享链接、旧链接失效、异常频次提醒。 |
| P2-2 | 宠主历史报告汇总 | 在本期公开报告页稳定后，再做宠主账号内历史记录。 |
| P2-3 | 会员、储值、套餐 | 不进入当前 P0；应等待预约/报告/留资闭环和数据口径稳定后再设计。 |
| P2-4 | 公众号模板消息和小程序订阅去重 | 避免报告通知、成片通知、小程序订阅消息重复触达。 |
| P2-5 | 经营分析看板 | 基于 P1 埋点和订单数据，做报告打开、预约转化、回访转化、服务复购。 |

## 文档修订清单

| 文档 | 当前问题 | 建议改法 |
| --- | --- | --- |
| `产品设计文档.md` | 写“宠主查看服务报告 v2”；预约时间写“无限制，随便选”；取消单展示和状态矩阵与代码/P0 不一致；数据模型缺新字段。 | 改为本期包含宠主预约、公开报告、留资；更新预约时间为可预约窗口和占用校验；状态矩阵对齐代码；补 `ReportLead`、成片字段、预约窗口等模型。 |
| `P0-研发落地清单.md` | 部分“当前需改”已过时，如状态机、报告分享弹层、我的页分组；部分缺口还未标清 owner。 | 重新标记 `已实现 / 部分实现 / 未实现 / 待测试`，并把每项绑定 owner 和验收命令。 |
| `宠主端预约体验优化-产品说明.md` | 仍有“到店不排队”示例；旧入口描述和当前主 CTA 不一致；登录策略未冻结。 | 删除容量承诺，统一为“在线选时段，到店更省心”；写清登录策略和深链协议。 |
| `产品全方位优化说明-v1.md` | 会员储值被写进 P0 路线图，与 common context 和 P0 清单冲突。 | 降级到 P1/P2 方向库，当前只保留预约/报告/留资闭环。 |
| `本期主线-下一步产品优化点.md` | 指标建议好，但缺事件 schema 和数据来源。 | 增加埋点事件、字段、统计窗口、owner、看板或日报落地方式。 |
| `洗美报告短视频成片方案.md` | 成片方案大体吻合代码，但重试频控、队列状态、成本指标没有落到验收。 | 增加成片任务状态机、失败重试次数、频控和 RC 检查。 |
| `公众号协同与全链路触达-产品说明.md` | P0 中包含运营后台配置，代码无法自动证明。 | 拆成“代码可验收”和“运营配置可验收”两栏。 |
| `backend/README.md`、`frontend/README.md` | 发布依赖、环境变量、微信合法域名、FFmpeg、上传大小、H5 origin 不完整。 | 增加本地开发、RC、生产上线三段式运行手册。 |

## 代码修订清单

### 后端

1. 建立统一鉴权中间层或拦截器，生成 `CurrentUserContext`：
   - `userId`
   - `role`
   - `storeId`
   - `isCustomer`
   - `isStaff`
   - `isBoss`

2. 控制器改造原则：
   - 管理接口不接受客户端传入的身份字段作为权限依据。
   - 查询列表时由上下文派生作用域。
   - 公开报告接口只允许 token 和必要参数。
   - 线索、宠物、报告、预约接口都做跨店/跨用户校验。

3. 数据约束：
   - `report.appointment_id` 加唯一约束。
   - `report_lead(report_id, phone)` 已有方向，但需迁移脚本确认。
   - 报告 token 增加状态、作废时间或过期策略的预留字段。
   - 成片任务若继续异步化，建议从 report 字段扩展到任务表或任务日志。

4. 配置与发布：
   - 移除仓库内真实环境敏感配置，使用环境变量和 profile。
   - 关闭生产 `ddl-auto:update` 和 SQL 明文输出。
   - 收敛 CORS 白名单。
   - 上传路径做归一化和类型校验。

### 前端

1. `reportView.vue`：
   - `isStaff` 使用角色判断，不使用 `isLoggedIn()`。
   - customer 登录态仍显示留资/预约入口。
   - 公开报告页不要依赖后端回包里的内部 token 字段。

2. `CustAppointmentCreate.vue`：
   - 支持 `scene` 解码。
   - 冻结登录策略并实现 guest 草稿或进入页登录。
   - 提交前确保 `userInfo.id` 一定存在或后端可识别匿名转登录上下文。

3. `Report.vue`：
   - 前后照片必填。
   - 与后端一致处理重复报告、非 `doing` 预约和无预约报告。

4. `Leads.vue`：
   - 默认隐藏或脱敏 openid/unionid。
   - 手机号展示按角色和操作场景收敛。

5. `ReportShareModal.vue`、`reportPublicUrl.js`：
   - 把 `VITE_REPORT_PUBLIC_ORIGIN` 写入发布手册和 RC 检查。
   - 链接、二维码、话术、封面规则做 smoke。

6. 共享 UI：
   - `AppPageState` 用品牌 icon 作为默认主视觉。
   - 清理主流程空态中的 emoji 主视觉。

## 建议实施顺序

1. 第 1 批：修文档口径、报告页角色判断、照片必填、`storeId` 深链协议、公开报告字段裁剪。
2. 第 2 批：统一鉴权上下文、跨店/跨用户校验、线索脱敏、报告唯一约束。
3. 第 3 批：迁移脚本、生产配置、上传安全、FFmpeg/上传/微信域名 RC 门禁。
4. 第 4 批：后端最小测试、前端构建与 smoke、埋点事件入库或可统计日志。
5. 第 5 批：token 作废、历史报告、会员储值、经营看板等 P2 方向。

## 最小验收集

### 后端

| 场景 | 期望 |
| --- | --- |
| `new -> doing` | 成功 |
| `doing -> done` | 成功 |
| `done -> doing` | 失败并返回明确业务码 |
| `done -> cancel` | 失败并返回明确业务码 |
| 重复 `startService` | 第二次失败 |
| 非本店用户查预约/线索/报告列表 | 403 |
| 公开 token 查报告 | 只返回公开页必要字段 |
| 同一预约重复创建报告 | 第二次失败 |
| 报告前图或后图为空 | 创建失败 |
| 同报告同手机号重复留资 | 幂等更新，不产生重复记录 |
| 上传路径带 `..` | 拒绝 |

### 前端

| 场景 | 期望 |
| --- | --- |
| customer 首页 | 只有一个主预约入口，空态不出现第二个主按钮 |
| 预约页普通 `storeId` query | 预填门店 |
| 预约页小程序码 `scene` | 预填门店 |
| 未登录填写预约 | 按冻结策略登录并恢复草稿，或进入前即登录 |
| customer 登录态打开报告页 | 仍显示留资/预约 CTA |
| boss/staff 打开报告页 | 显示内部分享/保存操作 |
| 报告提交无前图或后图 | 阻止提交并提示 |
| 报告提交成功 | 链接、二维码、两条话术同屏 |
| 成片处理中/失败/完成 | 三态文案和按钮正确 |
| 线索页 | openid/unionid 不默认明文展示 |

### 发布 RC

| 检查 | 期望 |
| --- | --- |
| `APP_BASE_URL` | 后端可生成可访问媒体 URL |
| `VITE_REPORT_PUBLIC_ORIGIN` | 报告链接和二维码非空且可访问 |
| `ffmpeg -version`、`ffprobe -version` | 服务端可执行 |
| 上传 50MB 视频 | 不被 Nginx 或后端误拦截 |
| 微信 request/download 合法域名 | 小程序可请求 API 并下载短片 |
| 生产配置扫描 | 无真实 DB/微信密钥、无 `ddl-auto:update`、无宽 CORS |

## 本次验证结果

| 命令 | 结果 | 说明 |
| --- | --- | --- |
| `mvn test`（`backend`） | 成功 | Maven 构建通过，但输出 `No tests to run`，不能证明业务逻辑有自动化覆盖。 |
| `npm --prefix frontend run build:h5` | 失败 | `frontend/node_modules` 不存在，`uni: command not found`；这是依赖/工具链缺失，不是源码编译通过或失败的业务结论。 |
| `git -C docs diff --check` | 成功 | 文档变更无空白检查错误。 |

## 结论

这套文档和代码的方向是对的：先做门店服务闭环，再把报告页作为宠主触达与复购入口。但现在不适合继续叠会员、储值或更复杂增长功能。下一轮应把 P0 的身份边界、报告不变量、隐私脱敏、文档口径和测试基线补齐；补齐后再推进埋点、公众号、经营看板和会员能力，风险会低很多。
