敏感词导出和上报(UGC)
分析文本导出与敏感词上报
分析文本导出对 TcaplusDB 的离线快照执行 SQL,将所需字段导出为文本。在 OMS「数据处理 → 分析文本导出」中,可以配置以下两类任务:
| 需求 | 选择的输出类型 | 发送目的地 | 配置与示例 |
|---|---|---|---|
| 导出数据供业务离线分析、下游处理 | 分析型文本 | SCP 目标机器或 COS | 分析文本导出 |
| 导出昵称、签名、公告等,供安全侧分析敏感词 | 敏感词文本 | UGC 系统(业务安全侧) | 敏感词导出和上报 |
只需要敏感词上报时,可直接阅读第 3 节,不需要先配置普通分析任务。 两类任务都通过 SQL 提取数据,区别主要在输出约定、文件命名和目的地。普通分析文本在 SQL 结果前增加表名、AppID、ZoneID 三列;敏感词文本直接使用双方约定的 SQL 结果列。TcaplusDB 负责导出和发送,敏感词识别及分析由业务安全侧完成。
本文先介绍共用概念,再分别说明两类任务。示例中的业务、数据和接收地址均为虚构。
1. 概念与基本原理
基本概念
数据所属的业务用 AppID 标识,业务下的区或表格组用 ZoneID 标识,表所属的集群用 SetID 标识。不同区可以有同名表,例如 App 100 在 Zone 1、2 下都有 PlayerInfo。
| 概念 | 含义 |
|---|---|
| 源表 | 从业务角度由 AppID 和表名确定的表;具体到一个区的表,还需要 ZoneID |
| 导出计划 | 为一张源表选择同一 Set 内一个或多个 Zone,统一设置数据快照、写入接口和每日启动时间 |
| 子任务 | 对计划选定的数据执行一份 SQL,独立配置编码、输出类型和发送目的地 |
| 数据快照 | 某个时刻的数据状态,可以是冷备时间点,也可以由备份和日志构造到指定时间点;与任务实际启动时间不同 |
同一计划的多个子任务共享所选数据,可类比针对同一份数据定义不同的查询视图:一份导出玩家资料,另一份提取昵称。各子任务独立执行,不传递中间结果,也不修改源表。
多区配置会分别提取各区同名表的数据,不会跨区关联记录或自动合成一个文件。所选表应能使用同一份 SQL;跨 Set、不同源表,或需要不同快照与启动时间时,分别建立计划。
一次导出如何完成
系统先准备计划指定的离线快照,再按各子任务的 SQL 筛选记录、提取和转换字段,最后生成文本文件并发送到目的地。数据通常按区、按分片形成多个文件;分片是表的数据分布单元。
SQL 中的 SELECT 决定输出哪些列,WHERE 决定保留哪些记录。它们处理的是这份快照,不能读取快照之后才写入的数据。具体的数据时间选择见第 5 节。
更多 SQL 能力
两类任务共用《离线导出 SQL 说明》中的语法。 本文只给出常见场景示例;编写业务 SQL 时,可按需求查阅:
- 嵌套字段与字段路径:TDR/PB 多层嵌套、map、数组下标、PB tag 及 bytes 内嵌 PB。
- 条件过滤:比较、AND/OR/NOT、LIKE、IN、CONTAINS 等 WHERE 条件。
- 函数与转换、CASE WHEN:类型转换、字符串处理、计算及条件映射。
- 数组展开:将 repeated/map 元素展开为多行,并保留所属记录的其他字段。
2. 分析文本导出
这一类任务用于业务离线分析,输出类型选择「分析型文本」,通过 SCP 或 COS 发送文件。本节以 SCP 为例;COS 配置见第 6 节。
源表和样例数据
假设业务已有下面这张 TDR 表,使用 SetData 写入。实际配置导出时,使用本业务已有表的定义即可,无需为导出重新建表。
<?xml version="1.0" encoding="UTF-8"?>
<metalib name="export_demo" tagsetversion="1" version="1">
<struct name="PlayerInfo" version="1"
primarykey="uin" splittablekey="uin">
<entry name="uin" type="uint64" notnull="true" />
<entry name="openid" type="string" size="128" />
<entry name="name" type="string" size="64" />
<entry name="signature" type="string" size="256" />
<entry name="platid" type="uint32" />
<entry name="level" type="uint32" />
</struct>
</metalib>
Zone 1 的快照中有三条记录:
| uin | openid | name | signature | platid | level |
|---|---|---|---|---|---|
| 10001 | openid_demo_1 | 星河旅人 | 今天也一起冒险 | 1 | 20 |
| 10002 | openid_demo_2 | 晨风 | (空字符串) | 1 | 8 |
| 10003 | openid_demo_3 | 山川 | 寻找队友 | 2 | 35 |
目标是每天导出等级不低于 10 的玩家 ID、昵称和等级。
计划和子任务怎么填
在 OMS「数据处理 → 分析文本导出」中创建计划。此例使用以下配置:
| 配置项 | 示例值 |
|---|---|
| 项目、源表 | App 100,PlayerInfo |
| 集群、区 | Set 10,Zone 1、2 的同名表 |
| 计划名称 | daily_player_export,同一项目内不重复 |
| 导出模式、日启动任务时间 | 冷备时间点导出,05:00:00;假设所需冷备已完成 |
| 数据写入接口 | SetData,与本例业务实际写入方式一致 |
| 子任务名称 | profile,同一计划内不重复 |
| 输出文件类型、导出文件格式 | 分析型文本、BASE64 |
| 导出文件上传方式 | SCP 至目标机器 |
在 profile 子任务中填写:
SELECT uin, name, level
FROM PlayerInfo
WHERE level >= 10
SCP 目的地需要业务提供 IP、SSH/SCP 端口、账号、密码和目标目录。例如 IP 为 192.0.2.10、端口为 22、账号为 export_reader、目录为 /data/export/player,密码填该账号的实际凭据。目标机器需要可从导出环境访问,账号应能登录并写入该目录,磁盘空间应充足。
这里先选择 BASE64,因为 SQL 只提取标量字段,它们仍输出为普通文本;复杂字段的编码见第 4 节。数据写入接口则必须向业务开发人员确认:实际使用 SetKey&SetValue 的表要选择对应选项,不能照抄本例的 SetData。
填写后校验 SQL,保存并确认计划已启用,即可按计划调度;禁用可暂停后续调度。SQL 校验只检查语法和可检查的元数据,不扫描实际记录,也不验证目标端接收格式。 首次运行仍需核对输出。
导出结果是什么样
假设上述记录位于 Zone 1、分片 1001,本次文件日期标签为 20261022,目标目录会收到类似文件:
PlayerInfo_100_20261022_daily_player_export_profile.log.1.1001
其中的数据为:
PlayerInfo|100|1|10001|星河旅人|20
PlayerInfo|100|1|10003|山川|35
SQL 选了三列,文件实际有六列:普通分析文本在 SQL 结果前加上了 表名、AppID、ZoneID。等级为 8 的玩家被 WHERE 排除;Zone 2 的结果会出现在对应区、对应分片的文件中。
列之间用 | 分隔。字符串内的 | 和换行会替换为空格,例如昵称 星河|旅人 会输出为 星河 旅人。文件日期的含义及如何判断接收完整,见第 7 节。
如果只想多导出一列签名,直接把 SQL 改为:
SELECT uin, name, signature, level
FROM PlayerInfo
WHERE level >= 10
玩家 10001 对应的行就变成:
PlayerInfo|100|1|10001|星河旅人|今天也一起冒险|20
SELECT 列顺序就是输出顺序。建议明确列出字段;输出文件名由子任务配置决定,无需在 SQL 中写 INTO。
3. 敏感词导出和上报
这一类任务用于每日提取玩家昵称、签名、公会名称、公告等文本及关联标识,输出类型选择「敏感词文本」,上传方式选择「UGC 上报」。原先依赖后台人工配置的流程已由新版 OMS 配置流程替代。
可以新建只包含敏感词子任务的计划,也可以在已有计划中增加上报子任务。下面复用第 2 节的 PlayerInfo XML 定义和样例数据,为昵称和签名分别配置 SQL;普通分析子任务不是上报的前置条件。
接入前准备
接入时需要对齐两部分信息:
- 与业务开发确认数据来源:AppID、表名、SetID 和 ZoneID 列表、实际写入接口及字段结构,以及所需快照和每日执行时间。跨 Set 分别建立计划。
- 与安全侧确认接收约定:CCID、每种内容的列顺序、字段类型、固定值和完整文件名前缀。CCID 是安全侧的业务标识,与 TcaplusDB 的 AppID 分别填写。
本例计划为 daily_player_export,使用 App 100、Set 10、Zone 1、2 的 PlayerInfo,采用冷备模式、SetData 写入接口,在所需备份完成后于 05:00:00 启动。实际使用时替换为本业务的信息。
假设本例拿到 CCID 123,并约定每行依次为:
文本 | openid | platid | area_id(固定为 0) | 玩家 ID
这是本例的接收约定,不是所有业务通用的格式。昵称使用完整文件名前缀 123-nickname-0,签名使用 123-signature-0;末尾的 0 也只是本例约定的标识。
昵称子任务
新增子任务 nickname_report,选择「敏感词文本」「UGC 上报」,编码选择 BASE64;在 CCID 项填 123,在上报文件名前缀项填完整的 123-nickname-0。
SELECT name AS content,
STRING(openid) AS openid,
UINT32(platid) AS platid,
INT(0) AS area_id,
uin AS player_id
FROM PlayerInfo
WHERE LENGTH(name) > 0
这里 INT(0) 补齐了接收方要求的固定列;AS 为结果列命名,不会改变列顺序。以玩家 10001 为例,上报文件 123-nickname-0-20261022.txt.1001 中对应的行是:
星河旅人|openid_demo_1|1|0|10001
敏感词文本直接使用 SQL 结果,不自动添加表名、AppID 和 ZoneID。 如果接收方需要区标识,应在约定位置显式选择 __zone_id__ 或相应业务字段。
昵称子任务没有等级限制,所以等级为 8 的玩家也会被导出。这说明子任务各自查询同一份源数据,而不是继续处理 profile 筛选后的结果。
签名子任务
再增加 signature_report,上传方式、CCID 和列约定相同,将完整前缀改为 123-signature-0,SQL 改为提取非空签名:
SELECT signature AS content, STRING(openid) AS openid,
UINT32(platid) AS platid, INT(0) AS area_id, uin AS player_id
FROM PlayerInfo
WHERE LENGTH(signature) > 0
玩家 10001 在 123-signature-0-20261022.txt.1001 中对应的行是:
今天也一起冒险|openid_demo_1|1|0|10001
玩家 10002 的签名为空,因此不会出现在这个子任务的结果中。若同时保留第 2 节的 profile,同一计划就有三份独立结果:玩家资料发到业务机器,昵称和签名分别上报 UGC。
子任务名称用于计划内管理,上报文件名前缀用于 UGC 命名,两者可以不同。 前缀中不要再填日期、扩展名和分片编号,系统会追加这些部分。同一 CCID 下需要区分体验服时,可与安全侧另行约定 123-tiyan_nickname-0 这样的前缀。
昵称或公告位于深层字段时,可直接使用嵌套字段路径;需要筛选上报范围时,使用条件过滤。更多函数、数组展开和类型转换也见《离线导出 SQL 说明》。
首次运行应抽查输出,并由安全侧确认列格式、文件前缀和接收情况。OMS 的静态 SQL 校验不读取实际记录,也不核验安全侧的业务约定;文件已送达不表示安全侧已完成内容分析。
4. 示例:提取嵌套字段,或把数组展开成多行
文本在嵌套结构中时,SQL 可以直接定位到子字段。下面是另一张 PB 表的完整定义;它与 PlayerInfo 是不同源表,应使用单独的导出计划。
syntax = "proto3";
import "tcaplusservice.optionv1.proto";
message Profile {
string signature = 1;
}
message Pet {
uint64 gid = 1;
string name = 2;
}
message PlayerWithPets {
option(tcaplusservice.tcaplus_primary_key) = "uin";
uint64 uin = 1;
string openid = 2;
Profile profile = 3;
repeated Pet pets = 4;
}
假设玩家 10001 的 profile.signature 为「今天也一起冒险」,pets 包含 (gid=101, name=团子) 和 (gid=102, name=雪球) 两个元素。
只取签名,用点号访问子字段:
SELECT uin, profile.signature AS signature FROM PlayerWithPets
如果选择分析型文本,在 App 100、Zone 1 中得到:
PlayerWithPets|100|1|10001|今天也一起冒险
每只宠物输出一行,用 EXPAND_ARRAY 展开数组,并把玩家 ID 带到每一行:
SELECT uin,
EXPAND_ARRAY(pets, $.gid) AS pet_id,
EXPAND_ARRAY(pets, $.name) AS pet_name
FROM PlayerWithPets
输出为:
PlayerWithPets|100|1|10001|101|团子
PlayerWithPets|100|1|10001|102|雪球
两次展开使用同一个 pets 数组,因此宠物 ID 和名字按元素对应。一条源记录变成了两行结果;上报宠物名称等内容时,也可以按接收方约定组合这些字段。
保留整个复杂字段时,可以写 SELECT uin, profile FROM PlayerWithPets。这时子任务的 BASE64/JSON 选择才影响 profile 这一列:选择 JSON 时,该列的内容形如:
{"signature":"今天也一起冒险"}
选择 BASE64 时,需要先对该列解码,再按对应结构及版本解析二进制。两种方式都不改变整行以 | 分隔的约定,也不会把 uin 等标量列一起编码。选择 JSON 并不意味着整份导出文件是一个 JSON 文档。
TDR 嵌套结构也可通过点号访问。若 bytes/char 中内嵌的是一段 PB,需要相应结构定义才能按名称定位;也可根据实际 proto 的 tag、类型及必要的数据头偏移取值。字段路径、map、tag 访问等详细语法见《离线导出 SQL 说明》,不用为这些提取需求额外编写清洗脚本。
5. 按需求选择数据时间
前面的例子采用冷备时间点导出,适合大多数每日离线分析和上报。如果快照对应 01:00:00,任务在 05:00:00 执行,03:00:00 才写入的数据不会出现在该快照中。这里的快照时刻和任务启动时刻是两回事。
如果业务明确需要 02:00:00 的数据状态,则选择指定时间点导出,把构造时间设为 02:00:00。系统利用备份及所需日志构造快照;若目标时刻早于当天冷备完成时间,可能需要使用前一天的冷备。每日启动时间应安排在所需备份和日志可用之后,并为构造、导出和发送留出时间。启动时间不代表文件到达时间。
只导出近期修改的记录,则在 SQL 中增加条件,而不是改变启动时间。例如,以数据构造时间为基准,筛选前 24 小时有修改的记录:
SELECT uin, name FROM PlayerInfo
WHERE __last_access_time__ > __make_data_time__ - 86400
AND __last_access_time__ <= __make_data_time__
__make_data_time__ 是本次数据构造的时间基准,__run_task_time__ 是任务实际开始执行的时间,两者都以 Unix 秒表示。__last_access_time__ 表示记录最后修改时间,虽然名称含 access,但不表示最后一次读取时间。
这类查询筛选的是快照中现存的记录,不会输出每一次修改的历史,也不能获取已删除记录。它适合近期修改筛选,不能直接当作完整的增量同步;连续运行时的窗口与去重需按业务需求处理。
6. 分析文本的发送配置
上传 COS
如果下游从 COS 取数,只需将普通分析子任务的上传方式改为 COS。SQL、输出列及文件命名保持相同,目的地需要填写:
| 信息 | 填写方式 |
|---|---|
| Endpoint | 从业务 COS 配置获取存储桶对应的访问端点,按页面要求填写 |
| AccessKeyId、AccessKeySecret | 填写能够访问 Bucket 并写入目标路径的实际凭据 |
| Bucket | 填写目标存储桶名称 |
| 目标路径 | 例如 /exports/player,表示 COS 中的存放路径,不是本地目录 |
因此,第 2 节的 profile 可以直接发往 <Bucket名称> 的 /exports/player,下游从该位置读取同样格式的文本。凭据、存储桶和路径应使用业务实际配置,示例不包含可用凭据。
SCP 到多台机器
目标 IP 可以用英文逗号分隔,例如 192.0.2.10,192.0.2.11。文件会按分片分散发送到这些机器,不是每台都收到一份全量副本;下游需要汇集各机器上的结果。
无论单机还是多机,接收账号都需要登录和目录写权限。账号、密码或目标路径变更后,应同步更新子任务配置。
7. 接收文件时需要知道的约定
文件名和日期
数据通常按区、按分片导出,当前文件命名为:
分析型文本:{表名}_{AppID}_{日期}_{计划名称}_{子任务名称}.log.{ZoneID}.{分片ID}
敏感词文本:{上报文件名前缀}-{日期}.txt.{分片ID}
日期格式为 YYYYMMDD,当前按本次导出用于命名的冷备日期的前一天生成。例如冷备日期为 2026-10-23,对应前文的文件日期标签 20261022。这个标签不代表任务一定在当天运行,也不代表文件包含该自然日的所有变更;数据状态由导出模式决定,记录范围由 SQL 决定。
多区、多分片会产生多个文件,需要收集同一批次的全部相关文件。文件数、源记录数、输出行数各有含义,例如第 4 节中一条玩家记录会展开为两行宠物结果。
校验清单和完成标记
普通分析文本通过 SCP 或 COS 发送后,还会有按区、按子任务区分的配套文件。例如 profile 在 Zone 1 的结果包括:
PlayerInfo_100_20261022_daily_player_export_profile.log.1.1001
100_1_PlayerInfo_20261022_daily_player_export_profile.md5sum_list
100_1_PlayerInfo_20261022_daily_player_export_profile.finish
.md5sum_list 列出数据文件名及 MD5;.finish 是对应结果发送完成的标记,内容可以为空。下游应等待完成标记,再按清单核对文件。两类配套文件的共同前缀是 {AppID}_{ZoneID}_{表名}_{日期}_{计划名称}_{子任务名称}。
SCP 多目标模式下,每台收到数据的机器用自己的清单核对所分配的文件,最后汇集各机器、各区的结果。UGC 上报不使用这些配套文件,应结合任务执行结果和安全侧接收情况确认。
完成标记反映文件发送流程完成,不能代替对记录解析异常和业务内容的检查;计划已启用也不表示某次任务已经成功。
首次运行如何核对
拿到结果后,先抽查几条熟悉的记录:字段顺序、固定列、来源区、字符串内容是否符合预期。UGC 还需要安全侧确认列约定、前缀及接收情况,文件已送达不表示安全侧已完成分析。
如果没有收到文件,先查看执行结果,再检查目的地连接和权限;如果文件为空,核对快照范围和 WHERE 条件,空结果本身不一定是失败。如果内容乱码或字段解析异常,优先核对数据写入接口、结构版本及内嵌 PB 的 tag、类型和偏移。
该功能用于离线分析,可能延迟或失败,不宜作为在线请求的必经环节。需要协助排查时,提供项目、计划、子任务、区、运行日期和错误信息即可,不要附带密码或 COS 密钥。
相关文档
- 《离线导出 SQL 说明》:完整字段路径、函数、过滤和数组展开语法。
- 《数据备份》:备份数据来源。