diff --git a/docs/admin-adfahi-research/服务端消息加密与内容审核改造文档.md b/docs/admin-adfahi-research/服务端消息加密与内容审核改造文档.md new file mode 100644 index 0000000..db8f507 --- /dev/null +++ b/docs/admin-adfahi-research/服务端消息加密与内容审核改造文档.md @@ -0,0 +1,570 @@ +# 服务端消息加密与内容审核改造文档 + +> 文档状态:待开发 +> 编制日期:2026-09-13 +> 适用项目:`im_manager`(Admin Server)、`im_server`(IM Server) +> 本文档是后续数据库设计、后端开发、联调、迁移和验收的共同依据。 + +## 1. 建设目标 + +本次改造同时建设两项能力: + +1. 内容审核:IM Server 在保存或修改内容前,根据商户策略执行本地敏感词检查和阿里云文本内容审核。 +2. 消息加密存储:审核通过的消息在 IM Server 内部使用 AES-256-GCM 加密,国内 IM 业务库只保存密文;经授权的后台查询由 IM Server 解密后返回明文。 + +本次不做以下内容: + +- 不做客户端 AES 二次加密。 +- 不做端到端加密(E2EE)。 +- 不改变现有 HTTPS/WSS 传输协议。 +- 不允许 Admin Server 直接读取 IM 数据库并自行解密。 +- 不在日志、Kafka 事件、死信或监控标签中保存消息明文。 + +## 2. 安全边界和部署前提 + +### 2.1 部署分工 + +| 区域 | 系统 | 保存数据 | +| --- | --- | --- | +| 国外 | HotGo / Admin Server | 商户审核策略、阿里云配置、字典根密钥、商户消息密钥 | +| 国内 | IM Server | 用户、会话、消息密文、审核结果和处置记录 | + +该方案主要防护目标是:任意一侧数据库单独泄露时,不能直接恢复消息明文。 + +已知边界:HotGo 数据库、IM 数据库和完整代码同时泄露不在本方案的主要防护范围内。 + +### 2.2 传输安全 + +- 客户端调用必须使用 `https://` 和 `wss://`。 +- 阿里云审核请求必须使用 HTTPS。 +- Admin Server 与 IM Server 的内部配置和查询接口必须使用 HTTPS,并保留现有签名鉴权;跨公网部署时建议叠加 mTLS 或固定出口 IP 白名单。 +- TLS 只解决传输保护,AES-256-GCM 解决消息落库后的静态保护。 + +## 3. 总体架构 + +```text +客户端 + | HTTPS/WSS明文业务数据(TLS通道内不可见) + v +IM Server + |-- 实时权限、宵禁、用户状态等校验 + |-- 获取商户审核策略 + |-- 本地敏感词补充检查 + |-- 阿里云 TextModerationPlus 审核 + |-- AES-256-GCM 加密 + `-- 密文写入国内IM库 + +Admin Server(国外HotGo) + |-- 管理阿里云配置 + |-- 管理商户审核策略 + |-- 管理商户消息密钥 + |-- 通过内部接口下发配置/密钥 + `-- 通过IM内部查询接口查看解密后消息 +``` + +## 4. 内容审核设计 + +### 4.1 审核范围 + +| 场景 | 阿里云 Service | 审核时机 | +| --- | --- | --- | +| 用户昵称 | `nickname_detection_pro` | 更新用户资料前 | +| 群昵称 | `nickname_detection_pro` | 更新群成员资料前 | +| 群名称 | `nickname_detection_pro` | 创建群或修改群资料前 | +| 用户签名 | `nickname_detection_pro` | 更新用户资料前 | +| 单聊文本 | `chat_detection_pro` | 消息入库及广播前 | +| 群聊文本 | `chat_detection_pro` | 消息入库及广播前 | +| 编辑后消息 | `chat_detection_pro` | 新版本入库前 | +| 群公告 | `chat_detection_pro` | 发布或修改前 | + +图片、视频和文件继续走文件审核链路,不属于本文本审核接口范围。 + +### 4.2 商户范围 + +- 第一阶段只为 `tenant.code=default` 的上架默认商户开启阿里云内容审核。 +- 其他团队商户默认关闭,不调用阿里云,不产生审核费用。 +- 数据模型保留按商户开启的能力,但只有平台管理员可变更。 +- 商户端管理员不得看到阿里云 AK/SK,不得修改平台级审核服务配置。 + +### 4.3 后台配置项 + +#### 4.3.1 阿里云服务配置 + +复用 `im_external_config` 的 `service=moderation`,由平台管理员维护: + +| 配置 | 说明 | +| --- | --- | +| `provider` | 固定为 `aliyun_green` | +| `endpoint` | 默认 `green-cip.cn-shanghai.aliyuncs.com` | +| `enabled` | 平台总开关 | +| `config_json.region_id` | 默认 `cn-shanghai` | +| `config_json.nickname_service` | 默认 `nickname_detection_pro` | +| `config_json.chat_service` | 默认 `chat_detection_pro` | +| `config_json.timeout_ms` | 单次请求超时,建议 1000~2000 ms | +| `config_json.failure_mode` | `allow` 或 `block` | +| `secret_json.access_key_id` | 只写不回显 | +| `secret_json.access_key_secret` | 只写不回显 | + +“检查配置”必须真实调用阿里云测试文本,不能只检查字段非空。返回仅包含成功与否、请求耗时、RequestID 和脱敏错误。 + +#### 4.3.2 商户审核策略 + +可在 `im_security_policy.sensitive_word_policy` 中保存,避免新增重复策略表。JSON 结构固定为: + +```json +{ + "enabled": true, + "aliyun_enabled": true, + "local_words_enabled": true, + "scenes": { + "user_nickname": true, + "user_signature": true, + "group_name": true, + "group_nickname": true, + "private_message": true, + "group_message": true, + "group_announcement": true + }, + "risk_actions": { + "high": "block", + "medium": "block", + "low": "audit_only", + "none": "allow" + }, + "failure_mode": "allow", + "block_message": "内容不符合平台规范,请修改后重试" +} +``` + +策略发布后使用现有 `im.config.event` 发布配置事件,IM Server 按商户清理本地缓存。 + +### 4.4 文本提取 + +审核输入不能直接对任意 JSON 执行字符串化,应按 Drafty 或消息类型提取用户可见文本: + +- 普通文本:提取完整文本。 +- Drafty 富文本:提取 `txt` 及按钮、链接等用户可见标题。 +- 图片/文件/语音:审核文件名、说明和附带文本,文件本体走文件审核。 +- 系统消息、音视频信令和已签名服务端消息不调用文本审核。 +- 空文本直接放行。 + +### 4.5 阿里云结果处置 + +IM Server 统一映射为: + +| 内部结果 | 动作 | +| --- | --- | +| `allow` | 继续加密、入库和广播 | +| `block` | 不入库、不广播,返回统一业务错误 | +| `audit_only` | 允许发送,但写入审核命中日志 | +| `retryable_error` | 根据 `failure_mode` 放行或拦截 | + +不只依赖 `RiskLevel/Label` 一个字段,需保留与验证阿里云原始结果中的扩展判断,针对引流、诈骗等样本建立回归测试集。 + +### 4.6 超时与降级 + +- 阿里云请求不得无限等待。 +- 建议默认 `failure_mode=allow`,阿里云异常时允许发送并记录告警,防止第三方故障造成 IM 全局不可用。 +- 连续失败达到阈值后进入短时熔断;熔断期间按失败策略处理。 +- 阿里云调用不得在数据库事务中执行。 + +## 5. 消息加密设计 + +### 5.1 算法 + +- 算法:AES-256-GCM。 +- DEK 长度:32 字节。 +- Nonce:每次加密使用 `crypto/rand` 生成的独立 12 字节随机值。 +- GCM 标签:使用 Go 标准库默认 16 字节认证标签。 +- 明文编码:将原 `content` JSON 规范化序列化为 UTF-8 字节。 +- 不允许固定 Nonce,不允许 AES-ECB,不使用未认证的 AES-CBC/CTR。 + +### 5.2 AAD + +每条消息加密时绑定以下 AAD: + +```text +tenant_id | topic | seqid | from | client_msg_id | key_version | content +``` + +`content` 是字面标识,用于与未来可能加密的 `head` 区分。AAD 按固定字段顺序和长度前缀编码,禁止使用可产生拼接歧义的简单字符串。 + +### 5.3 密钥层级 + +```text +HotGo字典根密钥(KEK) + -> 加密每个商户、每个版本的DEK + -> DEK加密该商户消息 +``` + +根密钥使用 HotGo `hg_sys_dict_data` 保存,但必须满足: + +1. 使用独立的内部字典类型,不出现在通用字典管理页。 +2. 后端字典列表、选项和导出接口必须对该类型做服务端拒绝,不能仅依赖菜单隐藏。 +3. 根密钥为 32 字节强随机值的 Base64 编码,不使用口令、UUID 或可读文本。 +4. 根密钥只由 Admin Server 密钥服务读取,不通过通用配置接口返回。 +5. 根密钥不写日志,不在后台页面回显,不进入 Kafka 消息。 + +### 5.4 商户密钥管理表 + +在 HotGo 库新增 `im_message_encryption_keys`: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `id` | bigint | 主键 | +| `tenant_id` | bigint | 商户 ID | +| `key_version` | int | 密钥版本,从 1 递增 | +| `algorithm` | varchar(32) | 固定 `AES-256-GCM` | +| `encrypted_dek` | blob | KEK 加密后的 DEK | +| `dek_nonce` | varbinary(12) | 加密 DEK 时的随机 Nonce | +| `status` | varchar(16) | `pending` / `active` / `retired` / `disabled` | +| `created_at` | datetime | 创建时间 | +| `activated_at` | datetime | 启用时间 | +| `retired_at` | datetime | 退役时间 | +| `created_by` | bigint | 操作人 | +| `remark` | varchar(255) | 备注 | + +约束: + +- 唯一键:`tenant_id + key_version`。 +- 每个商户同时只能有一把 `active` 密钥。 +- `retired` 密钥仍可用于解密历史消息,不得再用于新消息加密。 +- `disabled` 只用于安全事故应急,禁用前必须完成历史消息转密钥。 + +### 5.5 IM 消息表改造 + +`im_messages` 和 `im_message_revisions` 新增: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `content_ciphertext` | longblob | 消息密文,包含 GCM 认证标签 | +| `content_nonce` | varbinary(12) | 本条消息随机 Nonce | +| `key_version` | int | 商户 DEK 版本 | +| `encryption_alg` | varchar(32) | `AES-256-GCM` | +| `encrypted` | tinyint | `0` 明文兼容,`1` 密文 | + +迁移完成前保留现有 `content JSON`,规则如下: + +- 新写入:`content=NULL`、`encrypted=1`,只保存密文。 +- 旧数据:`content IS NOT NULL`、`encrypted=0`,按原逻辑读取。 +- 解密成功后还原为原 `any` JSON 结构,不修改对客户端的协议。 +- `head` 第一阶段不加密,但禁止在 `head` 中放置消息正文、联系方式等敏感明文。 + +`im_group_messages` 是群消息派生查询表,不得另存一份明文。实现时二选一: + +1. 推荐:仅保存索引、状态和统计字段,通过 `topic + seqid` 关联 `im_messages` 解密。 +2. 兼容:同步增加密文字段,但加密输入和密文必须与主消息表保持一致。 + +不允许 `im_messages` 已加密而 `im_group_messages.content` 仍保留明文。 + +### 5.6 加密和解密时机 + +#### 新消息 + +```text +校验消息 + -> 提取审核文本 + -> 内容审核 + -> 分配seqid/组装AAD + -> 序列化content + -> 使用当前active DEK加密 + -> 事务写入密文和消息元数据 + -> 向在线订阅者广播原内存明文 +``` + +#### 历史消息和离线同步 + +```text +从数据库读取密文 + -> 按tenant_id + key_version获取DEK + -> 重建AAD + -> GCM验签并解密 + -> JSON反序列化 + -> 按原Tinode协议返回 +``` + +任何验签失败均必须当作数据篡改或密钥错误,禁止返回部分内容或尝试忽略错误。 + +### 5.7 密钥获取和缓存 + +- IM Server 不得每发送一条消息都跨境请求 HotGo。 +- IM Server 按 `tenant_id + key_version` 在内存中缓存已解密 DEK。 +- 新消息使用当前 `active` 密钥,历史消息按记录中的 `key_version` 取密钥。 +- 密钥变更发布 `message.encryption.key.updated` 配置事件,IM Server 按商户刷新。 +- HotGo 短时不可用时继续使用内存密钥。 +- IM Server 重启后如无法取得当前密钥,该商户禁止写入新消息,不得降级为明文落库。 + +### 5.8 密钥轮换 + +1. Admin Server 生成新 DEK,使用 KEK 加密后以 `pending` 状态入库。 +2. 通知 IM Server 拉取并验证新密钥。 +3. 验证成功后,在事务中将旧密钥设为 `retired`,新密钥设为 `active`。 +4. 新消息立即使用新版本,旧消息仍按原版本解密。 +5. 历史转密钥作为单独低速任务执行,不阻塞即时通信。 + +## 6. Admin Server 改造 + +### 6.1 内容审核页面 + +将现有“审核插件”配置补齐为可用功能: + +- 阿里云基础配置。 +- 真实连通性检查。 +- 商户审核开关和场景开关。 +- 风险等级与处置动作映射。 +- 异常降级策略。 +- 配置发布版本和生效时间。 + +非默认商户显示“未开启内容审核”,不显示阿里云密钥编辑项。 + +### 6.2 消息加密管理页面 + +增加平台级“消息加密”页面,只允许最高权限平台管理员使用: + +- 查看商户加密状态、当前密钥版本和最后轮换时间。 +- 初始化商户密钥。 +- 启用/暂停商户消息加密。 +- 轮换密钥。 +- 查看历史迁移进度。 +- 密钥明文、根密钥和 `encrypted_dek` 均不展示。 + +### 6.3 内部接口 + +建议增加: + +| 方法 | 路径 | 用途 | +| --- | --- | --- | +| GET | `/internal/im-config/tenants/{code}/moderation-policy` | IM Server 拉取商户审核策略 | +| GET | `/internal/im-config/moderation/provider` | IM Server 拉取阿里云配置 | +| GET | `/internal/im-config/tenants/{code}/message-keys/active` | 获取当前消息 DEK | +| GET | `/internal/im-config/tenants/{code}/message-keys/{version}` | 获取指定版本 DEK | +| POST | `/internal/im-config/tenants/{code}/message-keys/ack` | IM Server 确认新密钥可用 | + +密钥接口返回的是服务端使用的 DEK,不允许从浏览器或通用 Admin API 调用。接口必须校验服务身份、时间窗、签名、请求 ID 和重放。 + +### 6.4 后台消息查询 + +- Admin Server 继续调用 IM Server internal admin-query,不直连 IM 业务库解密。 +- IM Server 在商户数据权限通过后批量解密当页消息。 +- 每次查看明文记录管理员 ID、商户、IP、查询条件、返回数量、时间和 TraceID。 +- 列表只解密当页,禁止一次加载全部历史消息。 + +## 7. IM Server 改造 + +### 7.1 新增内部模块 + +```text +server/moderation/ + provider.go 审核Provider接口 + policy.go 商户策略和缓存 + text.go 文本提取与结果映射 + aliyun/text.go TextModerationPlus实现 + +server/messagecrypto/ + cipher.go AES-256-GCM加解密 + key_provider.go 密钥获取接口 + key_cache.go tenant/version内存缓存 + serializer.go content规范序列化 +``` + +不将加密和阿里云调用直接写进 MySQL adapter;adapter 只保存已加密的存储模型和解密所需元数据。 + +### 7.2 消息发送接入点 + +在 `Topic.handlePubBroadcast` 中,于 `saveAndBroadcastMessage` 之前执行: + +1. 过滤 WebRTC/系统信令等不审核内容。 +2. 提取文本。 +3. 根据商户策略执行审核。 +4. 拦截时通过 Tinode ctrl 回复业务错误,不进入保存和广播。 +5. 放行时进入加密保存。 + +进程内广播使用本次请求已有明文,不为广播执行“加密后立即解密”。 + +### 7.3 资料修改接入点 + +用户昵称/签名、群名称/公告、群昵称的 HTTP 和 Tinode meta 修改通道都必须接入同一 moderation service,不能只修改某一个 HTTP API 而留下协议绕过路径。 + +### 7.4 存储层改造 + +- `types.Message` 和 `types.MessageRevision` 增加密文及密钥元数据字段。 +- MySQL 的普通库和共享库/多商户 adapter 必须同时改造。 +- 新建库 schema、启动 ensure 迁移和手工 SQL 脚本必须保持一致。 +- Save、GetOne、GetAll、编辑、撤回、删除、历史修订、管理后台查询均需覆盖。 +- 解密层应在业务存储服务边界统一执行,避免上层遗漏。 + +### 7.5 缓存与多 Pod + +- 每个 Pod 保持本地密钥和审核策略缓存。 +- Kafka 配置事件使所有 Pod 同时失效。 +- 事件处理必须幂等,重复消息不得导致重复创建密钥。 +- 新旧密钥切换期间,所有 Pod 必须同时具备旧版本解密和新版本加密能力。 + +## 8. 审核和解密日志 + +在 IM 业务库增加 `im_content_moderation_logs`: + +| 字段 | 说明 | +| --- | --- | +| `id` | 主键 | +| `request_id` | 审核请求 ID | +| `scene` | 昵称、单聊、群聊等 | +| `topic` / `seqid` | 关联消息,拦截时 `seqid` 可为空 | +| `actor_id` | 内容提交人 | +| `provider` | `local` / `aliyun_green` | +| `provider_request_id` | 第三方 RequestID | +| `risk_level` | 统一风险等级 | +| `risk_labels` | 命中标签 JSON | +| `action` | `allow` / `block` / `audit_only` | +| `content_hash` | SHA-256 摘要,不保存明文 | +| `latency_ms` | 审核耗时 | +| `error_code` | 脱敏错误码 | +| `created_at` | 创建时间 | + +后台明文查看继续写入统一审计事件,必须区分“列表解密”、“详情解密”和“导出解密”。默认不提供明文批量导出;需要开放时必须增加独立权限和审批。 + +## 9. 关键词查询限制 + +消息加密后,MySQL 无法对密文执行原有 `LIKE '%keyword%'`。第一阶段采用: + +1. 后台查询必须先提供商户、时间范围,并尽量提供用户或群号。 +2. IM Server 先用明文元数据缩小候选集。 +3. 对有上限的候选集批量解密,再在内存中匹配关键词。 +4. 限制查询最大时间范围和候选消息数,超限返回明确提示。 + +不建议在第一阶段建设明文分词索引,否则会降低加密保护效果。若后续必须支持全库高性能检索,再单独评审 HMAC 盲索引方案。 + +## 10. 历史数据迁移 + +### 10.1 发布顺序 + +1. 发布 HotGo 表结构、根密钥和密钥管理能力。 +2. 为默认商户初始化第 1 版 DEK,但不开启加密。 +3. 发布 IM Server 双读兼容版本,验证密钥获取、加解密和内容审核。 +4. 开启新消息加密写入,保持旧数据明文可读。 +5. 启动历史数据分批迁移。 +6. 对比数量、抽样解密和 GCM 验签,完成后清空明文 `content`。 +7. 观察期结束后,再决定是否删除原明文字段。 + +### 10.2 迁移任务要求 + +- 按主键或 `topic + seqid` 分批,不长时锁表。 +- 每批使用独立事务,记录游标、成功数、失败数和最后错误。 +- 重复执行必须幂等,`encrypted=1` 的记录直接跳过。 +- 先写入密文并验证可解密,再将明文字段置空。 +- 失败记录保留明文并进入重试列表,不得丢失原数据。 +- 迁移速率可配置,并监控数据库负载和消息延迟。 + +## 11. 错误码和客户端兼容 + +客户端不参与加解密,不修改消息协议。新增业务错误建议: + +| 错误 | 说明 | 客户端处理 | +| --- | --- | --- | +| `CONTENT_REJECTED` | 内容审核拒绝 | 保留未发送文本并提示修改 | +| `CONTENT_REVIEW_UNAVAILABLE` | 审核不可用且策略要求拦截 | 提示稍后重试 | +| `MESSAGE_ENCRYPTION_UNAVAILABLE` | 未取得加密密钥 | 提示服务暂时不可用 | +| `MESSAGE_DECRYPT_FAILED` | 历史消息验签/解密失败 | 展示“消息暂时无法加载”,不展示乱码 | + +不向客户端返回阿里云原始错误、风险标签详情、密钥版本内部信息或解密堆栈。 + +## 12. 监控指标 + +### 12.1 内容审核 + +- 按商户/场景统计请求量、放行数、拦截数、降级数。 +- 阿里云耗时 P50/P95/P99。 +- 超时率、错误率、熔断状态。 +- 各风险等级和标签命中数。 + +### 12.2 消息加密 + +- 加密/解密成功数、失败数和耗时。 +- 各商户当前密钥版本。 +- 密钥获取成功率、缓存命中率和缓存最后刷新时间。 +- 明文历史记录剩余数、迁移速率和迁移失败数。 +- GCM 验签失败必须产生高优先级告警。 + +监控标签不得使用消息正文、密文、Nonce、DEK 或 KEK。 + +## 13. 验收标准 + +### 13.1 审核验收 + +- `default` 商户开启后,单聊、群聊、用户昵称、群名称和群昵称均实际调用对应阿里云 Service。 +- 其他商户关闭时不调用阿里云。 +- 拦截内容不进入 `im_messages` 和 `im_group_messages`,也不向订阅者广播。 +- 本地敏感词与阿里云开关可独立启停。 +- 阿里云超时严格按 `failure_mode` 处理。 +- 配置发布后所有 IM Pod 在规定时间内生效。 + +### 13.2 加密验收 + +- 开启商户的新消息在数据库中不存在可读正文。 +- 相同内容连续发送两次,Nonce 和密文必须不同。 +- 修改密文、Nonce、AAD 关联字段的任意一处后,解密必须失败。 +- 在线发送、离线同步、多端漫游、编辑、回复、撤回、删除保持原有业务行为。 +- Admin 有权限账号可查看解密明文,无权限账号被拒绝。 +- 查看明文必须形成完整审计记录。 +- 旧密钥退役后仍可解密旧版本消息,新消息只使用新版本。 +- HotGo 暂时断开时,已有内存密钥的 IM Pod 仍可工作;无密钥时绝不允许明文降级落库。 + +### 13.3 性能验收 + +- 纯 AES 加密不得成为消息链路主要瓶颈。 +- 密钥缓存命中时不发生跨境 HotGo 请求。 +- 审核关闭的商户不增加阿里云网络耗时。 +- 审核开启时应分别统计本地处理、阿里云调用和消息入库耗时。 +- 历史迁移期间即时消息发送和数据库负载不得超过上线阈值。 + +## 14. 开发分批建议 + +### 批次一:配置和密钥基础 + +- Admin 阿里云内容审核配置、商户策略、密钥表和密钥管理页。 +- 字典根密钥隔离和专用密钥服务。 +- Admin internal config/key API 及 Kafka 失效事件。 + +### 批次二:IM 文本审核 + +- 阿里云 `TextModerationPlus` Go 客户端。 +- 策略缓存、文本提取、消息和资料修改接入。 +- 审核日志、错误码、告警和回归测试。 + +### 批次三:新消息加密 + +- AES-256-GCM 模块、密钥缓存和存储模型。 +- 新消息加密写入和历史消息解密读取。 +- 单聊、群聊、修订、后台查询的一致性验证。 + +### 批次四:历史迁移和轮换 + +- 历史明文分批加密。 +- 迁移仪表盘、少量失败重试和数据对账。 +- 密钥轮换和历史转密钥测试。 +- 完成观察后清理明文。 + +## 15. 开发前必须确认的决策 + +| 决策 | 文档默认值 | +| --- | --- | +| 阿里云异常时放行还是拦截 | 放行并告警 | +| 是否审核系统消息 | 不审核 | +| 是否加密 `head` | 第一阶段不加密 | +| 第一阶段启用商户 | 仅 `code=default` | +| 是否允许无密钥时明文降级 | 不允许 | +| 后台是否允许明文导出 | 默认不允许 | +| 历史消息是否迁移 | 是,分批迁移 | +| 根密钥位置 | HotGo 内部字典数据,严禁通用接口暴露 | + +## 16. 现有实现对照 + +| 能力 | 现状 | 本次改造 | +| --- | --- | --- | +| 阿里云文本审核 | 文档已人工验证,IM Server 无 Go 调用实现 | 增加 TextModerationPlus 客户端和业务接入 | +| 本地敏感词 | Admin 已有 CRUD,IM Server 未消费 | 作为可选补充审核 | +| 审核配置 | `im_external_config` 已有 `moderation` 占位 | 增加真实检查、商户策略和发布 | +| 配置同步 | 已有 internal config 客户端、Kafka 配置事件和缓存失效 | 复用并扩展新的策略/密钥类型 | +| 消息存储 | `im_messages.content` 和修订表 `content` 为 JSON 明文 | 改为 AES-256-GCM 密文且双读迁移 | +| 群消息派生表 | `im_group_messages.content` 为 JSON 明文 | 去明文副本或同步密文化 | +| 后台消息查询 | IM internal-query 返回库内内容 | 增加权限校验、解密和查看审计 |