# 服务端内容审核与消息加密改造文档 > 文档状态:分阶段实施 > > 更新日期:2026-09-16 > > 适用项目:`im_manager`(Admin Server)、`im_server`(IM Server) ## 实施顺序 本项目拆成两个相互独立的实施部分,并统一保存在本文档中: 1. 第一部分先完成内容审核,独立开发、灰度、验收和稳定观察;本阶段不修改消息存储格式。 2. 第二部分在内容审核稳定后实施消息加密;该阶段单独立项、单独发布,并对消息表和历史数据进行分批迁移。 两部分禁止同批切换。第二部分只接管“内容审核通过后的消息存储”,不得反向修改第一部分已经验收的审核决策。 ```text 第一部分: 客户端 -> TLS -> IM Server -> 内容审核 -> 现有明文存储 第二部分: 客户端 -> TLS -> IM Server -> 内容审核 -> AES-256-GCM -> 密文存储 ``` ## 第二部分启动门禁 消息加密开发必须同时满足: 1. 内容审核已全量上线并通过业务验收。 2. 误拦截率、阿里云耗时、超时降级和熔断策略达到生产要求。 3. 消息发送、编辑、资料修改和多 Pod 配置同步已经稳定。 4. IM 数据库全量备份及恢复演练通过。 5. 历史消息规模、迁移时间和数据库负载完成评估。 6. 加密双读、密钥轮换和回滚方案已在测试环境演练通过。 ## 第一部分:服务端内容审核(优先实施) > 文档状态:第一阶段,优先开发 > > 编制日期:2026-09-16 > > 适用项目:`im_manager`(Admin Server)、`im_server`(IM Server) > > 本阶段不修改消息存储格式,不实施消息加密。 ### 1. 目标 Admin Server 提供内容审核配置和商户策略管理,IM Server 在内容写入前根据策略执行: 1. 本地敏感词补充检查。 2. 阿里云 `TextModerationPlus` 文本审核。 3. 按风险等级放行、拦截或仅审计。 4. 记录审核结果、请求耗时和处置结果,不保存审核明文。 第一阶段只为 `tenant.code=default` 的上架默认商户开启阿里云审核。其他团队商户默认关闭,不产生阿里云调用和费用。 ### 2. 不在本阶段实施的内容 - 不改造 `im_messages.content` 和 `im_group_messages.content`。 - 不建设 AES 加解密模块。 - 不建设密钥管理表、密钥轮换和历史消息迁移。 - 不做客户端加密或端到端加密。 - 不改变现有 HTTPS/WSS 传输协议。 - 图片、视频和文件本体继续走独立的文件审核链路。 ### 3. 现有实现和差距 | 能力 | 现状 | 改造目标 | | --- | --- | --- | | 阿里云文本审核 | 文档已人工验证,IM Server 无 Go 调用实现 | 实现 `TextModerationPlus` 客户端并接入业务链路 | | 审核服务配置 | `im_external_config` 已有 `moderation` 占位 | 补齐阿里云参数、真实连通性检查和发布 | | 商户审核策略 | `im_security_policy.sensitive_word_policy` 可保存 JSON | 定义固定结构并提供后台配置 | | 本地敏感词 | Admin 已有 CRUD,IM Server 未消费 | 作为可选补充规则接入 IM 运行时 | | 配置同步 | 已有 internal config、Kafka 配置事件和缓存失效 | 复用现有机制扩展审核策略 | | 消息审核 | 消息直接保存和广播 | 在保存和广播前审核 | | 昵称审核 | 无 | 覆盖用户昵称、群昵称和群名称的所有修改入口 | ### 4. 总体链路 ```text 客户端 | HTTPS/WSS v IM Server |-- 权限、宵禁、用户状态等现有校验 |-- 根据tenant读取审核策略 |-- 提取用户可见文本 |-- 本地敏感词检查(可选) |-- 阿里云TextModerationPlus(可选) |-- 映射风险结果和处置动作 |-- block:返回业务错误,不入库、不广播 `-- allow/audit_only:按原逻辑入库和广播 ``` ### 5. 审核场景 | 场景 | 阿里云 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` | 发布或修改前 | 系统消息、WebRTC 信令、已签名的服务端消息和空文本不调用文本审核。 ### 6. Admin Server 改造 #### 6.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 和脱敏错误,不能只检查字段非空。 #### 6.2 商户审核开关与策略 `im_tenant_config` 增加两个商户级权威开关,并在“商户管理”的新增/编辑页面维护: | 字段 | 说明 | | --- | --- | | `aliyun_moderation_enabled` | 是否对该商户启用阿里云内容审核 | | `local_moderation_enabled` | 是否对该商户启用本地敏感词审核 | 两个开关默认关闭;现有 `default` 商户迁移时保持开启。只要任一开关开启,该商户即进入内容审核流程。安全策略不再重复维护审核引擎开关,只维护场景、风险动作、第三方失败策略和拦截提示。 复用 `im_security_policy.sensitive_word_policy`,统一 JSON 结构: ```json { "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_tenant_config` 的两个商户开关为准。 - 每个商户可以独立开启阿里云审核、本地审核,或同时开启。 - 商户管理员不得查看或修改阿里云 AK/SK。 - 策略发布使用现有 `im.config.event` 通知 IM Server 按商户失效缓存。 #### 6.3 本地敏感词 - 现有 `im_sensitive_words` 和分组管理作为补充规则保留。 - 只有商户配置 `local_moderation_enabled=1` 时才加载。 - 敏感词和分组按商户隔离,平台管理员及当前商户管理员按权限维护。 - 词库更新继续发布 `sensitive_words.updated` 事件。 #### 6.4 内部接口 | 方法 | 路径 | 用途 | | --- | --- | --- | | GET | `/internal/im-config/moderation/provider` | 拉取阿里云审核服务配置 | | GET | `/internal/im-config/tenants/{code}/moderation-policy` | 拉取商户审核策略 | | GET | `/internal/im-config/tenants/{code}/sensitive-words` | 拉取商户有效敏感词 | 接口复用现有内部服务签名鉴权、时间窗、RequestID 和重放校验,禁止浏览器直接调用。 ### 7. IM Server 改造 #### 7.1 内部模块 ```text server/moderation/ provider.go 审核Provider接口 policy.go 商户策略和缓存 extractor.go 文本提取 decision.go 风险结果映射 local/words.go 本地敏感词匹配 aliyun/text.go TextModerationPlus实现 ``` 统一接口需支持:商户、场景、操作人、业务对象、文本和 TraceID,返回 `allow/block/audit_only`、风险等级、标签和可审计的脱敏结果。 #### 7.2 消息发送接入 在 `Topic.handlePubBroadcast` 中,于 `saveAndBroadcastMessage` 之前: 1. 排除 WebRTC/系统信令。 2. 从 `msg.Pub.Content` 提取审核文本。 3. 执行本地规则和阿里云审核。 4. `block` 时通过 Tinode ctrl 返回业务错误,不调用保存和广播。 5. `allow/audit_only` 时继续现有消息链路。 消息编辑必须重新审核编辑后内容,不复用原消息结果。 #### 7.3 资料修改接入 用户昵称/签名、群名称/公告、群昵称的 HTTP 和 Tinode meta 修改通道必须接入同一 moderation service,防止通过另一个协议入口绕过审核。 #### 7.4 文本提取 - 普通文本:提取完整文本。 - Drafty 富文本:提取 `txt` 及按钮、链接的用户可见标题。 - 图片/文件/语音:提取文件名、说明和附带文本。 - 禁止将任意 JSON 直接字符串化后送审,避免审核协议元数据。 #### 7.5 阿里云结果映射 | 内部结果 | 动作 | | --- | --- | | `allow` | 继续保存和广播 | | `block` | 不入库、不广播,返回统一业务错误 | | `audit_only` | 允许业务继续,写审核日志 | | `retryable_error` | 根据 `failure_mode` 放行或拦截 | 阿里云已测试样本表明,部分引流诈骗话术可能只出现在扩展判断中。实现时不能只查看单一 `RiskLevel/Label`,需保留脱敏原始响应以便调试,并使用真实样本建立回归集。 #### 7.6 超时、降级和熔断 - 阿里云请求不得在数据库事务中执行。 - 必须设置独立超时,禁止无限等待。 - 默认 `failure_mode=allow`,第三方异常时放行并告警,避免 IM 全局不可用。 - 连续失败达阈值后短时熔断,熔断期间按 `failure_mode` 处理。 - 本地敏感词检查不受阿里云熔断影响。 #### 7.7 缓存和多 Pod - 阿里云全局配置、商户策略和敏感词分别缓存。 - 策略缓存键至少包含 `tenant_code + policy_version`。 - Kafka 配置事件和词库事件使所有 IM Pod 同时失效。 - 事件消费必须幂等,丢失事件后仍能通过 TTL 重新加载。 ### 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` | 创建时间 | 表中不保存原始明文、AK/SK 或完整第三方响应中的敏感信息。 ### 9. 错误码 | 错误 | 说明 | 客户端处理 | | --- | --- | --- | | `CONTENT_REJECTED` | 内容审核拒绝 | 保留未发送内容,提示用户修改 | | `CONTENT_REVIEW_UNAVAILABLE` | 审核不可用且策略为拦截 | 提示稍后重试 | 不向客户端返回阿里云原始错误、命中词详情、AK/SK 或内部堆栈。 ### 10. 监控 - 按商户/场景统计请求量、放行数、拦截数、审计数和降级数。 - 阿里云耗时 P50/P95/P99。 - 超时率、错误率、熔断状态和熔断持续时间。 - 按风险等级和标签统计命中数。 - 配置版本、各 Pod 最后刷新时间和缓存命中率。 - 监控标签不得放入原始消息文本。 ### 11. 开发任务拆分 #### 11.1 Admin Server 1. 补齐 `moderation` 外部服务配置和真实连通性检查。 2. 实现商户审核策略表单和发布。 3. 限制默认商户以外的敏感词入口和后端权限。 4. 增加阿里云配置、商户策略和词库 internal API。 5. 发布并验证 Kafka 配置失效事件。 #### 11.2 IM Server 1. 实现 moderation 统一接口。 2. 实现阿里云 `TextModerationPlus` 客户端。 3. 实现商户策略、本地敏感词和阿里云配置缓存。 4. 接入消息发送、消息编辑、用户资料和群资料入口。 5. 增加审核日志表、错误映射、监控和告警。 6. 补齐单元测试、集成测试和阿里云沙箱/测试账号回归。 ### 12. 测试重点 - 文本、Drafty、链接、表情、中英文混合和空内容提取。 - 同一内容在单聊、群聊和编辑入口处置一致。 - HTTP 和 Tinode meta 入口不能相互绕过。 - `default` 商户开启,其他商户关闭。 - 阿里云超时、500、限流、鉴权失败和熔断恢复。 - 本地词库命中、阿里云命中、双重命中和无命中。 - 多 Pod 收到配置发布后同步生效。 - 拦截消息不入库、不广播,不占用有效消息序号。 - 日志、Kafka、死信和监控中不出现消息明文和 AK/SK。 ### 13. 验收标准 1. `default` 商户的用户昵称、群昵称、群名称、单聊和群聊均真实调用对应阿里云 Service。 2. 其他商户关闭审核时不调用阿里云。 3. 拦截内容不进入消息表,不向任何订阅者广播。 4. 本地敏感词与阿里云开关可独立启停。 5. 阿里云超时和异常严格按 `failure_mode` 处理。 6. 策略和词库发布后,所有 IM Pod 在规定时间内生效。 7. 日志能追溯商户、场景、用户、RequestID、风险结果和处置动作,但不保存明文。 8. 审核上线后独立稳定运行和观察,不依赖消息加密功能。 ### 14. 发布顺序 1. 先发布 Admin Server 配置表单、internal API 和配置事件。 2. 发布 IM Server 审核模块,但保持所有商户策略关闭。 3. 对阿里云连通性、超时降级和多 Pod 配置同步进行生产前验证。 4. 仅对测试用户或小流量开启 `default` 商户审核。 5. 观察误拦截率、阿里云耗时、错误率和消息总延迟。 6. 完成全量开启和验收,进入独立稳定观察期。 7. 审核功能稳定并通过验收后,才能启动消息加密阶段。 ## 第二部分:服务端消息加密存储(审核稳定后实施) > 文档状态:第二阶段,内容审核稳定后实施 > > 编制日期:2026-09-16 > > 适用项目:`im_manager`(Admin Server)、`im_server`(IM Server) > > 该改造会影响消息写入、历史同步、后台查询和数据迁移,必须单独发布和验收。 ### 1. 实施前提 本阶段必须在《服务端内容审核改造文档》完成开发、全量上线和稳定观察后启动。 启动条件: 1. 内容审核的误拦截率、耗时、超时和降级策略已验收。 2. 审核与消息保存的边界已稳定,加密模块只接管“审核通过后的存储”。 3. 已完成 IM 数据库全量备份和可恢复性演练。 4. 已对 `im_messages`、`im_message_revisions`、`im_group_messages` 的数据量、存储量和迁移耗时完成评估。 ### 2. 目标和非目标 #### 2.1 目标 - 审核通过的新消息在 IM Server 使用 AES-256-GCM 加密后落库。 - 国内 IM 业务库不保存可读消息正文。 - HotGo 库保存根密钥和加密后的商户数据密钥。 - 客户端协议不变,IM Server 对历史消息解密后按原协议返回。 - Admin Server 通过 IM internal-query 查看授权范围内的解密消息。 - 支持密钥版本、轮换、退役和历史数据迁移。 #### 2.2 非目标 - 不做客户端 AES 加密。 - 不做端到端加密(E2EE)。 - 不改变 HTTPS/WSS 传输安全方案。 - 不允许 Admin Server 直连 IM 数据库后自行解密。 - 不在日志、Kafka、死信和监控标签中保存消息明文。 ### 3. 安全边界和部署 | 区域 | 系统 | 保存数据 | | --- | --- | --- | | 国外 | HotGo / Admin Server | 字典根密钥、商户消息密钥、加密开关和版本 | | 国内 | IM Server | 用户、会话、消息密文和密钥版本 | 防护目标:任意一侧数据库单独泄露时,不能直接恢复消息明文。 已知边界:HotGo 数据库、IM 数据库和完整代码同时泄露不在本方案的主要防护范围内。 Admin Server 和 IM Server 跨公网通信必须使用 HTTPS、现有签名鉴权和重放防护,建议叠加 mTLS 或固定出口 IP 白名单。 ### 4. 算法和存储格式 #### 4.1 算法 - 算法:AES-256-GCM。 - DEK:32 字节强随机密钥。 - Nonce:每次加密使用 `crypto/rand` 生成的独立 12 字节随机值。 - GCM 标签:使用 Go 标准库默认 16 字节认证标签。 - 明文:将原 `content` JSON 规范序列化为 UTF-8 字节。 - 禁止固定 Nonce、AES-ECB 和未认证的 AES-CBC/CTR。 #### 4.2 AAD 每条消息绑定: ```text tenant_id | topic | seqid | from | client_msg_id | key_version | content ``` `content` 是固定字段标识。AAD 使用固定字段顺序和长度前缀编码,不使用可形成拼接歧义的简单字符串。 #### 4.3 密钥层级 ```text HotGo字典根密钥(KEK) -> 加密每个商户、每个版本的DEK -> DEK加密该商户消息 ``` ### 5. HotGo 密钥管理 #### 5.1 字典根密钥 根密钥使用 `hg_sys_dict_data` 保存,必须满足: 1. 使用独立内部字典类型,不出现在通用字典管理页。 2. 后端字典列表、选项、导出和缓存接口必须服务端拒绝该类型,不得仅隐藏菜单。 3. 根密钥是 32 字节强随机值的 Base64 编码,不使用口令、UUID 或可读文本。 4. 根密钥只由 Admin Server 专用密钥服务读取。 5. 根密钥不回显、不写日志、不进入 Kafka,不通过通用配置接口返回。 #### 5.2 商户密钥表 新增 `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.3 后台管理页 只允许最高权限平台管理员: - 查看商户加密状态、当前密钥版本和最后轮换时间。 - 初始化商户密钥。 - 启用或暂停消息加密。 - 轮换密钥。 - 查看历史迁移进度。 页面不展示根密钥、DEK、`encrypted_dek` 和任何可恢复密钥的内容。 ### 6. IM 数据库改造 #### 6.1 `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=NULL`、`encrypted=1`。 - 旧数据:`content IS NOT NULL`、`encrypted=0`。 - 解密后必须恢复为原 `any` JSON 结构,不修改客户端协议。 - `head` 第一阶段不加密,但不得放置消息正文、联系方式等敏感明文。 #### 6.2 `im_group_messages` `im_group_messages` 是群消息派生查询表,不得另存一份明文。优先方案: - 仅保存索引、状态、审核和统计字段。 - 通过 `topic + seqid` 关联 `im_messages` 获取密文并解密。 - 迁移后清空或删除派生表中的明文 `content`。 如果必须保留独立内容列,则必须同步改为密文字段,禁止主表已加密而派生表仍保留明文。 #### 6.3 Schema 一致性 - MySQL 普通库和共享库/多商户 adapter 同时改造。 - 新建库 schema、启动 ensure 迁移和手工 SQL 脚本保持一致。 - `types.Message` 和 `types.MessageRevision` 增加密文元数据。 - Save、GetOne、GetAll、编辑、撤回、删除、修订记录和后台查询全部覆盖。 ### 7. IM Server 加解密模块 ```text server/messagecrypto/ cipher.go AES-256-GCM加解密 aad.go AAD稳定编码 key_provider.go 密钥获取接口 key_cache.go tenant/version内存缓存 serializer.go content规范序列化 ``` 不将 AES 逻辑或 HotGo 调用直接写入 MySQL adapter。adapter 负责存储密文模型,加解密由业务存储服务边界统一执行。 #### 7.1 新消息 ```text 完成现有校验 -> 完成内容审核 -> 分配seqid并组装AAD -> 序列化content -> 使用active DEK加密 -> 事务写入密文和元数据 -> 向在线订阅者广播内存中的原明文 ``` 审核必须发生在加密之前。广播使用请求内存中的明文,不执行“加密后立即解密”。 #### 7.2 历史消息和离线同步 ```text 读取数据库记录 -> encrypted=0:读原content -> encrypted=1:按tenant + key_version获取DEK -> 重建AAD -> GCM验签并解密 -> JSON反序列化 -> 按原Tinode协议返回 ``` GCM 验签失败必须当作数据篡改或密钥错误,不得忽略错误或返回部分内容。 ### 8. 密钥下发、缓存和轮换 #### 8.1 内部接口 | 方法 | 路径 | 用途 | | --- | --- | --- | | 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 确认新密钥可用 | 这些接口只允许 IM Server 服务身份调用,不得暴露给浏览器或通用 Admin API。 #### 8.2 缓存 - IM Server 不得每条消息都跨境请求 HotGo。 - 按 `tenant_id + key_version` 在内存缓存已解密 DEK。 - 新消息使用 `active` 密钥,历史消息按存储版本取密钥。 - 每个 Pod 持有本地缓存,Kafka 事件使全部 Pod 失效。 - HotGo 短时不可用时,已有内存密钥的 Pod 可继续工作。 - 无可用密钥时禁止写入新消息,不得降级为明文落库。 #### 8.3 轮换 1. Admin Server 生成新 DEK,使用 KEK 加密后以 `pending` 入库。 2. 通知 IM Server 拉取、解包并完成加解密自检。 3. IM Server 回传 ACK 后,在事务中将旧版本设为 `retired`,新版本设为 `active`。 4. 新消息立即使用新版本,旧消息仍按旧版本解密。 5. 历史转密钥作为独立低速任务,不阻塞即时通信。 ### 9. Admin 后台查看明文 - Admin Server 调用 IM internal admin-query,不直连 IM 库解密。 - IM Server 完成商户数据权限校验后,只解密当前分页数据。 - 记录管理员 ID、商户、IP、查询条件、返回数量、时间和 TraceID。 - 列表解密、详情解密和导出解密使用不同审计动作。 - 默认不提供明文批量导出。后续开放时必须增加独立权限和审批。 ### 10. 关键词查询 加密后 MySQL 无法对密文执行 `LIKE '%keyword%'`。第一阶段采用: 1. 查询必须限定商户和时间范围,并尽量限定用户或群号。 2. IM Server 使用明文元数据缩小候选集。 3. 对有上限的候选集批量解密,在内存中执行关键词匹配。 4. 超过时间范围或候选数限制时返回明确提示。 第一版不建设明文分词索引。如后续必须支持全库高性能检索,再单独评审 HMAC 盲索引。 ### 11. 历史数据迁移 #### 11.1 发布顺序 1. 发布 HotGo 密钥结构、密钥服务和管理页。 2. 为默认商户初始化第 1 版 DEK,但不开启加密写入。 3. 发布 IM Server 双读版本,验证密钥获取、加解密和所有读写链路。 4. 小流量开启新消息加密,旧数据仍可明文读取。 5. 全量开启新消息加密后,启动历史数据分批迁移。 6. 数量对账、抽样解密和 GCM 验签通过后,逐批清空明文 `content`。 7. 观察期结束后,再决定是否删除原明文字段。 #### 11.2 迁移任务 - 按主键或 `topic + seqid` 分批,不长时锁表。 - 每批独立事务,记录游标、成功数、失败数和最后错误。 - 重复执行幂等,`encrypted=1` 直接跳过。 - 先写密文并验证可解密,再将明文置空。 - 失败记录保留原数据并进入重试列表,不得丢失数据。 - 限制迁移速率,监控数据库负载和即时消息延迟。 ### 12. 错误处理 | 错误 | 说明 | 客户端/后台处理 | | --- | --- | --- | | `MESSAGE_ENCRYPTION_UNAVAILABLE` | 未取得当前加密密钥 | 暂停新消息写入,不明文降级 | | `MESSAGE_DECRYPT_FAILED` | 验签或解密失败 | 显示“消息暂时无法加载”,不返回乱码 | | `MESSAGE_KEY_VERSION_NOT_FOUND` | 历史密钥版本不存在 | 报警并阻止该记录解密 | 客户端不参与加解密,不返回密钥版本、Nonce、密文、解密堆栈或内部错误。 ### 13. 监控和审计 - 加密/解密成功数、失败数和耗时。 - 各商户当前密钥版本。 - 密钥获取成功率、缓存命中率和最后刷新时间。 - 明文历史记录剩余数、迁移速率和迁移失败数。 - GCM 验签失败产生高优先级告警。 - 后台明文查看记录管理员、商户、IP、查询范围、返回数量和 TraceID。 - 监控标签和日志不得保存消息正文、密文、Nonce、DEK 或 KEK。 ### 14. 开发任务拆分 #### 14.1 Admin Server 1. 根密钥字典隔离和通用接口防护。 2. `im_message_encryption_keys` 及业务逻辑。 3. 消息加密管理页面和最高权限控制。 4. 密钥下发、ACK 和 Kafka 失效事件。 5. 密钥初始化、轮换、退役和审计。 #### 14.2 IM Server 1. AES-256-GCM、AAD 和规范序列化模块。 2. 密钥获取、多版本缓存和多 Pod 失效。 3. 新消息加密写入和双读解密。 4. 单聊、群聊、修订、离线同步和后台查询覆盖。 5. 历史数据迁移、进度统计和失败重试。 6. 加解密监控、验签告警和后台解密审计。 ### 15. 测试重点 - 相同明文连续加密两次,Nonce 和密文不同。 - 修改密文、Nonce、AAD 或 `key_version` 任意一项后解密失败。 - JSON 字符串、数组、对象、Drafty、空值和超长内容往返一致。 - 在线发送、离线同步、多端漫游、编辑、回复、撤回和删除行为不变。 - 新旧消息混合分页和历史同步。 - 新旧密钥版本并存和多 Pod 切换。 - HotGo 暂时不可用、IM Pod 重启且无缓存、密钥版本缺失。 - 历史迁移中断、重试、幂等和数量对账。 - 旧密钥退役后可读旧消息,新消息只使用新密钥。 - 日志、Kafka、错误响应和监控中无明文和密钥。 ### 16. 验收标准 1. 开启商户的新消息在数据库中不存在可读正文。 2. 相同内容的 Nonce 和密文不同。 3. 篡改密文、Nonce 或 AAD 后必须验签失败。 4. 所有在线和历史消息功能保持原行为。 5. Admin 有权限账号可查看当页解密明文,无权限账号被拒绝。 6. 后台明文查看形成完整审计记录。 7. 旧密钥退役后仍可解密旧消息,新消息只使用新版本。 8. HotGo 短时断开时,有内存密钥的 Pod 可工作;无密钥时不得明文降级。 9. 历史数据迁移完成后,`im_messages`、`im_message_revisions`、`im_group_messages` 无明文副本。 10. 纯 AES 加解密不得成为消息链路主要瓶颈,密钥缓存命中时不产生跨境请求。 ### 17. 上线和回滚 1. 第一次只为测试商户或小流量用户开启新消息加密。 2. 加密开关必须按商户生效,不做全局一次性切换。 3. 观察写入失败、解密失败、同步延迟、数据库负载和密钥缓存。 4. 未开始历史迁移前,可停止新加密写入,但已加密消息仍必须保持可解密读取。 5. 禁止通过删除旧密钥实现回滚。 6. 历史迁移开始后,回滚必须保留双读能力,不允许只回滚数据库结构或只回滚代码。