Skip to content

敏感词过滤系统 ​

概述 ​

WasiCore 提供服务端敏感词过滤 API,用于在玩家文本被广播、展示、保存或跨服转发前执行内容过滤。开发聊天、私聊、世界频道、昵称、公会名、房间名、留言或其他用户生成文本时,应把敏感词过滤作为服务端输入处理流程的一部分,以降低内容合规风险。

敏感词过滤不是完整的内容安全或反滥用系统。项目仍需按业务处理身份校验、长度限制、频率限制、权限、举报、禁言和审计策略。

API ​

该 API 仅在服务端可用:

csharp
#if SERVER
SensitiveWordFilterResult result = Game.FilterSensitiveWords(text);
#endif

SensitiveWordFilterResult 包含:

属性含义
WasFiltered是否至少命中并替换了一处敏感词
FilteredText过滤后的文本;未命中时与输入文本等价

WasFiltered 不是错误码,也不表示远端服务状态。当前 API 不返回初始化或可用性错误;它只表达本次文本是否发生替换。

传入 null 会抛出 ArgumentNullException。空字符串可以调用,结果为未发生替换。

推荐处理流程 ​

玩家文本必须以服务端为信任边界:

  1. 客户端提交原始文本。
  2. 服务端验证发送者、权限、禁言状态、发送频率和文本长度。
  3. 服务端调用 Game.FilterSensitiveWords。
  4. 项目按产品策略替换文本或拒绝提交。
  5. 只把过滤后的文本用于广播、持久化、排行榜、邮件或跨服消息。
  6. 客户端显示服务端确认后的文本,不显示本地未经确认的原文。

替换后继续发送 ​

csharp
#if SERVER
public static string PrepareChatText(string input)
{
    ArgumentException.ThrowIfNullOrWhiteSpace(input);

    if (input.Length > 200)
    {
        throw new ArgumentOutOfRangeException(nameof(input), "聊天内容不能超过 200 个字符");
    }

    return Game.FilterSensitiveWords(input).FilteredText;
}
#endif

命中后拒绝提交 ​

csharp
#if SERVER
public static bool TryAcceptPlayerName(string input, out string acceptedName)
{
    var result = Game.FilterSensitiveWords(input);
    if (result.WasFiltered)
    {
        acceptedName = string.Empty;
        return false;
    }

    acceptedName = result.FilteredText;
    return true;
}
#endif

昵称、公会名等身份文本通常更适合拒绝策略;聊天正文可以按产品需求选择替换或拒绝。

与消息系统的关系 ​

DualEndEvent、TypedMessage、ProtoCustomMessage 和 ServerChannel 都是消息传输机制,不负责理解载荷中的业务语义,也不会自动过滤玩家文本。

无论使用哪种传输方式,都应在服务端收到玩家输入后、向其他玩家分发前显式调用敏感词过滤 API。

ServerChannel 的准确边界 ​

ServerChannel 是运行相同游戏的不同服务端实例之间的发布/订阅消息传输机制。代码中的 channel 表示消息主题或路由标识,例如 world-events、auction-updates 或 user:{id}:notification。它不是专门面向玩家的聊天频道,也不会因为主题名包含 chat 就启用内容审核。

ServerChannel 载荷可以是复杂 JSON 对象或任意字节数据。传输层不会遍历对象字段、识别哪些字符串来自玩家,也不会修改载荷。因此:

  • 纯逻辑数据,例如跨服组队邀请、拍卖状态、排行榜更新,不需要为了传输本身调用敏感词过滤。
  • 载荷中只要包含将展示给玩家的用户生成文本,就必须由业务代码显式过滤。
  • 跨服聊天应优先在来源服务器过滤,再发送 FilteredText。
  • 如果接收端可能收到旧版本服务器或其他未受信任来源的数据,可在展示前再次过滤。
csharp
#if SERVER
public static bool SendCrossServerChat(
    long senderUserId,
    long targetUserId,
    string input)
{
    var result = Game.FilterSensitiveWords(input);

    var message = new CrossServerChatMessage
    {
        SenderUserId = senderUserId,
        Content = result.FilteredText,
        Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
    };

    return ServerChannelMessage.PublishToUser(
        targetUserId,
        "private-message",
        message);
}
#endif

日志与持久化 ​

  • 普通运行日志优先记录发送者 ID、消息 ID、长度和 WasFiltered,不要直接记录过滤前全文。
  • CloudData、排行榜、邮件、自定义数据库和 ServerChannel 不会自动过滤文本。
  • 如果业务需要保留原文用于受控审计,应由项目单独设计权限、加密、保留期限和访问记录;不要把原文混入普通日志。
  • 过滤后的文本仍可能需要长度、格式、字符集和业务规则校验。

常见错误 ​

只在客户端过滤 ​

客户端逻辑可以被绕过。客户端过滤只能改善输入体验,不能替代服务端过滤。

把 WasFiltered 当成调用成功状态 ​

false 表示本次没有替换,不表示服务不可用。不要把它包装成 CmdResult 错误。

以为 ServerChannel 会过滤 chat 主题 ​

主题名只是路由信息。ServerChannel 不识别玩家聊天,也不检查消息对象里的 Message、Content 或其他字符串字段。

过滤后仍发送原变量 ​

csharp
var result = Game.FilterSensitiveWords(input);

// 错误:仍然发送 input
ServerChannelMessage.Publish("global-message", new { Content = input });

// 正确:发送过滤结果
ServerChannelMessage.Publish("global-message", new { Content = result.FilteredText });

相关文档 ​