主题
敏感词过滤系统
概述
WasiCore 提供服务端敏感词过滤 API,用于在玩家文本被广播、展示、保存或跨服转发前执行内容过滤。开发聊天、私聊、世界频道、昵称、公会名、房间名、留言或其他用户生成文本时,应把敏感词过滤作为服务端输入处理流程的一部分,以降低内容合规风险。
敏感词过滤不是完整的内容安全或反滥用系统。项目仍需按业务处理身份校验、长度限制、频率限制、权限、举报、禁言和审计策略。
API
该 API 仅在服务端可用:
csharp
#if SERVER
SensitiveWordFilterResult result = Game.FilterSensitiveWords(text);
#endifSensitiveWordFilterResult 包含:
| 属性 | 含义 |
|---|---|
WasFiltered | 是否至少命中并替换了一处敏感词 |
FilteredText | 过滤后的文本;未命中时与输入文本等价 |
WasFiltered 不是错误码,也不表示远端服务状态。当前 API 不返回初始化或可用性错误;它只表达本次文本是否发生替换。
传入 null 会抛出 ArgumentNullException。空字符串可以调用,结果为未发生替换。
推荐处理流程
玩家文本必须以服务端为信任边界:
- 客户端提交原始文本。
- 服务端验证发送者、权限、禁言状态、发送频率和文本长度。
- 服务端调用
Game.FilterSensitiveWords。 - 项目按产品策略替换文本或拒绝提交。
- 只把过滤后的文本用于广播、持久化、排行榜、邮件或跨服消息。
- 客户端显示服务端确认后的文本,不显示本地未经确认的原文。
替换后继续发送
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 });