ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Rocket.Chat 消息回应列表真实姓名修复:broadcast 广播管线对 reactions 显示名的批量补齐机制解析

Rocket.Chat 消息回应列表真实姓名修复:broadcast 广播管线对 reactions 显示名的批量补齐机制解析 Rocket.Chat 消息回应列表真实姓名修复broadcast 广播管线对 reactions 显示名的批量补齐机制解析【免费下载链接】Rocket.ChatThe Secure CommsOS™ for mission-critical operations项目地址: https://gitcode.com/GitHub_Trending/ro/Rocket.Chat本文围绕.changeset/wet-turtles-fix.md这一变更记录讲解 Rocket.Chatrocket.chat/meteor包在开启UI_Use_Real_Name后消息回应列表Reaction List Modal曾出现“移动端空白条目、Web 端显示用户名”而非真实姓名的缺陷以及官方通过在消息广播管线watch.messages中按用户名批量查询并补齐reactions.names的修复方案。读完本文你将掌握该缺陷的完整调用链、服务端富集实现与客户端渲染逻辑并能基于单元测试用例快速定位和验证同类问题。一、变更背景与影响面该变更记录位于仓库根目录的 .changeset/wet-turtles-fix.md内容属于 Changesets 发布体系中的一条patch补丁级变更声明--- rocket.chat/meteor: patch ---它声明的两个关键事实是缺陷表现当管理端开启UI_Use_Real_Name界面使用真实姓名后点击消息上的某个 Emoji 反应所弹出的“反应列表弹窗”标题为Users_reacted中移动端出现空白条目Web 端则退而显示用户名而不是用户配置的真实姓名Real Name。修复手段消息**广播管线broadcast pipeline在向客户端分发消息前通过批量查询batch query**为所有 reactions 补齐其对应的展示名display names写入reaction.names数组。由于这是rocket.chat/meteor包的 patch 修复它会随下一次 patch 版本发布生效无需用户手动迁移数据或改动配置仅依赖服务端广播逻辑在运行时补齐字段。二、数据模型与配置基础为什么需要names字段2.1 reactions 在消息文档中的存储结构在 Rocket.Chat 中消息的reactions字段是一个以“表情符号”为键、以“反应参与者”为值的对象。以服务端单元测试 notifyListener.spec.ts 中的构造样例为例reactions: { :smile:: { usernames: [user1, user2] }, :heart:: { usernames: [user1, user3] }, },也就是说消息原始数据中每种反应只持久化参与者的username列表并不天然携带每个用户当前的“真实姓名”name。这是问题产生的数据层根因name属于用户档案属性随时可能变更不可能冗余同步写入每一条历史消息。因此需要在“面向用户展示”之前的某个环节把username对应到name并写入reaction.names数组与usernames按下标一一对应。2.2UI_Use_Real_Name设置项该开关由服务端设置定义位于 server/settings/layout.tsawait this.add(UI_Use_Real_Name, false, { type: boolean, public: true, });默认值false即默认各界面展示用户名。typeboolean。publictrue客户端可通过useSetting订阅读取。该设置会被客户端在渲染姓名时读取见下文客户端逻辑也会被服务端在广播消息前判断是否需要做姓名富集。三、缺陷现象与根因分析3.1 反应列表弹窗的渲染路径弹窗组件为 ReactionListModal.tsxexport type ReactionListModalProps { reactions: RequiredIMessage[reactions]; onClose: () void; }; const ReactionListModal ({ reactions, onClose }: ReactionListModalProps) { const { t } useTranslation(); return ( GenericModal variantinfo title{t(Users_reacted)} onClose{onClose} onConfirm{onClose} confirmText{t(Close)} Reactions reactions{reactions} / /GenericModal ); };弹窗本体不负责姓名字段补齐它把reactions原样交给子组件 Reactions.tsx 逐条渲染const useRealName useSetting(UI_Use_Real_Name); // ... {Object.entries(reactions).map(([reaction, { names [], usernames }]) ( // ... {usernames.map((username, i: number) ( ReactionUserTag key{username} displayName{useRealName ? names[i] || username : username} / ))} ))}这里揭示了两条关键渲染逻辑只有UI_Use_Real_Name为真时才期望显示真实姓名真实姓名完全依赖names[i]与usernames[i]下标对齐。当names数组缺失或为空时Web 端组件因为有|| username的回退逻辑尚能退化为显示用户名——这正是缺陷描述中“Web 显示 usernames”的直接原因而移动端Rocket.Chat RN 客户端位于独立仓库对该字段缺失的容错更严格names[i]取到undefined后即渲染为空白条目。3.2 根因定位广播出来的消息未携带补齐后的namesRocket.Chat 的消息订阅分发走DDP 流watch.messages消息变更新增/编辑/加反应等由服务端广播给订阅者。若服务端广播出去的消息对象里reaction.names根本没有被填充那么无论 Web 还是移动端弹窗拿到的都是“半成品”数据展示真实姓名自然无从谈起。历史背景此前服务端存在另一条“按需补齐”路径详见下文第四节对照但广播路径并未对 reactions 做同等的姓名富集于是形成了本缺陷。四、修复方案广播管线中的批量姓名补齐修复集中在服务端消息广播前的“净化/富集”函数getMessageToBroadcast位于 server/lib/notifyListener.ts。4.1 触发链路广播函数的调用关系如下同文件export const notifyOnMessageChange async ({ id, data }: { id: IMessage[_id]; data?: IMessage }): Promisevoid { const message await getMessageToBroadcast({ id, data }); if (!message) { return; } void api.broadcast(watch.messages, { message }); };即所有消息变化通知都会先经过getMessageToBroadcast预处理再通过api.broadcast(watch.messages, ...)下发。该函数此前已经负责三件事均受UI_Use_Real_Name门控为message.u发送者补齐name为message.mentions中的每个提及对象补齐name本次新增为message.reactions的每个反应补齐names。4.2 核心实现逐段拆解函数定义见 notifyListener.ts 的 getMessageToBroadcast先读取设置并门控const useRealName (await getSettingCached(UI_Use_Real_Name)) true;设置读取与姓名查询均做了内存级记忆化缓存memoizeconst getUserNameCached mem( async (userId: string): Promisestring | undefined { const user await Users.findOnePickIUser, name(userId, { projection: { name: 1 } }); return user?.name; }, { maxAge: 10000 }, ); const getSettingCached mem(async (setting: string): PromiseSettingValue Settings.getValueById(setting), { maxAge: 10000 }); const getUsersByUsernamesCached mem( async (usernames: string[]): PromiseMapstring, string | undefined { const users await Users.findByUsernames(usernames, { projection: { username: 1, name: 1 } }).toArray(); return new Map(users.filter((u): u is IUser { username: string } !!u.username).map((u) [u.username, u.name])); }, { maxAge: 10000, cacheKey: ([usernames]) JSON.stringify([...usernames].sort()) }, );这些缓存的有效期均为10000ms10 秒getUsersByUsernamesCached的缓存键是“排序后的用户名数组 JSON”保证同一批用户不会重复查询数据库且不受传入顺序影响。本次修复新增的 reactions 补齐逻辑如下if (message.reactions) { const allUsernames [...new Set(Object.values(message.reactions).flatMap((r) r.usernames))]; if (allUsernames.length 0) { const nameByUsername await getUsersByUsernamesCached(allUsernames); for (const reaction of Object.values(message.reactions)) { reaction.names reaction.usernames.map((username) nameByUsername.get(username) || username); } } }其工程要点值得单独强调步骤实现方式意义收集flatMap取出全部反应的全部usernames再用Set去重同一用户可能对多个表情都点了反应避免重复查询查询通过getUsersByUsernamesCached一次性批量Users.findByUsernames投影{ username: 1, name: 1 }把“每个用户一条 findOne”的 N 次查询压成 1 次兼顾缓存命中写入对每个 reactionusernames.map(u nameByUsername.get(u) || u)生成names下标与usernames严格对齐缺失时回退为用户名保证 Web 端不会出现空值如果某消息没有任何反应reactions为空对象或不存在则直接跳过查询避免无效 DB 开销——这一点也被单元测试显式覆盖见第六节。4.3 效果为什么能同时修复 Web 与移动端修复后经由广播管线下发的每条带反应消息都包含与usernames对齐的names。Web 端names[i] || username会优先取到真实姓名移动端因广播数据已完整同样能渲染出真实姓名而非空白。由于补全发生在数据源端服务端广播前所有消费watch.messages流的客户端Web、移动、桌面都受益无需各自单独处理。五、客户端渲染链路与展示组件客户端侧rocket.chat/meteor的 Web 端相关组件结构如下便于理解该字段的最终消费方式ReactionListModal/index.ts弹窗入口。Reactions.tsx决定每条反应下展示的“人名”取自names真实姓名模式还是username默认模式。ReactionUserTag.tsx将单个姓名渲染为 FuselageTag标签。需要区分的是消息气泡本身上展示的反应计数与悬浮提示走的是另一套组件 components/message/content/Reactions.tsx它展示的是username形式的提示在展示前会过滤掉当前用户自己与“反应列表弹窗”按真实姓名排序展示的场景不同二者是独立的渲染路径。本次修复针对的是点击反应计数后弹出的Users_reacted弹窗即ReactionListModal这一路径。该弹窗的触发入口可参考 useShowMessageReactionsAction.tsx。六、同类机制的横向对照在服务端另一条数据读取路径中存在与本次修复几乎相同逻辑的独立实现值得对照阅读server/lib/utils/lib/normalizeMessagesForUser.ts 会在把消息返回给用户前对u、starred、mentions、reactions统一做姓名补齐其 reactions 部分同样采用“先收集用户名→批量查name→按下标映射写names”的模式message.reactions Object.fromEntries( Object.entries(message.reactions).map(([keys, reaction]) { reaction.names reaction.usernames.map((username) getNameOfUsername(names, username)); return [keys, reaction]; }), );该文件头部注释还特别指出“we should let clients get user names on demand instead of doing this”未来更理想的做法是让客户端按需获取用户名说明服务端预补齐是一种在既有架构下的务实取舍。由此可以推断本次 broadcast 管线的修复本质上是把原先只在 DB 直读/普通消息查询路径存在的“姓名预补齐”补全到流式广播路径上从而消除了两条数据路径的不一致。七、测试验证如何用单测锁定该行为修复配套的单元测试位于 apps/meteor/tests/unit/server/lib/notifyListener.spec.ts对getMessageToBroadcast的行为覆盖得非常具体用例 1开启真实姓名时reactions 被补齐 names。输入:smile:由user1/user2触发:heart:由user1/user3触发。期望输出reactions: { :smile:: { usernames: [user1, user2], names: [Real User, Name for user2] }, :heart:: { usernames: [user1, user3], names: [Real User, Name for user3] }, },注意user1同时出现在两个反应中期望结果证明去重后的单次批量查询能满足多个反应的映射需要。用例 2开启真实姓名但消息没有反应reactions: {}时不触发任何用户查询。测试断言相关行如下if (useRealName message.reactions Object.keys(message.reactions).length) { expect(usersFindByUsernamesStub.calledOnce).to.be.true; expect(usersFindByUsernamesStub.calledWith(deduplicated, { projection: { username: 1, name: 1 } })).to.be.true; } if (useRealName message.reactions !Object.keys(message.reactions).length) { expect(usersFindByUsernamesStub.called).to.be.false; }即验证了批量查询方法findByUsernames恰好被调用一次且投影只取username与name两个字段而空反应消息不会产生多余的 DB 查询。调试建议修改notifyListener.ts中 reactions 补齐逻辑后运行apps/meteor包内对应的 Jest 单元测试文件例如jest选中notifyListener.spec.ts即可回归验证上述行为若删除names写入逻辑用例 1 会立即失败模拟出本文描述的线上缺陷。八、回归路径小结与维护要点综合全文本修复把“展示真实姓名”这一用户体验目标落实到了正确的数据管道位置其核心链路可以浓缩为消息变更 └─ notifyOnMessageChange() └─ getMessageToBroadcast() ← 本次修复点 ├─ getSettingCached(UI_Use_Real_Name) ├─ 补齐 message.u.name ├─ 补齐 mentions[].name └─ 补齐 reactions[].names ← 批量 findByUsernames 缓存 └─ api.broadcast(watch.messages, { message }) └─ 客户端 ReactionListModalWeb/移动端一致展示真实姓名给后续维护者的几点提示names是运行时派生的展示字段不持久化进消息文档任何“绕过getMessageToBroadcast/normalizeMessagesForUser直接产出消息数据”的新通道如新的 stream、新的 API 直读返回都需要考虑是否同样补齐否则此类缺陷容易在新入口复现。数组对齐是隐性契约names[i]必须与usernames[i]一一对应任何排序或过滤操作都可能破坏对应关系应复用现有的“先整体去重批量查、再按下标 map”模式。缓存一致性姓名查询带 10 秒记忆化缓存用户改名后广播数据可能短暂保留旧名这是缓存窗口内的可接受行为如需即时生效需评估缓存策略。通过本文你不仅理解了这一条 patch 的来龙去脉也掌握了 Rocket.Chat 消息流中“存储用户名、展示前富集姓名”这一通用范式可将其迁移到其他需要“列表内联展示用户信息”的功能场景中。【免费下载链接】Rocket.ChatThe Secure CommsOS™ for mission-critical operations项目地址: https://gitcode.com/GitHub_Trending/ro/Rocket.Chat创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表