Files
Pawer/docs/功能缺陷核验与修复设计.md

344 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 汪圈小程序 - 功能缺陷核验与修复设计
> 核验日期2026-06-20
> 来源报告:`docs/功能缺陷审查报告.md`
> 核验基线:当前工作区源码,重点核验 `src` 与 `cloudfunctions`,忽略 `dist` 构建产物
> 目标:逐项确认报告中仍被标为缺陷/风险的条目是否真实存在,并为需要修改的条目形成可落地设计
## 1. 总体结论
报告中的“已修复确认”条目,当前代码整体能支撑报告结论;本次未发现需要重新打开的已修复项。消息页、发布页、附近页、聊天页的主要 v1 修复点均能在当前实现中找到对应逻辑。
报告中仍被标为遗留、新发现或系统性问题的条目共核验 35 项:
| 结论 | 数量 | 说明 |
|------|------|------|
| 成立 | 24 | 当前代码中确实存在对应行为或缺口 |
| 部分成立 | 5 | 现象存在,但报告描述、严重程度或根因需要修正 |
| 不成立 | 6 | 当前代码已解决或报告判断与实现不符 |
需要特别修正报告优先级:
- `nearbyPets` 云函数不按距离排序:不成立。当前 `cloudfunctions/nearbyPets/index.js` 会计算距离、筛选半径内位置,并按 `_dist` 升序排序。
- `messageList` 变量遮蔽:成立,但只是维护性风险,不应列为 P0。
- 聊天图片 `cloud://` 不显示:部分成立。服务层已经尝试解析 `cloud://` 为临时 URL真实问题是解析失败后会丢失可重试的 fileId页面只能显示“图片暂不可用”。
- 当前没有阻断级 P0 缺陷;建议从 P1 的数据一致性、分页、错误回滚开始修。
## 2. 已修复条目复核摘要
| 页面/模块 | 报告已修复项 | 本次复核 |
|-----------|--------------|----------|
| 广场页 | 分页、动态话题、广告随机、详情页、点赞竞态、搜索入口 | 代码中存在 `nextCursor``normalizeTopicOptions``createAdStartIndex``post-detail` 导航、`pendingLikeIds`/`pendingLikeOverrides`、搜索防抖 |
| 附近页 | 位置上报、尊重可见性、喜欢乐观更新、online 过滤 | 代码中存在 `updateLocation``nearbyVisible` 分支、`pendingLikeIds` 回滚、前后端 online 过滤 |
| 发布页 | 草稿恢复、字数上限、上传失败提示、关闭前保存草稿提示 | 代码中存在 `getDraft`/`restoreDraft``textLength <= 2000``UploadPostMediaError``handleClose` 草稿弹窗 |
| 消息页 | 已读等待、系统会话区分、搜索 activeUsers | 代码中 `openConversation` 会等待跳转/已读结果,系统消息不进聊天,搜索同时过滤在线汪友 |
| 聊天页 | 历史分页、智能轮询、发送失败不清空、发送状态、可靠滚动、图片/表情 | 代码中存在 `loadOlder`、3s/12s 轮询、成功后清空输入、`sending` 状态、`scrollIntoView`、图片和表情发送 |
| 我的动态 | 首次 onShow 重复加载 | `useRefreshOnShow` 会跳过首次 show |
| 跨页面 | 草稿系统、操作错误提示、`commentCreate` 类型 | 当前代码已接入草稿解析、常见操作 try/catch、`CloudFunctionName` 包含 `commentCreate` |
## 3. 逐项核验清单
| # | 报告条目 | 核验结论 | 是否需要修改 | 设计归属 |
|---|----------|----------|--------------|----------|
| 1 | 广场收藏功能入口缺失 | 成立。`Post`/云函数有收藏字段与能力,但 `PostCard` 无入口,`feed.service.ts` 也没有封装 `setPostFavorite` | 需要 | D1 |
| 2 | 广场 `loadFeed` 依赖 `debouncedKeyword` 导致频繁清空 | 部分成立。每次防抖关键词变化都会重载并清空列表;这是搜索行为的一部分,但会造成闪空体验 | 建议修改 | D4 |
| 3 | `PostCard` 移除更多按钮 | 成立。当前只剩点赞/评论,无举报、分享等入口 | 暂缓,需产品确认更多菜单内容 | D6 |
| 4 | 话题搜索与关键词不能同时生效 | 不成立。前端同时传 `topic``keyword`,云函数也会按 topic 查询后做关键词匹配 | 不修改,仅可优化提示文案 | D4 |
| 5 | 附近页 `nearbyVisible=null` 时显示默认上海与空列表 | 成立。偏好加载期间页面会渲染默认中心与空状态 | 需要 | D4 |
| 6 | 关闭附近可见不清理旧位置 | 不成立。`locationUpdate``visible=false` 时会写入 `location:null``geo:null``visible:false`,不会被附近查询命中 | 不修改 | - |
| 7 | `useUploadMedia` 暴露内部 `setMedia` | 成立。发布页直接用 setter 恢复草稿 | 建议小改 | D4 |
| 8 | 草稿恢复后宠物默认选中逻辑冲突 | 部分成立。当前逻辑会把“草稿无 petId”解释为“不关联宠物”但无法区分历史缺字段与用户主动不关联 | 需要明确语义后修改 | D4 |
| 9 | 聊天图片 `cloud://` 不显示 | 部分成立。正常路径会解析为临时 URL解析失败时会丢失 fileId无法重试 | 需要 | D3 |
| 10 | `loadLatest` 提前返回不清 `loadingInitial` | 不成立。`return` 位于 `try` 内,`finally` 仍会执行 | 不修改 | - |
| 11 | 个人页偏好开关无失败回滚 | 成立。`changePreference` 先改 UI`await updateProfile` 无 catch | 需要 | D1 |
| 12 | `useSession` 与个人页重复 `ensureLogin` | 成立但影响很低。store 有 inflight 去重,不会造成重复请求 | 暂不修改,可作为结构清理 | D6 |
| 13 | 资料编辑 `type='nickname'` 仅真机生效 | 成立但属于微信平台限制 | 不按缺陷修,可补充提示 | D6 |
| 14 | `avatarUrl``avatarKey` 共存冲突 | 不成立。当前是图片优先、渐变头像兜底;`getProfile` 已解析 cloud URL | 不修改 | - |
| 15 | 资料编辑页无未保存离开提示 | 成立。关闭按钮直接 `navigateBack` | 需要 | D4 |
| 16 | 宠物数量无上限 | 部分成立。当前确实无限制,但是否是缺陷取决于产品策略 | 需要产品确定上限后修改 | D6 |
| 17 | 宠物照片交互暗示必填 | 不成立。当前无必填标识,校验也只要求名字和品种 | 不修改,可加“可选”文案 | - |
| 18 | 联系人互关后 `isFriend` 未即时更新 | 成立。`toggleFollow` 返回 `mutual`,页面未使用 | 需要 | D1 |
| 19 | 联系人“发现”搜索需手动提交 | 成立。`onInput` 只更新本地关键词,只有 `onConfirm` 请求 | 建议修改 | D1 |
| 20 | 点击用户只能发起聊天,无个人主页 | 成立。当前没有用户主页页面 | 中期功能 | D6 |
| 21 | 我的动态点赞无错误回滚 | 成立。`likePost` 无 try/catch | 需要 | D1 |
| 22 | 我的动态无分页,云函数 `limit(50)` | 成立。`userPosts` 无 cursor前端一次加载 | 需要 | D2 |
| 23 | 我的动态点赞事件导致重复 setPosts | 成立但影响低。服务成功会广播,页面本地也已设置 | 可随点赞回滚一起收敛 | D1 |
| 24 | 帖子详情评论缺少分页 | 成立。`postDetail` 一次最多 200 条评论 | 需要 | D2 |
| 25 | 帖子详情点赞闭包竞态 | 成立。回滚用当前渲染闭包中的 `post` | 需要 | D1 |
| 26 | 发送评论后作者硬编码“我” | 成立。乐观评论未使用当前用户资料 | 需要 | D1 |
| 27 | 帖子详情加载失败也设 `commentsLoaded=true` | 成立。`load` 无 catch失败后会显示空评论 | 需要 | D4 |
| 28 | 全局登录态守卫缺失 | 成立。`logout` 是本地标记,非 profile 页面未统一拦截 | 需要设计为轻量会话门禁 | D5 |
| 29 | `StatsRow` 计数可能不同步 | 部分成立。`profileGet` 已实时计算,但 follow/favorite 等操作后没有统一标记 profile 失效 | 需要 | D5 |
| 30 | `cloud-file.ts` 临时 URL 缓存永不过期 | 成立 | 需要 | D5 |
| 31 | `dataBus.ts` `postCache` 永不清除 | 成立 | 需要 | D5 |
| 32 | `messageList``openid` 回调变量遮蔽 | 成立,但只是可维护性问题 | 顺手修 | D6 |
| 33 | `messageThread` 每次轮询都写已读 | 成立。云函数每次取线程都会 update 会话已读 | 需要 | D3 |
| 34 | `nearbyPets` 不按距离排序 | 不成立。当前会按距离排序 | 不修改 | - |
| 35 | 消息通知/推送未接入 | 成立,但这是新功能,不是当前消息列表逻辑缺陷 | 长期迭代 | D6 |
## 4. 修复设计
### D1. 互动状态一致性与回滚
目标:统一点赞、收藏、关注、偏好开关、评论乐观更新的失败回滚与跨页面同步,优先解决用户可感知的数据错乱。
涉及文件:
- `src/services/feed.service.ts`
- `src/services/social.service.ts`
- `src/pages/my-posts/index.tsx`
- `src/pages/post-detail/index.tsx`
- `src/pages/profile/index.tsx`
- `src/pages/contacts/index.tsx`
- `src/features/feed/components/PostCard/index.tsx`
- `cloudfunctions/postFavorite/index.js`
设计:
1. 收藏能力补齐:
-`feed.service.ts` 新增 `setPostFavorite(postId, favorited)`,调用 `postFavorite` 云函数。
- `postFavorite` 云函数补齐缺集合兜底,避免新库第一次收藏失败。
- `PostCard` 增加收藏按钮与数量展示,使用 `post.favoritedByMe``counts.favorites`
- 广场、我的动态、帖子详情统一实现乐观收藏、失败回滚、成功后写入服务端返回值。
- 新增收藏同步事件或复用 dataBus 扩展,例如 `emitPostFavorite({ postId, favorited, favorites })`
2. 我的动态点赞:
- 引入 `postsRef``pendingLikeIds`,按广场页模式保存操作前快照。
- `setPostLike` 失败时恢复 `likedByMe``counts.likes`
- `onPostLike` 监听中先比较目标 post 当前值,相同则返回原数组,减少重复 re-render。
3. 帖子详情点赞:
- 新增 `postRef` 保存最新 post`likePost` 以 ref 为准,不用渲染闭包中的 `post`
-`pendingLikeIds` 防止同一帖子连续点击造成并发请求。
- 失败时回滚到请求开始时的 snapshot。
4. 评论乐观作者:
- `PostDetailPage` 使用 `useSession()` 获取当前用户。
- 乐观评论 author 使用 `user._id``user.nickname``user.avatarKey``user.avatarUrl`
- `commentCreate` 可在后续返回完整 comment短期先保持 `{ commentId, comments }` 返回结构不变。
5. 联系人关注:
- `onFollow` 使用 `toggleFollow` 返回的 `following``mutual` 回写 `isFollowing``isFriend`
- 失败回滚到完整旧 user snapshot而不是只反转 `isFollowing`
- `social.service.toggleFollow` 成功后调用 `markStale('profile')`,让资料页统计在返回时刷新。
6. 偏好开关:
- `changePreference` 保存 previous user。
- 乐观更新后 `try/catch` 调用 `updateProfile`;失败恢复 previous user 并 toast。
- `updateProfile``markStale('profile')` 建议移动到云函数成功后,避免失败也标记新鲜/脏状态混乱。
验收:
- 断网或云函数失败时,点赞/收藏/关注/偏好开关都能回滚 UI。
- 互关后联系人页立即显示“汪友”。
- 新评论展示当前用户昵称和头像,不再硬编码“我”。
- 同一帖子在广场、我的动态、详情页的点赞/收藏状态保持一致。
### D2. 列表与评论分页
目标:移除 50/200 条硬上限,保证长列表可继续加载。
涉及文件:
- `cloudfunctions/userPosts/index.js`
- `cloudfunctions/postDetail/index.js`
- `src/services/feed.service.ts`
- `src/pages/my-posts/index.tsx`
- `src/pages/post-detail/index.tsx`
- `src/types/cloud.ts`
设计:
1. `userPosts` 分页:
- 云函数入参新增 `cursor?: string``pageSize?: number`
-`createdAt desc` 查询 `pageSize + 1`cursor 使用上一页最后一条 `createdAt`
- 返回 `{ list, nextCursor }`
- `getUserPosts` 改为返回 `CursorResponse<Post>`,兼容 `authorId`
- `MyPostsPage` 增加 `nextCursor``loadingMore``loadMore`,在滚动到底部加载。
2. 评论分页:
- `postDetail` 入参新增 `commentCursor?: string``commentPageSize?: number`
- 评论按 `createdAt asc` 返回,初始取最早一页;下一页使用 `createdAt > commentCursor`
- 返回 `{ post, comments, commentsNextCursor }`
- `PostDetailPage` 增加 `commentsNextCursor``loadingCommentsMore`、底部“加载更多评论”。
- 为避免刷新帖子详情时重复替换图片,继续保留当前“只更新计数”的策略。
验收:
- 我的动态超过 50 条时可以继续加载。
- 帖子评论超过单页限制时可以继续加载下一页。
- 分页加载失败有 toast当前列表不被清空。
### D3. 聊天图片可靠展示与已读写入控制
目标:图片临时 URL 解析失败后可重试;聊天轮询不再每次写数据库。
涉及文件:
- `src/services/message.service.ts`
- `src/pages/chat/index.tsx`
- `src/types/domain.ts`
- `cloudfunctions/messageThread/index.js`
设计:
1. 图片消息数据结构:
- `ChatMessage` 增加可选字段 `fileId?: string``assetState?: 'ready' | 'unresolved'`
- `resolveThreadAssets` 解析图片时保留原始 `cloud://``fileId``content` 存临时 URL解析失败时 `content=''``assetState='unresolved'`
- 页面渲染 unresolved 图片时展示占位和“重试”操作。
- 重试时调用 `resolveCloudFileUrl(fileId)`,成功后只更新该条消息。
2. 已读写入控制:
- `messageThread` 入参新增 `markRead?: boolean`,默认 `true` 以兼容旧调用。
- 首次打开会话、用户主动刷新时传 `markRead:true`
- 静默轮询 `loadLatest({ silent:true })` 和加载历史传 `markRead:false`
- 云函数仅在 `markRead !== false` 且会话存在时执行已读 update。
验收:
- 图片临时 URL 获取失败后,页面不会永久丢失 cloud fileId。
- 静默轮询不再触发会话已读 update。
- 首次进入聊天仍会清除当前用户未读数。
### D4. 页面加载、搜索与离开保护
目标:减少空白闪烁,补齐错误状态与未保存提示。
涉及文件:
- `src/pages/plaza/index.tsx`
- `src/pages/nearby/index.tsx`
- `src/features/nearby/components/NearbySheet/index.tsx`
- `src/pages/post-detail/index.tsx`
- `src/pages/profile-edit/index.tsx`
- `src/hooks/useUploadMedia.ts`
- `src/pages/publish/index.tsx`
设计:
1. 广场搜索:
- 搜索参数变化时保留旧列表,设置 `searching=true`,新结果回来后替换。
- 或仅在用户按确认键时清空列表,防抖输入走静默刷新。
- 搜索结果文案增加当前 topic例如“在 #遛弯 中搜索...”,解决语义提示问题。
2. 附近偏好加载:
- 增加 `preferenceLoading = nearbyVisible === null`
- 偏好未返回前,地图可显示默认中心,但列表区展示“正在读取附近设置”,不展示“附近暂时没有汪友”。
- `NearbySheet` 接收 `loading``state`,区分 loading、disabled、empty 三种状态。
3. 帖子详情加载失败:
- `load` 增加 catch设置 `commentsError`
- 失败时展示“评论加载失败,点击重试”,不显示“还没有评论”。
- `finally` 只负责关闭 loading不吞掉错误状态。
4. 资料编辑未保存提示:
-`initialRef` 保存首次加载后的表单快照。
- 关闭按钮走 `handleClose`dirty 时弹窗确认放弃/继续编辑。
- 保存成功后更新 dirty 基线或直接返回。
5. 上传媒体 hook
- `useUploadMedia` 对外暴露 `replaceMedia(next)`,发布页草稿恢复改用该方法。
- 保留内部 `setMedia` 私有,后续上传状态不会被外部绕过。
6. 草稿宠物语义:
- 新草稿保存时增加 `petSelectionExplicit: boolean` 或将 `petId` 扩展为 `string | null`
- `petId=null` 表示用户明确选择“不关联宠物”;`petId` 缺失表示历史草稿/未选择,可默认第一只宠物。
- 对旧草稿做兼容:没有该字段时默认第一只宠物,除非后续产品明确要求保持“不关联”。
验收:
- 广场搜索输入时不出现明显空白闪烁。
- 附近页读取设置期间不显示错误的空列表语义。
- 评论加载失败有可见错误和重试。
- 编辑资料有未保存离开确认。
- 草稿恢复不会误把历史缺字段理解为用户主动不关联。
### D5. 会话门禁、统计失效与缓存淘汰
目标:统一“本地登出/资料未完成”的门禁语义,并控制长期运行内存增长。
涉及文件:
- `src/store/session.store.ts`
- `src/services/auth.service.ts`
- `src/services/social.service.ts`
- `src/services/feed.service.ts`
- `src/services/pet.service.ts`
- `src/store/dataBus.ts`
- `src/services/cloud-file.ts`
- `src/pages/home/index.tsx`
设计:
1. 轻量登录态守卫:
- 新增 `useSessionGate({ requireCompleted?: boolean })` 或等价 helper。
- 对发布、消息、联系人、附近等需要真实用户态的页面/操作,若 `isLoggedOut()``!user.profileCompleted`,引导到“我的”完成登录资料。
- 广场可保持只读,但点赞、收藏、评论、关注等写操作必须先过 gate。
- 由于微信 openid 是静默登录,本设计不把“未登录”理解为无 openid而是“用户本地退出或资料未完成”。
2. 统计失效:
- follow、favorite、post create/delete、pet save/delete 成功后统一 `markStale('profile')`
- 资料页返回时通过现有 `useTabRefresh('profile')` 获取实时 `profileGet` 统计。
- 如后续新增联系人资源 key可扩展 `ResourceKey``contacts`,用于关注列表自身失效。
3. 临时 URL 缓存:
- `tempUrlCache``Map<string,string>` 改为 `Map<string,{ url:string; expiresAt:number; lastAccess:number }>`
- 默认 TTL 50 分钟,低于微信临时 URL 常见有效期。
- 最大容量建议 500超出时按 `lastAccess` 淘汰最旧项。
- 每次 resolve 前执行轻量 prune。
4. 帖子 hand-off 缓存:
- `postCache` 增加 TTL 10 分钟、最大容量 100。
- `getCachedPost` 读取过期项时删除并返回 `undefined`
- `cachePost` 写入时执行容量淘汰。
验收:
- 本地退出后,写操作不会继续静默执行。
- 关注/收藏后回到资料页,统计能刷新。
- 长时间浏览大量帖子/图片后,缓存 Map 不会无界增长。
### D6. 中长期产品能力与低风险清理
这些条目成立或部分成立,但不建议混入第一批稳定性修复。
1. 更多菜单:
- `PostCard` 预留 `onMore`
- 菜单项建议先做“分享”“复制内容/链接”“举报”。
- 举报若上线,需要新增 `postReport` 云函数和审核字段,不只是前端按钮。
2. 用户个人主页:
- 新增 `/pages/user-profile/index?userId=...`
- 复用 `profileGet({ userId })``userPosts({ authorId })`,只展示公开资料与公开动态。
- 联系人列表头像/名称点击进主页,聊天按钮保留为独立操作。
3. 宠物数量上限:
- 需要先确定产品上限,例如 6 或 10。
- 前端 `PetShelf` 达上限隐藏/禁用“添加宠物”。
- 后端 `petSave` 新增创建前 count 校验,防止绕过前端。
4. 消息推送:
- 作为订阅消息能力单独立项。
- 需要用户授权订阅模板、发送时机、频率限制、退订处理。
5. 低风险清理:
- `messageList` 回调参数 `openid` 改名为 `memberOpenid`
- 个人页重复 `ensureLogin` 可在重构时简化,但当前无需优先。
- `type='nickname'` 平台限制可在开发/非真机环境补充提示,不作为功能修复。
## 5. 推荐实施顺序
| 阶段 | 内容 | 原因 |
|------|------|------|
| P1 | D1 互动状态一致性、D2 我的动态分页、D3 聊天图片重试、D4 帖子详情错误状态 | 直接影响用户操作结果与内容可见性 |
| P2 | D2 评论分页、D4 广场/附近加载体验、资料编辑离开保护、D5 统计失效与会话门禁 | 提升长列表、返回刷新和基础流程可靠性 |
| P3 | D5 缓存淘汰、D6 更多菜单/用户主页/宠物上限/推送/清理项 | 风险较低或属于产品能力扩展 |
## 6. 开发校验建议
1. 每个云函数分页改动都保留旧入参兼容,避免旧包调用失败。
2. 点赞、收藏、关注、偏好开关需要分别模拟云函数失败,确认 UI 回滚。
3. 图片消息需要模拟 `getTempFileURL` 失败,确认 fileId 被保留且可重试。
4. 使用 `npm run typecheck` 验证类型改动。
5. 微信开发者工具中重点回归:广场搜索、附近首次进入、聊天轮询、我的动态滚动加载、帖子详情评论加载。