笔记

聊天 @提及怎样保存身份和位置?

群聊输入 @ 后选择成员,页面会插入名字并高亮。但如果两人都叫张三,或者成员以后改名,单独保存一段 @张三 就无法确认提到的是谁。

这个 Flutter App 把身份与显示文字一起保存,接收端还需要知道名字在正文中的区间。问题因此分成两部分:关联正确的人,以及在正确位置展示提及。

只传名字不够,通常还要传什么?

可以把提及作为独立的结构化片段保存,也可以在原始文本中加入身份标记,再生成展示文本。项目采用后一种约定,示意如下:

snippet
text
原始:请 @[张三](user-a@example.com) 看一下
展示:请 @张三 看一下

保存结果包含原始文本、展示文本和提及列表。每个提及记录用户的 XMPP 地址标识 JID、这次显示的名字,以及展示文本中的起止位置。重名用户的身份仍然不同,旧消息是否跟随资料改名则需要产品另定规则。

解析使用的正则是:

snippet
dart
final mentionRegex = RegExp(r'@\[([^\]]+)\]\(([^)]+)\)');

它只处理这套格式,名字中的右方括号、身份中的右括号与转义仍需约定,不能当成通用 Markdown 解析器。输入端允许的字符与序列化规则要一致。

第一处名字缩短了,第二处位置怎么计算?

原始标记比展示文字长,前面替换一次,后面的位置就会前移。例如:

snippet
text
请 @[张三](a@example.com) 和 @[李四](b@example.com) 确认

项目记录前面累计减少的长度,用原始匹配位置减去这个偏移,再按展示名长度计算结束边界。计算关系可以写成:

snippet
text
开始位置 = 原始匹配位置 - 已累计减少的长度
结束位置 = 开始位置 + 显示名长度 + 1
本次减少 = 原始标记长度 - 显示名长度 - 1

两个 1 都对应 @。最终展示“请 @张三 和 @李四 确认”,提及区间为 [2, 5) 与 [8, 11),按左闭右开截取。原始匹配位置与最终区间属于两份文本,后面不能混着用。

前面加一个 emoji,为什么位置又错了?

程序索引的单位未必等于人眼看到的字符数。Dart 的 String.length 官方文档 使用 UTF-16 code unit,😀 占两个单位。

因此 😀 @张三 中的提及从 3 开始、到 6 结束,不能按一个表情加一个空格算成 2。跨端传区间时,索引单位要统一,展示文本也必须相同。

一端如果替换换行、规范化字符或更新昵称,另一端继续拿旧区间截取,单位即使一致也会错。位置是在某份具体文本上的坐标,不是脱离正文仍然有效的身份。

验证可以把 emoji、连续提及和重名样例走完输入、序列化、协议转换与接收,再根据区间截出名字核对。一个普通的单次 @张三,很难覆盖这些偏移和边界问题。

接收时还需要验证什么?

项目 XML 接收会提取身份、名字和区间,数字转换失败回退零,身份非空就收集。起止顺序、越界、重叠和截取内容与显示名是否一致,尚未完整校验。

编辑辅助中的区间比较还有另一套边界,可能描述光标落点,需要按调用用途核对,不能直接与截取子串的左闭右开统一。App 输入模型又用 nickname 表示名字,协议模型使用 displayName,发送前需要经过专门转换;绕过它直接传 JSON,显示名可能丢失。

返回上一页