im-wasm 新版本:敏感词过滤、跨端文件上传与 WebSocket 服务端支持

2026-08-05 44 预计阅读时间: 1 分钟
来源: oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

宇连通 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 上传文件并新增服务端接收文件的能力,可以适配需要长连接的即时通信场景。典型流程可以设计为:

  1. 客户端发送上传初始化消息,包含文件大小、分片大小和文件摘要。
  2. 服务端返回 upload_id 以及当前已接收的分片列表。
  3. 客户端通过 WebSocket 发送二进制分片,并携带对应的分片序号。
  4. 服务端校验分片后返回确认消息。
  5. 客户端在断线重连后查询缺失分片,只补传未确认的数据。
  6. 所有分片校验通过后,服务端合并文件并返回最终状态。

下面是一个基于 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 能力。接入时应同时评估性能收益、协议兼容、词库更新和文件安全边界,才能让跨端统一真正转化为维护成本的下降。


相关推荐