宇连通 Im 的 im-wasm 最近完成了一轮面向多端通信场景的更新。本次变化集中在两个高频问题:如何更快、更稳定地过滤敏感词,以及如何让 Java、Flutter、鸿蒙等客户端使用统一的文件上传能力。
从摘要披露的信息看,新版本已经将部分核心能力下沉到 Rust,并通过 WASM 或跨端封装同步到不同平台。这样做的价值不只是减少重复实现,也有助于让不同客户端在规则、数据格式和传输行为上保持一致。
用字典树优化敏感词过滤
旧式敏感词过滤通常会遍历关键词列表,或者对每个关键词调用字符串查找。当关键词数量增加时,重复扫描会带来明显开销,尤其是在聊天消息、群公告和评论等需要实时处理的场景中。
新版本采用自实现字典树过滤关键词。字典树按照字符逐层组织关键词,扫描文本时可以从每个起点向后匹配,避免对所有关键词逐个比较。敏感词库被同步到 Java、鸿蒙和 Flutter 多端后,各端可以共享相同的过滤思路和规则来源。
下面是一个可直接运行的 Python 最小实现,用于理解这种过滤方式。生产环境还需要根据业务补充大小写、全角半角、Unicode 归一化、重复命中和替换策略。
class TrieNode:
def __init__(self):
self.children = {}
self.is_word = False
class SensitiveWordFilter:
def __init__(self, words):
self.root = TrieNode()
for word in words:
self.add(word)
def add(self, word):
node = self.root
for char in word:
node = node.children.setdefault(char, TrieNode())
node.is_word = True
def replace(self, text, replacement="*"):
chars = list(text)
i = 0
while i < len(chars):
node = self.root
j = i
matched_end = None
while j < len(chars) and chars[j] in node.children:
node = node.children[chars[j]]
j += 1
if node.is_word:
matched_end = j
if matched_end is not None:
for k in range(i, matched_end):
chars[k] = replacement
i = matched_end
else:
i += 1
return "".join(chars)
if __name__ == "__main__":
words = ["违规词", "测试敏感词", "badword"]
word_filter = SensitiveWordFilter(words)
message = "这是一条测试敏感词消息,也包含 badword。"
print(word_filter.replace(message))
将这类逻辑放到 Rust 中,再编译为 WASM,可以为多端提供相对稳定的执行性能。不过,词库更新策略同样重要:词库版本、发布时间、灰度范围和回滚方式都应该纳入客户端协议,而不是只依赖应用发版。
跨端统一文件上传链路
本次更新还对 Java、Flutter 和鸿蒙端的文件上传能力进行了统一。摘要显示,im-wasm 使用 Rust 实现了 StupidBear 高性能数据序列化,用于多端之间的数据传递和协议统一。
统一序列化层通常需要明确以下内容:
- 文件元数据如何表示,例如名称、大小、MIME 类型和校验值。
- 文件内容如何切片,以及每个分片如何编号。
- 上传进度、暂停、重试和取消如何反馈给调用方。
- 客户端与服务端如何判断分片是否已经成功接收。
- 序列化格式升级时,旧客户端是否仍然可以通信。
如果实际协议尚未对外公开,可以先用一个清晰的消息模型约束各端实现。下面的 JSON 仅作为协议设计示例,字段名称和编码方式需要根据 im-wasm 的实际接口调整:
{
"type": "file_chunk",
"upload_id": "u_20250308_001",
"file_name": "report.pdf",
"content_type": "application/pdf",
"total_size": 10485760,
"chunk_index": 3,
"chunk_size": 1048576,
"sha256": "replace-with-real-sha256",
"payload": "binary-data-or-encoded-data"
}
实际传输时,不建议把大文件内容直接编码成 JSON 字符串。可以让 Rust 序列化层负责固定长度字段、元数据和分片头,文件内容则使用二进制帧传输,从而减少 Base64 带来的额外体积。客户端还应在发送前计算分片校验值,服务端确认后再推进进度。
WebSocket 文件接收能力
文件上传不再只依赖传统 HTTP 请求,WebSocket 上传文件并新增服务端接收文件的能力,可以适配需要长连接的即时通信场景。典型流程可以设计为:
- 客户端发送上传初始化消息,包含文件大小、分片大小和文件摘要。
- 服务端返回
upload_id以及当前已接收的分片列表。 - 客户端通过 WebSocket 发送二进制分片,并携带对应的分片序号。
- 服务端校验分片后返回确认消息。
- 客户端在断线重连后查询缺失分片,只补传未确认的数据。
- 所有分片校验通过后,服务端合并文件并返回最终状态。
下面是一个基于 Python websockets 的服务端接收示例。它是一个可改造的协议样例,不代表 im-wasm 的最终接口。运行前安装依赖:pip install websockets。
import asyncio
import json
from pathlib import Path
import websockets
UPLOAD_DIR = Path("uploads")
UPLOAD_DIR.mkdir(exist_ok=True)
async def receive_file(websocket):
metadata = json.loads(await websocket.recv())
upload_id = metadata["upload_id"]
total_chunks = metadata["total_chunks"]
target = UPLOAD_DIR / f"{upload_id}.part"
with target.open("wb") as output:
for expected_index in range(total_chunks):
header = json.loads(await websocket.recv())
if header["chunk_index"] != expected_index:
raise ValueError("unexpected chunk index")
payload = await websocket.recv()
if not isinstance(payload, bytes):
raise ValueError("chunk payload must be binary")
output.write(payload)
await websocket.send(json.dumps({
"type": "chunk_ack",
"chunk_index": expected_index,
}))
await websocket.send(json.dumps({
"type": "upload_complete",
"upload_id": upload_id,
"path": str(target),
}))
async def main():
async with websockets.serve(lambda ws: receive_file(ws), "127.0.0.1", 8765):
await asyncio.Future()
if __name__ == "__main__":
asyncio.run(main())
生产环境不能直接照搬这个示例。至少需要增加身份认证、上传大小限制、路径安全检查、磁盘配额、超时控制、断线恢复、分片摘要校验和临时文件清理。对于即时通信系统,还要限制单个连接并发上传数量,避免大文件占用事件循环或内存资源。
落地时关注版本兼容
这类跨端更新的难点通常不在单个算法,而在多个运行时同时升级。建议将以下信息显式放进构建和发布流程:
- Rust 核心库版本与 WASM 构建版本。
- 敏感词词库版本和词库摘要。
- 序列化协议版本。
- 文件上传能力版本及服务端最低兼容版本。
- Java、Flutter、鸿蒙 SDK 的发布版本。
如果客户端与服务端升级不同步,协议版本字段可以帮助服务端选择兼容解析器。文件上传尤其需要考虑中断恢复:服务端必须能够识别已经写入的分片,客户端也必须能在重连后重新获取状态,而不是从头发送整个文件。
采用建议
对于准备接入新版本的团队,可以按以下顺序验证:
- 先用固定词库测试字典树过滤的命中结果,确认多端替换规则一致。
- 使用小文件验证 HTTP 和 WebSocket 两条链路,再测试大文件、弱网和断线重连。
- 对序列化数据做跨语言回归测试,确保 Java、Flutter、鸿蒙端能够互相解析。
- 记录分片确认、重试、合并和清理日志,便于定位线上上传失败。
- 在灰度阶段保留旧协议解析能力,确认客户端升级完成后再收缩兼容范围。
im-wasm 这次更新的核心方向,是把敏感词处理和文件传输从各端重复实现,逐步收敛到可复用的 Rust/WASM 能力。接入时应同时评估性能收益、协议兼容、词库更新和文件安全边界,才能让跨端统一真正转化为维护成本的下降。