docs: M3.5 体验补齐——任务拆解与第一批客户端修复报告入档
CI / docs-build (push) Failing after 1s

- 01 任务拆解:用户实测 6 项反馈的分类与处置;**数据模型审计推翻迁移预估**
  (nickname/avatar_asset_id 列 V1/V3 早已存在、purpose 白名单是配置项)→ 本批零迁移
- 02 第一批已交付:中文本地化 + 日期录入收口 + 花费卡月份(flutter 502→526)
- 6 项待拍板(首页 demo 裁剪范围、获赞端点形态、头像写权限档等)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-11 09:27:17 +08:00
parent 1bcfe444c6
commit 95976ae2c3
3 changed files with 434 additions and 0 deletions
@@ -0,0 +1,142 @@
# 01 M3.5 任务拆解:体验补齐
**迭代定位**:M3 收官(v0.3.0 发布)后,用户桌面实测反馈 6 项问题的补齐迭代。规模远小于 M1~M3,不做 8 角色开工分析。
**执行人**:主会话(PM agent 两次卡死未落盘,实情由主会话独立审计取得)
**日期**2026-09-10
---
## 0. 缘起与范围
用户 2026-09-10 在 Linux 桌面实测 M3 成果,反馈 6 项问题。分类后:
| 用户反馈 | 定性 | 归属 |
| --- | --- | --- |
| ① 日历英文 | 缺陷(从未配本地化) | **第一批已修**M3.5-01 |
| ② 月份只能 `< >` 切 | 可用性缺陷,已实际致误录 | **第一批已修**M3.5-02 |
| ⑤ 本月花费 ¥0 | **不是 bug**:记录在 2026-04-09、当天 2026-09-109 月确实为 0;根因是 ② | **第一批已修** UI 可自查性(M3.5-03 |
| ③ 宠物无头像上传 | ADR-010 剪出项,M3 媒体链路已就位 | 本批 |
| ④ 资料页无法改昵称/头像、显示 demo | 代码层缺口 | 本批 |
| ⑥ 资料页关注/获赞/作品是假数据 | 同 ④(同一 demo 页) | 本批 |
| (未提)首页顶部 demo | 混合性质,须裁剪 | 本批部分 |
**本批范围一句话**:把「用户资料(昵称+头像)与宠物头像」从 demo 打通为真实读写,资料页数据真实化,并裁剪首页 demo 中属本迭代的部分。
---
## 1. 数据模型实情审计(**推翻了原先的迁移预估**)
开工前一度以为需要 Flyway V6 加列。**逐一核实原始 SQL 与代码后确认:所需列全部早已存在,本批零迁移。**
| 需求 | 原预估 | **实情(已核实)** |
| --- | --- | --- |
| 用户昵称 | `identity.users` 无 nickname 列,需加 | **V1 第 63 行就有** `nickname varchar(32)`,含 `ck_users_nickname`btrim + 1~32 长度);`UserRepository.insertUser(id, username, nickname, phone)` 注册时已在写 |
| 用户头像 | 需加列 | **V1 第 67 行就有** `avatar_asset_id uuid`,含 `fk_users_avatar_asset`(→`media.assets` ON DELETE SET NULL)与索引 `ix_users_avatar_asset` |
| 宠物头像 | 需加列 | **V3 第 64 行就有** `avatar_asset_id uuid REFERENCES media.assets(id) ON DELETE SET NULL`,含索引 `ix_pets_avatar`。但 **pet 模块代码零处读写它**grep 无命中) |
| 获赞总数 | 需加冗余列 | `community.posts` 已有 `like_count/comment_count/bookmark_count` 冗余列(M3 写侧同事务维护),`SUM(like_count)` 即可 |
| 头像 media 用途 | 需加枚举/约束迁移 | `media.assets.purpose``varchar(32) NOT NULL` **无 CHECK 约束**;白名单在 **配置项** `MediaProperties.allowedPurposes = List.of("post_image")` —— 加新用途只改配置 + 契约枚举 |
**已就位可直接复用的能力**
- `/internal/users/profiles` 已返回 `PublicProfileResponse(userId, nickname, avatarAssetId)`**nickname→username 回退在 SQL 层完成**M3 T3-05 交付)。即 Feed 作者名一旦用户设了昵称即自动生效,无需改社区侧。
- media 两步上传(user :8082+ 客户端 `MediaUploader` 六态编排(M3 T3-03/T3-13)。
- `MediaAssetGateway` 的 asset 校验先例(ready + 属本人,M3 T3-04)。
- `GET /api/v1/me/posts`(含草稿)、`GET /api/v1/me/bookmarks``GET /api/v1/users/{id}/follow-stats` 均已实现。
**真实缺口只在代码层**`MeResponse` 只有 `(userId, username, phone, createdAt)` 缺昵称/头像;**无任何写接口**(无 `PATCH /me`);pets 不读写头像列;获赞总数无端点。
---
## 2. 工单拆解
编号自 **T3.5-04** 起(01~03 已由第一批客户端修复占用)。
### 第一波:后端与契约
#### T3.5-04 用户资料读写(user 模块)
- **仓库**patbond-apipatbond-user
- **描述**`GET /api/v1/me` 响应补 `nickname``avatarUrl`(预签名 GET,沿用 M3 私有桶签名读口径,URL 会过期不得持久化);新增 `PATCH /api/v1/me` 支持改 `nickname``avatarAssetId`(置 null 即清除头像)。校验:nickname 与 `ck_users_nickname` 对齐(btrim、1~32);avatarAssetId 必须 `status='ready'`、属当前用户、`purpose='user_avatar'`(复用 MediaAssetGateway 同类校验语义,错误码沿用 40405/42203)。`MediaProperties.allowedPurposes``user_avatar`
- **验收**:设昵称后 `/me``/internal/users/profiles` 双端一致;清空昵称回退 username(回退逻辑已在 SQL 层,勿重复实现);非 ready/非本人/错 purpose 的 asset 被拒;六类路径覆盖。
- **依赖**:无。**规模**M
#### T3.5-05 宠物头像读写(pet 模块)
- **仓库**patbond-apipatbond-pet
- **描述**`PATCH /api/v1/pets/{petId}` 支持 `avatarAssetId`(含置 null);宠物详情/列表/FeedCard 无关响应补 `avatarUrl`(预签名 GET,community 侧本地现签先例见 M3 T3-04)。asset 校验复用 `MediaAssetGateway`ready + 属本人)+ `purpose='pet_avatar'`。权限沿用 `PetAccessService`:改头像属 **WRITE 档**owner+caregiver)还是 **MANAGE 档**(仅 owner)——见待拍板 D3.5-3。`allowedPurposes``pet_avatar`
- **验收**:owner 设头像后详情返回可访问 URLviewer 改被拒;version 乐观锁沿用 40902;非法 asset 被拒。
- **依赖**:无(与 T3.5-04 可并行,但同仓需串行提交)。**规模**:S~M
#### T3.5-06 获赞总数聚合(community 模块)
- **仓库**patbond-apipatbond-community
- **描述**:提供当前用户的社区统计:获赞总数(`SUM(like_count)` over 本人未删帖)、作品数(已发布帖数)。端点形态见待拍板 D3.5-2(新增 `GET /api/v1/me/community-stats` vs 扩展既有 follow-stats)。**不引入新冗余列**——写侧维护成本高于读侧聚合收益,且量级远未到瓶颈。
- **验收**:草稿/软删帖不计入;空数据返回 0 而非 null;口径写入契约描述。
- **依赖**:无。**规模**S
#### T3.5-07 契约冻结 v1.4.0
- **仓库**patbond-docopenapi.yaml+ patbond-api(快照同步)
- **描述**:沿用 M2/M3 迭代式冻结:草案(TODO-FREEZE 标注待定型点)→ 随 04/05/06 实现定型回填 → 拍板 → 合入 **v1.4.0****四模块字节级快照同步**pet/auth/community/user 的 `src/test/resources/contract/`)→ 契约矩阵扩展新操作全响应格。
- **验收**`mkdocs build --strict` 通过;契约矩阵零漂移;升版后 CI 绿(**漏快照同步必红**,见 iteration-3/19)。
- **依赖**T3.5-04/05/06 定型。**本波闸门,不冻结不放行第二波前端。规模**:M
### 第二波:客户端
#### T3.5-08 资料页真实化 + 编辑页
- **仓库**patbond-flutter
- **描述**`lib/features/profile/profile_page.dart`(现 176 行全硬编码 demo:「萌宠新手(豆豆家长)」/24/1.8k/2)替换为真实数据——昵称(空则 username)、头像、获赞、作品数、关注数;新增编辑页(昵称输入 + 头像上传复用 `MediaUploader`,复用 M3 的 gating/失败语义);四态齐备。
- **验收**:登录 `llx` 显示 `llx` 而非 demo 文案;改昵称后 Feed 作者名同步(同一后端回退链);头像上传全链路;四态有 widget 测试。
- **依赖**T3.5-07 冻结。**规模**L
#### T3.5-09 宠物头像上传接线
- **仓库**patbond-flutter
- **描述**:宠物详情页头像的铅笔角标(**当前已渲染但无功能**)接 `MediaUploader`;列表/详情展示真实头像(`SignedNetworkImage` 复用,缓存 key 剥签名参数已就位);无头像回退现有爪印占位。
- **验收**:上传后详情与列表同步显示;viewer 不显示编辑入口;失败态可重试。
- **依赖**T3.5-07 冻结。**规模**M
#### T3.5-10 首页 demo 裁剪
- **仓库**patbond-flutter
- **描述**:首页顶部 demo 分项处置——问候语「下午好,豆豆」改真实昵称(**本批做**);天气/位置(接外部服务,**建议出本批**);圈子「柴犬圈/猫咪圈/救助站」(实为话题,ADR-018 已剪出,**建议出本批**);促销卡「新用户首单立减 ¥20」(属 M5 服务域,**建议出本批**)。见待拍板 D3.5-1。
- **验收**:按拍板结果,保留项标注为「刻意保留的占位」并在报告登记,避免下次实测重复反馈。
- **依赖**T3.5-04 定型(昵称字段)。**规模**:S~M
---
## 3. 波次与关键路径
```text
第一波: T3.5-04 / T3.5-05 / T3.5-06(同仓串行提交)→ [T3.5-07 契约冻结 v1.4.0 闸门]
第二波: T3.5-08(L) / T3.5-09 / T3.5-10
```
预计一到两波收,无 L 工单堆叠在关键路径(仅 T3.5-08 一个 L)。发布按 releases.md 的**新流程走 PR**(main 已受保护,首发的直推写法已作废)。
---
## 4. 待拍板清单
| # | 决策 | 选项与影响 | 建议 |
| --- | --- | --- | --- |
| D3.5-1 | **首页 demo 裁剪范围** | 天气/位置需接外部服务(和风天气类,含 key 管理与配额);圈子=话题(ADR-018 剪出);促销卡属 M5 服务域 | **仅做问候语真实化**,其余三项留待对应里程碑,并在代码注释与报告显式标注「刻意保留的 demo 占位」 |
| D3.5-2 | **获赞总数端点形态** | A. 新增 `GET /api/v1/me/community-stats`(语义清晰、可扩展);B. 扩展既有 `follow-stats`(少一个端点,但语义混杂——它现在是「某用户的关注数」,加"我的获赞"会变成两种主体) | **A** |
| D3.5-3 | **宠物头像的写权限档** | WRITEowner+caregiver 都能改)vs MANAGE(仅 owner | **WRITE**——头像属日常照护信息,与体重/疫苗同档;照护人本就能改这些 |
| D3.5-4 | **nickname 唯一性** | 现 DB 无唯一约束(仅长度/btrim CHECK)。允许重名(社区常见,靠 userId 区分)vs 加唯一约束(需迁移,破本批零迁移前提) | **允许重名**,不加约束 |
| D3.5-5 | **注册时是否让用户填昵称** | 现注册只收 username/phone/passwordnickname 走 `insertUser` 但值来源需确认)。加一个可选昵称输入 vs 保持不填、注册后到资料页设 | **保持不填**(少一步注册摩擦),资料页可设 |
| D3.5-6 | **版本号** | v0.3.1(补丁语义)vs v0.4.0(含新端点,属功能增量) | **v0.4.0**——加了端点与字段,不是纯修补 |
---
## 5. 风险清单
| # | 风险 | 缓解 |
| --- | --- | --- |
| R1 | **契约升版漏同步快照** → 四模块守卫测试全红 | 冻结与快照同步放**同一工单**(T3.5-07)连贯执行,iteration-3/19 已有可照抄的操作序列 |
| R2 | 头像 URL 是**会过期的预签名 GET**,客户端若持久化会出现"图突然裂" | 沿用 M3 纪律:URL 不入本地存储;`SignedNetworkImage` 缓存 key 已剥签名参数 |
| R3 | 同仓(patbond-api)三个后端工单并行会抢工作树 | 同仓串行派工(M2/M3 已验证的模式) |
| R4 | 首页 demo 裁剪范围失控,滑向"顺手把首页重做一遍" | D3.5-1 钉死范围;保留项显式登记,避免反复 |
| R5 | 头像功能上线后,真机验证清单需新增项(弱网上传头像、头像缓存) | 收口时按维护约定登记进 `development/device-verification.md` |
---
## 6. 与既有纪律的衔接
- **零迁移**:本批不新增 Flyway 版本(下一个版本号仍为 V6,留给后续真正需要建表的迭代)
- **契约先行**:T3.5-07 冻结前,前端不得依赖未冻结字段
- **分支保护**:日常推 dev;发布走 PR(releases.md「发布后生效的纪律」)
- **路径参数化**:文档内命令一律 `cd <你的工作区>/<仓名>`