Files
Pawer/docs/功能缺陷审查报告.md

22 KiB
Raw Permalink Blame History

汪圈小程序 — 功能逻辑缺陷审查报告 v2.0

审查日期2026-06-19 基线版本v1.0 报告审查后的最新代码 审查范围:全部 11 个页面、10 个 Service、25+ 个云函数、4 个类型文件、3 个 Store/Hook、1 个新增 cloud-file 工具模块 审查分支:master


v1 → v2 修复总结

v1 报告共提出 36 条 问题(含跨页面系统性问题 6 条),本次审查确认的修复情况如下:

类别 v1 问题数 已修复 ⚠️ 部分修复 未修复 🆕 新发现
广场页 7 5 0 1 3
附近页 5 4 0 0 2
发布页 4 4 0 0 2
消息页 4 4 0 0 0
聊天页 6 6 0 0 2
个人资料页 2 0 1 0 1
资料编辑页 3 1 0 2 0
宠物编辑页 2 0 0 2 0
联系人页 3 0 1 2 0
我的动态页 3 2 0 0 2
帖子详情页 4 (新增页)
跨页面系统 6 4 0 2 2
合计 36+ 26 2 7 18

一、广场页 pages/plaza/index.tsx

v1 修复确认

v1 # 问题 状态 说明
1 Feed 无分页 / 无限滚动 已修复 引入 nextCursorloadNextPagePageShell lowerThreshold={160}loadingMore 状态,实现了完整的无限滚动。
2 话题列表硬编码 已修复 引入 topicOptions 状态 + normalizeTopicOptions(),从云端 result.topics 动态合并。
3 广告卡片位置固定 已修复 引入 createAdStartIndex() 随机起点 + AD_INTERVAL = 6 间隔,每次加载重新随机化。
5 没有帖子详情页 已修复 新增 pages/post-detail/index 页面,PostCard 支持 onOpen 导航。
6 点赞乐观更新竞态 已修复 引入 feedRequestSeq + pendingLikeIds / pendingLikeOverrides 防重复机制,失败时回滚到 previousPost 快照。
7 搜索栏无功能 已修复 绑定 onInput + 300ms 防抖 debouncedKeyword,搜索时展示结果计数 + 清除按钮。

v1 遗留

v1 # 问题 说明
4 收藏功能入口缺失 Post.favoritedByMe 字段和 feed.service.setPostFavorite() 仍然存在,PostCard 仍然没有收藏操作的 UI 入口。跨页面系统性问题 #2 未解决。

🆕 v2 新发现

# 缺陷 严重程度 位置 说明
1 loadFeed 依赖数组包含 debouncedKeyword 导致频繁重置 🟡 useEffect (L156-158) loadFeeduseCallback 依赖了 debouncedKeyword。防抖 300ms 后 debouncedKeyword 变化 → loadFeed 重建 → useEffect 触发 → 清空列表重新加载。用户每输入一个字符(停顿 300ms 后),整个列表会被清空重载一次,体验上表现为列表频繁闪烁空白再重新出现。应将 loadFeed 拆分为"参数变化时的首次加载"和"追加加载",或使用 ref 追踪 keyword 变化而非作为 useCallback 依赖。
2 PostCard 移除了 more 按钮但无替代操作 🟡 PostCard (L44-66) v1 中 PostCard 有一个 more 图标按钮三点点v2 中完全移除了。用户无法对帖子执行更多操作(举报、分享、复制链接等),丢失了内容管理入口。
3 话题搜索结果与关键词搜索混合逻辑不一致 🟡 搜索逻辑 (L278-306) 关键词搜索通过 debouncedKeyword 传给 getFeedList,服务端过滤。但话题筛选通过 topic 参数传给同一个 getFeedList。两者不能同时生效——用户选择了某个话题后又输入搜索词行为不明确。UI 上也没有层级提示告诉用户"在话题内搜索"还是"全局搜索"。

二、附近页 pages/nearby/index.tsx

v1 修复确认

v1 # 问题 状态 说明
1 没有调用 updateLocation 上报自身位置 已修复 获取位置后调用 updateLocation(loc, true)relocate 时也会更新,nearbyVisible === false 时传 null, false
2 没有尊重 preferences.nearbyVisible 偏好 已修复 getProfile() 读取偏好,nearbyVisible === false 时不请求位置、清空宠物列表、不上报位置。
3 likePet 没有乐观更新和错误回滚 已修复 引入 pendingLikeIds、乐观设置 likedByMe: true,失败时回滚到 previousLiked
4 filter: 'online' 后端过滤能力不确定 已修复 nearby.service.ts 新增 filterNearbyPets() 前端兜底过滤。nearbyPets 云函数也已支持 online filter。

🆕 v2 新发现

# 缺陷 严重程度 位置 说明
1 nearbyVisiblenull 时地图显示默认中心上海但无数据 🟡 初始化流程 (L32, L57-83) nearbyVisible 初始值为 null(等待 getProfile 返回),此时地图渲染 DEFAULT_CENTER(上海),centernull 所以 loadPets 不触发,宠物列表为空。用户看到一个空地图和空列表,直到 getProfile 完成(可能 1-2 秒)。应显示加载指示器或骨架屏。
2 没有清理自身旧位置(登出 / 关闭可见性) 🟢 nearbyVisible === false 分支 (L61-67) 调用 updateLocation(null, false) 但没有确认云函数是否正确处理 null location从数据库中移除旧坐标。如果云函数只是不写入用户的旧位置数据会一直留在数据库中。

三、发布页 pages/publish/index.tsx

v1 修复确认

v1 # 问题 状态 说明
1 没有恢复草稿的逻辑 已修复 useEffect 调用 getDraft(),通过 hasDraftContent() 判断后 restoreDraft() 回填所有表单字段。
2 没有内容字数上限预警 已修复 canSubmit 检查 textLength <= 2000,超限时 submit()toast: '正文最多 2000 字'
3 图片上传失败静默跳过 已修复 新增 UploadPostMediaError 自定义错误类,showUploadFailure() 弹窗明确告知第几张图片上传失败。
4 返回没有未保存提示 已修复 handleClose() 检查 hasContent,弹窗询问"保存草稿?" 或 "不保存"离开。

🆕 v2 新发现

# 缺陷 严重程度 位置 说明
1 useUploadMediasetMedia 暴露了内部 setter 🟢 useUploadMedia 解构 (L53) const { media, setMedia, addImages, removeMedia } = useUploadMedia([]) — 直接暴露 setMedia 并在 restoreDraft 中使用。绕过了 useUploadMedia 的内部管理逻辑,如果该 hook 未来添加了额外状态(如上传状态),直接 setMedia 不会触发这些逻辑。应通过 hook 暴露一个 replaceMedia 方法。
2 草稿恢复成功后宠物默认选中逻辑冲突 🟡 useEffect (L81-87) getPetListuseEffectsetSelectedPetId(prev => prev || (draftRestoredRef.current ? '' : list[0]._id))。当草稿恢复成功时 draftRestoredRef.current = true,但如果草稿没有设置 petIddraft.petId 为空),宠物不会被默认选中——这可能不是用户期望的(用户之前可能选中了一个宠物但草稿没保存 petId

四、消息页 pages/messages/index.tsx

v1 修复确认

v1 # 问题 状态 说明
1 markConversationRead 是 fire-and-forget 已修复 openConversation 中正确 await markConversationRead,失败时回滚 unreadCountnavigateTo 失败时也回滚。
2 没有区分 single / system 会话交互 已修复 item.type === 'system' 时直接调用 markConversationRead 标记已读并 toast "系统消息已读",不再跳转聊天页。
3 没有消息通知 / 推送机制 ⚠️ 部分修复
4 搜索不会过滤 activeUsers 已修复 搜索时也过滤 activeUsersvisibleActiveUsers,有匹配时显示"在线汪友"分区。

v2 无新增问题


五、聊天页 pages/chat/index.tsx

v1 修复确认

v1 # 问题 状态 说明
1 消息列表没有分页加载历史 已修复 引入 loadOldernextCursorupperThreshold={48} 触发加载。加载后滚动到锚点消息位置。
2 轮询频率固定 5 秒 已修复 引入 ACTIVE_POLL_MS = 3000 / IDLE_POLL_MS = 12000 / ACTIVE_POLL_WINDOW_MS = 60000 三档智能调节。
3 发送失败时输入框闪空 已修复 send() 中不再先 setDraft(''),而是在 sendMessage 成功后才 setDraft('')sendImageMessage 也类似处理。
4 没有消息发送状态指示 已修复 sending 状态驱动 UI按钮显示"发送中"、输入框 disabled、工具栏按钮 disabled。
5 scrollTop 递增 hack 不可靠 已修复 改用 scrollIntoView + 动态 bottomAnchorIdscrollSeq 递增)实现可靠滚动到底部。
6 不支持图片 / 表情消息 已修复 新增图片发送 (sendImages)、表情面板 (EMOJI_OPTIONS)、图片消息展示 + 预览 (previewImage)。

🆕 v2 新发现

# 缺陷 严重程度 位置 说明
1 图片消息只显示非 cloud:// URL 的图片 🔴 图片渲染逻辑 (L442) m.content && !m.content.startsWith('cloud://') — 当消息的 content 是 cloud:// 文件 ID已上传到云端但 resolveCloudFileUrl 没有成功转换时),会显示"图片暂不可用"文本气泡。messageThread 云函数和 resolveThreadAssets 应该已经解析了这些 URL但如果解析失败用户会看到"图片暂不可用",没有重试机制。更严重的是,任何以 cloud:// 开头的内容都被认为是"不可用图片"而非普通文本,如果某些消息的 type 被错误标记为 image 但 content 是 cloud ID将无法展示。
2 loadLatest 提前返回时可能不清除 loadingInitial 🟢 loadLatest (L183-220) 如果 requestId !== latestRequestSeq.current 导致提前 returnloadingInitial 不会被清除——但 finally 块仍会执行return 在 try 块内),所以实际不会出现此问题。仅限极端竞态场景。

六、个人资料页 pages/profile/index.tsx

⚠️ v1 部分修复

v1 # 问题 状态 说明
1 偏好 changePreference 无错误回滚 ⚠️ 部分修复 updateProfile 内部调用 markStale('profile'),数据会在下次 useRefreshOnShow 时刷新。但 changePreference 本身仍然没有 try-catch——如果 updateProfile 抛出异常UI 状态已改但数据未持久化,直到下次刷新才恢复。用户仍然会看到一个短暂的"假成功"状态。

🆕 v2 新发现

# 缺陷 严重程度 位置 说明
1 useSession 初始化时 ensureLoginProfilePage 自身的 ensureLogin 重复调用 🟢 useEffect (L40-47) useSession hook 内部在 mount 时已经调用了 ensureLogin()ProfilePageuseEffect 中又调用了一次。虽然有 inflight 去重机制不会产生两次网络请求,但代码结构上造成了理解困惑。可考虑依赖 useSession 的 user 即可。

七、资料编辑页 pages/profile-edit/index.tsx

v1 遗留

v1 # 问题 状态 说明
1 type='nickname' 仅真机生效 未修复 仍然是 type='nickname',没有非真机环境的校验提示。这是微信平台限制,可接受。
2 avatarUrlavatarKey 共存冲突 未修复 新增了 avatarDisplayUrl 变量L25: avatarUrl && !avatarUrl.startsWith('cloud://') ? avatarUrl : ''),解决了 cloud:// ID 在 <Image> 中无法显示的问题。但 avatarKey 在头像已上传的情况下仍然是一个无用的遗留值,Avatar 组件和 Image 组件的优先级关系仍不明确。
3 没有未保存离开提示 未修复 关闭按钮仍然是直接 navigateBack(),没有检测是否有未保存修改。

v2 无新增问题


八、宠物编辑页 pages/pet-edit/index.tsx

v1 遗留

v1 # 问题 状态 说明
1 没有宠物数量上限限制 未修复 PetShelf 组件仍然无条件显示"添加宠物"按钮。后端也没有数量限制。
2 照片隐式非必填但交互暗示必填 未修复 提交验证只检查 namebreed,无照片也能保存。新增了 photoDisplayUrl 逻辑L81解决了 cloud:// ID 显示问题。

v2 无新增问题


九、联系人页 pages/contacts/index.tsx

⚠️ v1 部分修复

v1 # 问题 状态 说明
3 互关后 isFriend 未即时更新 ⚠️ 部分修复 v1 报告指出 onFollow 只更新 isFollowing 不更新 isFriend。当前代码仍然只更新 isFollowingisFriend 未同步。需调用 toggleFollow 后根据返回的 mutual 更新 isFriend

v1 遗留

v1 # 问题 状态 说明
1 "发现"搜索需手动提交 未修复 仍然只有 onConfirm 触发搜索,没有实时搜索或防抖输入触发。
2 点击用户只能发起聊天 未修复 仍然没有用户个人主页入口。

v2 无新增问题


十、我的动态页 pages/my-posts/index.tsx

v1 修复确认

v1 # 问题 状态 说明
2 useDidShow 首次触发导致重复加载 已修复 改用 useRefreshOnShow('myPosts', () => load(true)),首次 onShow 被跳过。

v1 遗留

v1 # 问题 状态 说明
3 likePost 没有错误回滚 未修复 likePost 仍然没有 try-catch 和回滚逻辑——await setPostLike(postId, target.likedByMe) 失败时不会恢复之前的点赞状态。

🆕 v2 新发现

# 缺陷 严重程度 位置 说明
1 仍然没有分页加载 🟡 load() (L21-31) userPosts 云函数硬编码 limit(50),前端一次性加载全部 50 条。如果用户帖子超过 50 条,超出部分无法查看。应添加 cursor / pageSize 参数到 userPosts 云函数和前端。
2 likePost 通过 onPostLike 事件同步但有重复调用 🟡 likePost (L49-63) likePost 做了乐观更新 + 调用 setPostLike(后者通过 emitPostLike 广播)。同时还有一个 useEffect 订阅 onPostLike 事件。当用户在"我的动态"页点赞时,setPostLikeemitPostLike → 本页的 onPostLike 监听器也会触发 setPosts,导致同一条帖子的点赞状态被设置了两次(第一次是 likePost 直接设置,第二次是事件监听器设置)。虽然结果相同,但多了一次不必要的 re-render。

十一、帖子详情页 pages/post-detail/index.tsx 🆕 新增页面

# 缺陷 严重程度 位置 说明
1 评论缺少分页加载 🟡 load() (L30-59) postDetail 云函数一次返回最多 200 条评论(safeGet('comments', { postId }, 200)),前端没有分页。如果帖子评论超过 200 条,超出部分无法查看。
2 likePost 的乐观更新使用闭包中的 post 而非 ref 🟡 likePost (L66-85) const previousPost = postL82 回滚时使用)来自当前渲染闭包中的 post state。如果用户快速双击点赞第一次 setPost(next) 还没 re-render第二次进入时 post 仍是旧值),乐观更新的"之前"状态会不准确。应使用 ref 追踪最新的 post。
3 发送评论后新评论的 author 硬编码为"我" 🟡 send() (L87-116) 新评论被乐观添加到列表,但 author: { id: 'me', name: '我', avatarKey: 'gradient-avatar-1' } 是硬编码的,没有使用当前用户的真实昵称和头像。用户看到自己评论显示为"我"而非自己的昵称。应从 useSession() 获取当前用户信息。
4 load 函数的 finally 中总是设置 commentsLoaded = true 🟢 load() (L58) 即使 getPostDetail 抛出异常,commentsLoaded 也会被设为 true,导致"评论加载中…"变成"还没有评论"。但此时用户不知道加载失败了(没有错误提示)。应在 catch 中显示错误状态。

十二、跨页面系统性问题

v1 修复确认

v1 # 问题 状态 说明
3 草稿系统断裂 已修复 发布页 mount 时调用 getDraft()resolveDraftMedia() 解析草稿中的 cloud file URL表单完整回填。
5 没有全局错误边界 已修复 各页面和 service 层普遍添加了 try-catch 和错误回滚。虽然不是 React ErrorBoundary但每个操作都有用户可感知的错误提示。
6 CloudFunctionName 缺少 commentCreate 已修复 cloud.ts 中已包含 commentCreate

v1 遗留

v1 # 问题 说明
1 全应用没有全局登录态守卫 session.store.ts 实现了共享 session + ensureLoginapp.tsx 在 launch 时预热登录。但广场、附近、消息、发布等页面仍然没有检查登录态——未登录用户能看到空数据但不会跳转到登录页。
4 StatsRow 数据可能与子页面不同步 未修复。profile.getProfile()contacts.getFollowList() 返回的计数仍然可能因缓存延迟不一致。

🆕 v2 新发现

# 问题 影响范围 严重程度 说明
1 cloud-file.tstempUrlCache 永不过期 全应用 🟡 resolveCloudFileUrls 将 cloud file ID 到临时 URL 的映射缓存在内存 Map 中,但永不清除。长时间使用小程序后,这个 Map 会持续增长(每次加载帖子/头像/评论都添加条目)。虽然单个 URL 映射很小,但如果用户大量浏览内容(数百帖子,数千评论),累积效果不应忽视。应添加 LRU 或 TTL 淘汰策略。
2 dataBus.tspostCache 永不清除 跨页面 🟢 cachePost 将帖子数据存入全局 MapgetCachedPost 只在 PostDetailPage 中调用。这个 Map 永远不会被清除——如果用户浏览了很多帖子,所有数据都会留在内存中。应在一定时间后或 Map 大小超过阈值时清除。
3 messageList 云函数中 peerOpenids 过滤的变量遮蔽 消息页 🟡 messageList/index.js L88: (c.memberOpenids || []).find(openid => openid !== OPENID)find 的回调参数名 openid 遮蔽了外层的 OPENID 常量。逻辑上结果正确(参数 openid !== OPENID 等价于"找到不是我的那个成员"),但变量命名遮蔽在未来维护时容易引入真实 bug。应重命名回调参数member)。
4 messageThread 云函数每次轮询都执行一次已读标记 update 聊天页 → 消息页 🟢 messageThread/index.js L97-99: 每次请求消息列表都会 update conversation 清除未读。聊天页的轮询3-12 秒一次)会频繁触发不必要的数据库写入。如果两个用户同时在看同一会话,一方的轮询会清除另一方的未读状态——虽然 _.pull(OPENID) 只清除当前用户,但频繁 update 仍是不必要的开销。应区分首次加载(标记已读)和轮询刷新(不标记已读)。
5 nearbyPets 云函数不按距离排序 附近页 🔴 nearbyPets/index.js L86: db.collection('pets').where(query).limit(50).get() — 没有 .orderBy 按距离排序。返回的宠物列表按 MongoDB 默认的写入顺序排列,用户看到的附近宠物不是按距离远近排列的。对于"附近"功能来说,距离排序是核心体验。应使用 geoNear 或在应用层按 distanceText 排序。

十三、修复优先级建议

P0 — 必须立即修复

  1. nearbyPets 云函数不按距离排序 — "附近"功能的核心体验缺失,用户期望看到最近的宠物排在最前面
  2. 聊天页图片消息 cloud:// 不显示 — 用户发送/接收的图片消息可能显示"图片暂不可用"
  3. messageList 云函数 peerOpenids 变量遮蔽 — 代码可维护性风险,未来修改时容易引入真实 bug

P1 — 短期内修复

  1. 联系人页 onFollow 互关后未即时更新 isFriend
  2. 联系人页"发现"搜索无实时搜索
  3. 我的动态页 likePost 错误回滚
  4. 我的动态页无分页 + userPosts 云函数 limit(50) 硬编码
  5. 广场页搜索输入导致列表频繁重载(debouncedKeywordloadFeed 重建 → 清空列表)
  6. 帖子详情页评论 author 硬编码"我"
  7. 收藏功能 UI 入口补全(PostCard 添加收藏按钮)
  8. changePreference 添加 try-catch
  9. 帖子详情页 likePost 闭包问题 → ref

P2 — 中期迭代

  1. 资料编辑页 / 宠物编辑页未保存离开提示
  2. 帖子详情页评论分页
  3. PostCard 恢复"更多操作"按钮
  4. 宠物数量上限
  5. 全局登录态守卫(非 TabBar 页面检查登录)
  6. 联系人页添加用户个人主页入口
  7. messageThread 轮询时避免不必要的已读标记 update
  8. 附近页 nearbyVisible 加载状态提示

P3 — 长期优化

  1. cloud-file.ts tempUrlCache 添加 TTL / LRU 淘汰
  2. dataBus.ts postCache 添加大小限制 / TTL
  3. 消息通知 / 推送接入
  4. 用户个人主页(完整帖子列表 + 宠物列表展示)
  5. 广场页话题与关键词搜索混合逻辑优化

本报告基于源码静态审查生成,所有问题均指向代码层面的逻辑缺陷,不涉及 UI/UX 视觉设计层面的评审。