# IM Kafka 接入方案 > 日期:2026-08-26 > 关联文档:[IM Kafka 接入范围识别](./IM-Kafka接入范围识别.md)、[IM 前后端逐图对接协议](./IM-前后端对接文档.md) > 目标:明确 Admin 后台、IM 服务、HotGo 控制库、租户业务库之间的 Kafka 接入边界、查询方式、鉴权方式和首批落地范围。 ## 0. 与范围识别文档的关系 [IM Kafka 接入范围识别](./IM-Kafka接入范围识别.md) 是本方案的前置输入,负责按前后端对接文档逐章识别哪些业务适合 Kafka、哪些仍应走 WebSocket/Redis/MySQL/OSS。本方案在该识别结果基础上,进一步确定服务边界、库访问边界、前后台交互模型、内部查询接口、鉴权方式和首批落地步骤。 两份文档分工如下: | 文档 | 作用 | | --- | --- | | [IM Kafka 接入范围识别](./IM-Kafka接入范围识别.md) | 回答“哪些业务该用 Kafka,哪些不该用”。 | | 本方案 | 回答“Kafka 怎样接入,Admin 与 IM 怎样交互,服务和库怎样隔离”。 | 实施时以范围识别文档的逐章清单确定业务覆盖面,以本方案确定技术实现方式。若后续新增需求,先补充范围识别,再同步调整本方案的 Topic、Command、内部接口和鉴权规则。 ## 1. 核心原则 系统按“服务和库一一对应”设计,不允许不同服务直连对方数据库。 | 服务 | 只能直接访问 | 说明 | | --- | --- | --- | | Admin/HotGo 服务 | `hotgo` 控制库 | 管理后台、租户主数据、后台配置、审核、操作单。 | | IM 服务 | 当前租户业务库 | 用户、好友、群、消息、文件引用、工作台业务数据。 | | Redis | IM 实时状态与缓存 | 在线状态、短状态、热点消息、缓存分层。 | | Kafka | Admin 与 IM 的命令、事件、结果桥 | 后台写操作、异步任务、业务事件、审计、缓存失效。 | | OSS | 文件对象存储 | 上传下载字节流不进入 Kafka。 | 最终边界: 1. Admin 不直连租户库。 2. IM 不直连 `hotgo` 库。 3. Admin 写租户业务操作统一通过 Kafka Command。 4. IM 写后台业务状态不直写 `hotgo`,通过 Kafka Result/Event 返回 Admin。 5. Admin 查询租户业务数据通过 IM 内部只读 API。 6. IM 查询租户配置、字典、路由通过 Admin 内部只读 API,或消费 Kafka 配置事件维护本地副本。 7. 在线实时投递继续走 Redis + WebSocket,Kafka 不替代 WS 主链路。 ## 2. 总体架构 ```text Admin/HotGo 服务 -> 写 hotgo 操作单 -> 生产 Kafka Command -> IM 服务消费 Command -> 写租户业务库 -> 生产 Kafka Result/Event -> Admin 消费 Result/Event -> 更新 hotgo 操作状态 Admin 查询租户业务数据 -> 调 IM 内部只读 API -> IM 查租户业务库 -> 返回 Admin IM 查询租户配置/字典/路由 -> 优先读本地缓存/本地副本 -> 缓存缺失时调 Admin 内部只读配置 API -> Admin 查 hotgo -> 返回 IM ``` ## 3. Kafka 使用范围 | 场景 | 是否使用 Kafka | 说明 | | --- | --- | --- | | Admin 后台群操作通知 C 端 | 是 | 后台写 hotgo 操作单,投 Kafka,IM 执行租户库变更并 WS 下发。 | | Admin 后台用户封禁、解封、重置密码 | 是 | IM 消费后更新租户库、踢在线、通知客户端。 | | Admin 后台租户配置、字典、功能开关变更 | 是 | Admin 写 hotgo 后投配置事件,IM 刷新缓存或本地副本。 | | 企业注册、实名、举报、反馈审核结果 | 是 | Admin 审核后投 Kafka,IM 生成通知或更新租户侧状态。 | | 工作台会议、日报、月报、信箱通知 | 是 | 状态变更、提醒、红点更新走 Kafka。 | | 私聊/群聊消息异步落库 | 是 | WS 收消息后完成鉴权、幂等和 seq,再投 Kafka 异步持久化。 | | 文件安全检测、恶意文件检测 | 是 | OSS 上传完成后投检测任务,消费者调用第三方并更新状态。 | | 离线 Push | 是 | 消息或业务事件落地后投 Push 任务。 | | 在线消息实时 fanout | 不作为主链路 | 继续 Redis Pub/Sub/Stream + WS。 | | 文件上传下载字节流 | 否 | 走 OSS/文件服务;Kafka 只处理上传完成后的事件。 | | 验证码、Action Token、限流、在线状态 | 否 | 继续 Redis。 | | 普通查询接口 | 否 | 走内部只读 API 或本服务数据库。 | ## 4. 前后台交互模型 ### 4.1 后台写租户业务 Admin 后台所有会影响租户业务库或 C 端展示的写操作,统一走 Kafka Command。 ```text Admin 1. 校验后台管理员权限 2. 写 hotgo.admin_command,状态 pending 3. 发送 Kafka command IM 4. 消费 command 5. 校验租户、对象、版本、权限和幂等 6. 写租户业务库 7. 必要时通过 WS 通知在线 C 端 8. 发送 Kafka result Admin 9. 消费 result 10. 更新 hotgo.admin_command 状态 ``` 典型操作: | 操作 | Command | IM 执行动作 | C 端通知 | | --- | --- | --- | --- | | 群资料修改 | `group.profile.update` | 更新群表和 Topic 资料 | `group.profile.updated` | | 设置/删除管理员 | `group.admin.set` / `group.admin.remove` | 更新成员角色 | `group.role.updated` | | 禁言/解禁 | `group.member.mute` / `group.member.unmute` | 更新禁言表 | `group.mute.updated` | | 踢人 | `group.member.remove` | 更新成员状态和订阅 | `group.member.removed` | | 解散群 | `group.delete` | 解散 Topic、撤销权限 | `group.deleted` | | 用户封禁 | `user.disable` | 更新用户状态、踢在线 | `user.disabled` | | 重置密码 | `user.password.reset` | 更新密码和 `auth_version` | 可选安全通知 | ### 4.2 后台查询租户业务 Kafka 不适合做普通查询。Admin 需要实时查看租户业务数据时,调用 IM 提供的内部只读 API。 只读 API 只允许 `GET`,不提供通用 SQL 或万能查询接口。 | 接口 | 用途 | | --- | --- | | `GET /internal/admin-query/tenants/{tenant_code}/users/{user_id}` | 查询租户用户详情。 | | `GET /internal/admin-query/tenants/{tenant_code}/groups/{topic}` | 查询群详情。 | | `GET /internal/admin-query/tenants/{tenant_code}/groups/{topic}/members` | 查询群成员。 | | `GET /internal/admin-query/tenants/{tenant_code}/messages/{topic}/{seq}` | 查询消息受控快照。 | | `GET /internal/admin-query/tenants/{tenant_code}/files/{file_id}` | 查询文件引用和审核状态。 | | `GET /internal/admin-query/tenants/{tenant_code}/reports/abuse/{report_id}/target` | 查询举报目标快照。 | | `GET /internal/admin-query/tenants/{tenant_code}/kafka/dead-letters` | 查询 IM 租户库 Kafka 死信列表。 | | `GET /internal/admin-query/tenants/{tenant_code}/kafka/dead-letters/{dead_id}` | 查询单条死信详情和原始 envelope。 | 后台列表、统计、高频页面优先使用 `hotgo` 摘要表。摘要由 IM 通过 Kafka 事件同步给 Admin,Admin 查询自己的库。 推荐摘要表: | 摘要表 | 来源 | | --- | --- | | `admin_im_user_snapshot` | IM 用户资料变更事件。 | | `admin_im_group_snapshot` | IM 群资料、成员数、状态变更事件。 | | `admin_im_group_member_stat` | 群成员统计事件。 | | `admin_im_file_audit_snapshot` | 文件审核和引用状态事件。 | | `admin_im_workbench_snapshot` | 工作台红点、会议、日报/月报状态事件。 | ### 4.3 IM 查询后台配置 IM 不直连 `hotgo`。IM 需要租户路由、租户状态、字典、第三方配置时,使用两种方式: 1. 常态:Admin 修改配置后发送 Kafka 配置事件,IM 消费后更新进程内缓存或本地副本。 2. 兜底:IM 缓存缺失或启动预热时,调用 Admin 内部只读配置 API。 | 接口 | 用途 | | --- | --- | | `GET /internal/im-config/tenants/{tenant_code}` | 查询租户状态、名称、路由标识。 | | `GET /internal/im-config/tenants/{tenant_code}/limits` | 查询人数、群数、上传大小等限制。 | | `GET /internal/im-config/tenants/{tenant_code}/features` | 查询功能开关。 | | `GET /internal/im-config/dictionaries?types=oss,file_security,sms,jpush,agora` | 查询字典配置。 | | `GET /internal/im-config/app/releases/latest` | 查询 App 版本配置。 | Admin 内部配置 API 只返回 IM 运行需要的字段,不返回后台管理员账号、菜单权限、审核私有字段。 ## 5. Topic 规划 | Topic | 用途 | | --- | --- | | `im.admin.command` | Admin 发给 IM 的租户业务写命令。 | | `im.admin.command.result` | IM 回传命令执行结果。 | | `im.config.event` | Admin 发给 IM 的配置、字典、租户状态变更。 | | `im.business.event` | IM 内部或 Admin 触发的 C 端业务事件。 | | `im.message.persist` | 私聊消息异步落库。 | | `im.group.message.persist` | 群聊消息异步落库。 | | `im.message.push` | 离线 Push 任务。 | | `im.file.security` | 文件内容安全、恶意文件检测任务。 | | `im.cache.invalidate` | 跨节点缓存失效。 | | `im.audit.event` | IM 与 Admin/HotGo 的跨系统审计事件。 | | `im.dead.letter` | 消费失败死信。 | 首版可合并部分 Topic,但事件体必须保留 `biz_type` 和 `event_type`,方便后续拆分。 ## 6. 消息结构 统一 Kafka Envelope: ```json { "event_id": "evt_01K...", "trace_id": "req_01K...", "tenant_code": "default", "source_service": "hotgo-admin", "target_service": "im", "event_type": "group.member.mute", "biz_type": "group", "biz_id": "grp...", "idempotency_key": "idem_01K...", "version": 3, "occurred_at": "2026-08-26T10:00:00.000Z", "actor": { "type": "admin", "id": "admin_1", "display_name": "管理员" }, "payload": {} } ``` Command Result: ```json { "event_id": "evt_result_01K...", "trace_id": "req_01K...", "tenant_code": "default", "command_id": "cmd_01K...", "command_event_id": "evt_01K...", "status": "succeeded", "reason": "", "message": "", "result": {}, "occurred_at": "2026-08-26T10:00:01.000Z" } ``` 失败时 `status=failed`,`reason` 使用稳定错误码,例如 `GROUP_VERSION_CONFLICT`、`FORBIDDEN`、`RESOURCE_NOT_FOUND`、`COMMAND_DUPLICATED`。 ## 7. 幂等、事务与重试 ### 7.1 生产端 Admin 写操作必须先落 `hotgo.admin_command`,再发送 Kafka。推荐使用 outbox 模式: 1. 同一事务写 `admin_command` 和 `admin_outbox`。 2. outbox poller 发送 Kafka。 3. Kafka 发送成功后标记 outbox 已发送。 4. 发送失败按退避重试。 IM 内部业务事件也按同样方式写租户库 outbox,避免“数据库成功、事件丢失”。 ### 7.2 消费端 每个消费者维护消费幂等记录: | 字段 | 说明 | | --- | --- | | `event_id` | Kafka 事件唯一 ID。 | | `consumer_group` | 消费组。 | | `status` | `processing/succeeded/failed/dead`。 | | `retry_count` | 重试次数。 | | `last_error` | 最近失败原因。 | | `created_at/updated_at` | 时间。 | 消费前先插入或锁定 `event_id + consumer_group`。已成功的事件直接跳过;处理中超时可重新抢占。业务表必须再用唯一键兜底,例如 `tenant + command_id`、`tenant + client_mid`、`tenant + topic + seq`。 ### 7.3 重试与死信 | 类型 | 策略 | | --- | --- | | 临时失败 | 指数退避重试。 | | 版本冲突 | 不自动重试,回传 failed,由 Admin 刷新后重新发命令。 | | 权限失败 | 不重试。 | | 对象不存在 | 不重试,除非明确是延迟同步。 | | 第三方失败 | 可重试,超过次数写死信和失败表。 | ### 7.4 消费失败处理流程 消费者收到消息后按固定流程处理: ```text 收到 Kafka 消息 -> 校验 Envelope 格式、签名字段、租户和事件类型 -> 抢占 event_id + consumer_group 幂等记录 -> 已 succeeded:提交 offset,跳过 -> processing 超时:允许重新抢占 -> 执行业务逻辑 -> 成功:标记 succeeded,提交 offset -> 可重试失败:retry_count + 1,按退避策略延迟重试 -> 超过最大重试:写入死信,标记 dead,提交 offset -> 不可重试失败:标记 failed 或 dead,必要时回传 command result,提交 offset ``` 重试建议参数: | 参数 | 建议值 | 说明 | | --- | --- | --- | | `max_retry_count` | 5~10 | 第三方接口可取高值,权限/参数错误不重试。 | | `retry_backoff` | 10s、30s、1m、5m、15m | 指数退避,可加随机抖动。 | | `processing_timeout` | 5m~30m | 超过后允许其他消费者重新抢占。 | | `dead_letter_retention` | 30~90 天 | 便于排障和人工补偿。 | ### 7.5 死信队列处理 进入死信表示该事件已超过自动恢复能力,系统不能再无限重试。死信必须形成“可查看、可诊断、可重放、可关闭”的闭环。 死信写入两处: 1. Kafka `im.dead.letter` Topic:保留原始事件,便于流式订阅、告警和后续自动补偿。 2. 数据库失败表:便于 Admin 后台查询、人工处理和审计。 推荐失败表字段: | 字段 | 说明 | | --- | --- | | `dead_id` | 死信记录 ID。 | | `event_id` | 原 Kafka 事件 ID。 | | `source_topic` | 原 Topic。 | | `consumer_group` | 失败消费组。 | | `tenant_code` | 租户编码。 | | `event_type` | 事件类型。 | | `biz_type` / `biz_id` | 业务对象。 | | `payload` | 原始 Envelope。 | | `retry_count` | 已重试次数。 | | `last_reason` | 稳定失败原因。 | | `last_error` | 最近错误摘要,敏感字段脱敏。 | | `status` | `dead/pending_replay/replayed/ignored/resolved`。 | | `replay_count` | 人工重放次数。 | | `created_at/updated_at` | 时间。 | | `resolved_by/resolved_at` | 人工处理人和处理时间。 | | `resolve_note` | 处理备注。 | 死信来源分类: | 分类 | 示例 | 默认处理 | | --- | --- | --- | | 临时资源失败超过上限 | DB 短暂不可用、第三方超时 | 修复环境后允许重放。 | | 业务状态冲突 | 群版本冲突、对象已被删除 | 不自动重放,Admin 刷新状态后重新发命令。 | | 权限失败 | 管理员无权限、租户停用 | 不重放,标记 ignored 或 failed result。 | | 参数错误 | Envelope 缺字段、payload 非法 | 不重放,修复生产方后关闭。 | | 第三方永久失败 | 文件违规、Push token 无效 | 按业务结果落状态,不重放或仅重放特定步骤。 | Admin 后台需要提供死信处理页: | 操作 | 说明 | | --- | --- | | 查看详情 | 展示事件、租户、业务对象、错误原因、重试次数和 trace_id。 | | 重放 | 将原事件或修正后的事件重新投递到原 Topic,生成新的 `event_id`,保留 `parent_event_id`。 | | 忽略 | 确认该事件无需继续处理,状态改为 `ignored`。 | | 标记已处理 | 线下补偿完成后改为 `resolved`。 | | 重新生成命令 | 对版本冲突类命令,不直接重放旧事件,而是基于最新状态创建新 Command。 | 重放规则: 1. 重放必须生成新的 `event_id`,原 `event_id` 保留在 `parent_event_id`。 2. 重放前重新校验租户状态、对象是否存在、操作者权限和业务版本。 3. 参数错误、权限失败、版本冲突默认禁止直接重放。 4. 文件安全检测、Push、审计写入等可重试任务允许重放。 5. 每次重放都写审计,记录处理人、原因、原事件和新事件。 死信告警: | 指标 | 建议 | | --- | --- | | 单 Topic 死信数 | 5 分钟内超过阈值告警。 | | 同一 `event_type` 连续失败 | 可能是代码或配置问题,立即告警。 | | 同一租户大量失败 | 检查租户库、配置或权限状态。 | | 死信积压时间 | 超过 24 小时未处理提醒。 | 对 Admin Command 类事件,进入死信后必须回传 `im.admin.command.result`,状态为 `failed` 或 `dead`,让后台操作单结束等待状态。否则后台会长期显示处理中。 ## 8. 内部接口鉴权 内部 API 鉴权非常关键,必须同时做网络层、服务层和业务层控制。 ### 8.1 网络层 1. `/internal/*` 只允许内网网关或内网地址访问。 2. 禁止公网直接暴露内部接口。 3. 有条件时启用 mTLS。 ### 8.2 服务签名 内部请求统一携带: | Header | 说明 | | --- | --- | | `X-Service-Name` | 调用方服务名,例如 `hotgo-admin` 或 `im-server`。 | | `X-Timestamp` | Unix 秒或毫秒时间戳。 | | `X-Nonce` | 随机串,短期内不可重复。 | | `X-Request-ID` | 请求追踪 ID。 | | `X-Signature` | HMAC 签名。 | 签名内容: ```text METHOD + "\n" + PATH_WITH_QUERY + "\n" + BODY_SHA256 + "\n" + X-Timestamp + "\n" + X-Nonce ``` 校验规则: 1. 时间窗建议 5 分钟。 2. `nonce` 写 Redis,TTL 5 分钟,防重放。 3. 每个服务独立 `service_id/service_secret`。 4. 每个服务配置允许访问的内部接口白名单。 ### 8.3 业务授权 Admin 调 IM 查询时,签名 payload 或 Header 中必须包含后台操作者信息: | 字段 | 说明 | | --- | --- | | `admin_user_id` | 后台用户 ID。 | | `admin_role` | 后台角色。 | | `admin_permissions` | 本次已校验过的权限列表。 | | `tenant_code` | 被查询租户。 | | `reason` | 查询原因或工单号。 | IM 仍需根据 `tenant_code + permission + resource` 做二次校验,并写内部查询审计。 IM 调 Admin 配置接口时,Admin 只校验服务身份和接口白名单,不返回超出 IM 运行需要的字段。 ## 9. 文件与工作台 ### 9.1 文件 文件上传下载不走 Kafka,OSS 是文件字节流主链路。当前只有消息落库使用 Kafka;文件生命周期改为同步处理: 1. `im.file.security`:仍可用于内容安全、恶意文件检测。 2. 消息附件引用绑定在消息落库 consumer 内同步执行。 3. 群文件登记/删除由对应 HTTP 业务接口同步落库。 ### 9.2 工作台 工作台查询不走 Kafka,状态变化和提醒当前也改为同步处理: 1. 会议创建、更新、取消、提醒、响应:业务接口同步生成通知/红点事件。 2. 日报/月报提交、撤回、重开、催办:业务接口同步生成通知/红点事件。 3. 总经理信箱提交、处理结果:业务接口同步处理。 4. 工作台模块配置变更:`im.config.event` 或 `im.cache.invalidate`。 ## 10. 首批落地建议 | 阶段 | 内容 | | --- | --- | | 第一步 | 增加 Kafka 配置、生产者、消费者、健康检查、统一 Envelope。 | | 第二步 | 增加消费幂等表、outbox 表、死信表或死信 Topic。 | | 第三步 | 接入 `im.admin.command` 和 `im.admin.command.result`,先覆盖后台群治理操作。 | | 第四步 | 增加 IM 内部只读查询 API,供 Admin 查用户、群、消息、文件快照。 | | 第五步 | 增加 Admin 内部配置 API,供 IM 查询租户配置、字典、第三方参数。 | | 第六步 | 接入文件安全检测、离线 Push、工作台事件。 | | 第七步 | 消息异步落库接入 `im.message.persist`,完善幂等和重试。 | ## 11. 分端任务拆分 ### 11.1 Admin/HotGo 侧任务 Admin 侧只访问 `hotgo` 库,负责后台操作入口、配置主数据、命令生产、结果消费和死信处理。 | 模块 | 任务 | 产出 | | --- | --- | --- | | Kafka 基础 | 增加 Kafka producer/consumer 配置、健康检查、统一 Envelope 组装。 | 可发送 `im.admin.command`、消费 `im.admin.command.result`。 | | 操作单 | 新增 `admin_command`、`admin_outbox` 表和状态机。 | `pending/running/succeeded/failed/dead` 操作状态。 | | Outbox | 后台写操作单与 outbox 同事务落库,poller 发送 Kafka。 | 防止 hotgo 写成功但 Kafka 丢事件。 | | Command 生产 | 群治理、用户封禁/解封、重置密码、审核结果、工作台配置等后台操作生成 Command。 | `im.admin.command` 事件。 | | Result 消费 | 消费 IM 回传结果并更新操作单。 | 后台页面可展示最终成功/失败原因。 | | 配置事件 | 租户配置、字典、功能开关、第三方配置变更后发送 `im.config.event`。 | IM 刷新缓存或本地副本。 | | 配置查询 API | 提供 `/internal/im-config/*` 只读接口。 | IM 启动预热和缓存缺失兜底查询。 | | 摘要表 | 建立用户、群、文件、工作台等后台摘要表。 | 后台列表统计优先查 hotgo。 | | 死信后台 | 提供死信查看、重放、忽略、标记已处理、重新生成命令。 | 运维闭环。 | | 内部鉴权 | 实现服务签名校验、nonce 防重放、接口白名单、审计。 | 保护 Admin 内部配置 API。 | | 审计 | 记录后台操作者、租户、资源、Command、Result、死信处理。 | 可追踪后台操作链路。 | Admin 首批优先级: 1. Kafka 基础配置、Envelope、producer。 2. `admin_command` + `admin_outbox`。 3. 群治理 Command 生产。 4. `im.admin.command.result` 消费。 5. `/internal/im-config/*` 配置查询 API。 6. 死信管理页。 ### 11.2 IM Server 侧任务 IM Server 侧只访问租户业务库,负责执行 Admin Command、维护租户业务状态、通过 WS 通知 C 端,并向 Admin 回传结果。 | 模块 | 任务 | 产出 | | --- | --- | --- | | Kafka 基础 | 增加 Kafka producer/consumer 配置、健康检查、统一 Envelope 解析。 | 可消费 Command、生产 Result/Event。 | | 消费幂等 | 新增租户库消费幂等表或统一消费日志。 | `event_id + consumer_group` 幂等。 | | Command 消费 | 消费 `im.admin.command`。 | 后台命令进入 IM 执行链路。 | | 群治理执行器 | 执行群资料、管理员、禁言、踢人、解散、群主转让等命令。 | 更新租户库和 Topic 状态。 | | 用户治理执行器 | 执行封禁、解封、重置密码、踢在线等命令。 | 更新用户状态和 `auth_version`。 | | Result 生产 | 执行成功/失败后发送 `im.admin.command.result`。 | Admin 操作单闭环。 | | WS 通知 | Command 成功后生产 C 端业务事件并投递在线客户端。 | `info.what="im|group"` 下发。 | | 配置消费 | 消费 `im.config.event`,刷新进程内缓存或本地副本。 | 租户配置、字典、第三方配置生效。 | | 配置兜底查询 | 调 Admin `/internal/im-config/*`。 | IM 启动预热和缓存缺失恢复。 | | 内部只读 API | 提供 `/internal/admin-query/*`。 | Admin 实时查询租户业务数据。 | | 文件任务 | 上传完成、附件绑定、群文件登记/删除同步处理。 | 文件审核、引用绑定、回收。 | | 消息异步落库 | 接入 `im.message.persist`、`im.group.message.persist`。 | 私聊/群聊消息削峰落库。 | | Push 任务 | 生产 `im.message.push`。 | 离线 Push 异步投递。 | | 死信处理 | 写失败表、投 `im.dead.letter`、Command 死信回传 Result。 | 失败可诊断、可补偿。 | | 内部鉴权 | 校验 Admin 调用 `/internal/admin-query/*` 的签名、权限和审计。 | 保护租户业务查询。 | IM Server 首批优先级: 1. Kafka 基础配置、Envelope、consumer/producer。 2. 消费幂等表和死信写入。 3. `im.admin.command` 群治理消费执行。 4. `im.admin.command.result` 回传。 5. WS 业务事件下发。 6. `/internal/admin-query/*` 只读查询 API。 ### 11.3 双方联调契约 | 契约 | Admin 负责 | IM Server 负责 | | --- | --- | --- | | Envelope 字段 | 生产完整字段,保证 `event_id/trace_id/tenant_code/event_type/idempotency_key`。 | 校验字段完整性和事件类型白名单。 | | Command 状态 | 创建操作单,展示状态。 | 回传成功、失败、死信结果。 | | 幂等 | 同一后台操作复用 `command_id/idempotency_key`。 | 同一 Command 重复消费只执行一次。 | | 错误码 | 展示 IM 返回的稳定 `reason`。 | 返回稳定 `reason`,不让 Admin 解析错误文本。 | | 查询接口 | 带管理员身份、权限、原因和签名。 | 校验签名、权限和租户资源,写审计。 | | 配置接口 | 提供最小必要配置字段。 | 不直连 hotgo,使用缓存、事件或内部 API。 | | 死信 | 提供处理入口和人工操作审计。 | 写死信、回传 Command dead/failed 状态。 | | Trace | 生成并传递 `trace_id`。 | 日志、Kafka、WS 下发沿用同一 `trace_id`。 | ### 11.4 首批联调用例 | 用例 | 预期 | | --- | --- | | Admin 禁言群成员 | Admin 操作单 pending,IM 消费后更新租户库,C 端收到 `group.mute.updated`,Admin 状态 succeeded。 | | Admin 重复提交同一禁言操作 | IM 只执行一次,Result 返回同一最终状态。 | | Admin 删除管理员但群版本冲突 | IM 返回 `GROUP_VERSION_CONFLICT`,Admin 操作单 failed,不自动重试。 | | IM 消费时临时 DB 失败 | 自动退避重试,未超限不进入死信。 | | IM 消费超过重试上限 | 写 `im.dead.letter`,Admin 操作单进入 dead/failed。 | | Admin 查询群成员 | Admin 调 `/internal/admin-query/*`,IM 校验签名和权限后返回只读数据。 | | IM 启动加载租户配置 | IM 优先读缓存,缺失时调 `/internal/im-config/*`,Admin 只返回必要配置。 | ### 11.5 IM Server 已接入的首批 Admin Command IM Server 首批消费 `im.admin.command`,执行后回传 `im.admin.command.result`。所有命令必须带 `tenant_code`,`payload.actor_user_id` 必须是租户内 IM 用户 ID,用于落群事件和 C 端通知。 | Command `event_type` | 说明 | 必填 payload | | --- | --- | --- | | `group.profile.update` | 修改群名称、头像、简介。 | `command_id,actor_user_id,topic,profile,version` | | `group.governance.update` | 修改进群审批、全员禁言、成员邀请等治理开关。 | `command_id,actor_user_id,topic,governance,version` | | `group.admin.set` | 设置一个或多个管理员。 | `command_id,actor_user_id,topic,user_ids` | | `group.admin.remove` | 删除管理员,降为普通成员。 | `command_id,actor_user_id,topic,user_id` | | `group.member.mute` | 禁言成员。 | `command_id,actor_user_id,topic,user_id,reason,expires_at` | | `group.member.unmute` | 解除禁言。 | `command_id,actor_user_id,topic,user_id` | | `group.member.remove` | 移出群成员。 | `command_id,actor_user_id,topic,user_id` | | `group.owner.transfer` | 转让群主。 | `command_id,actor_user_id,topic,target_user_id,version` | 当前 IM Server 消费 `im.admin.command` 时已经接入 `im_kafka_consume_log`: 1. 按 `event_id + consumer_group` 抢占消费。 2. 已成功事件直接跳过,不重复执行业务。 3. 处理中且未超时的事件跳过,避免并发重复执行。 4. 失败或处理超时的事件允许重新抢占。 5. 不可重试错误或超过重试次数后写入 `im_kafka_dead_letters`,并投递 `im.dead.letter`。 6. Admin Command 进入死信时回传 `im.admin.command.result`,`status=dead`。 ### 11.6 IM Server 已接入的配置事件 IM Server 首批消费 `im.config.event`,用于 Admin 修改 HotGo 配置后通知 IM 刷新进程内缓存。当前实现以“缓存失效”为主,收到事件后清除对应缓存,下次业务读取时重新加载。 | Config `event_type` | 说明 | IM 行为 | | --- | --- | --- | | `tenant.updated` | 租户基础信息变更。 | 清理租户 code/id 缓存。 | | `tenant.status.changed` | 租户启停、状态变化。 | 清理租户 code/id 缓存。 | | `tenant.limits.updated` | 租户人数、群数、上传限制等变化。 | 清理租户 code/id 缓存。 | | `tenant.features.updated` | 租户功能开关变化。 | 清理租户 code/id 缓存。 | | `dictionary.updated` | 字典配置变化。 | 清理第三方/字典配置缓存。 | | `third_party.updated` | OSS、文件安全、短信、极光、声网等第三方配置变化。 | 按 group 清理第三方配置缓存。 | | `registration.config.updated` | 注册、短信开关变化。 | 清理注册配置缓存。 | 事件示例: ```json { "event_id": "evt_01K_config", "trace_id": "req_01K", "tenant_code": "default", "source_service": "hotgo-admin", "target_service": "im-server", "event_type": "third_party.updated", "biz_type": "config", "biz_id": "im_third_party", "occurred_at": "2026-08-26T10:05:00.000Z", "payload": { "tenant_code": "default", "group": "im_third_party", "keys": ["oss.endpoint", "file_security.enabled"] } } ``` 禁言命令示例: ```json { "event_id": "evt_01K_mute", "trace_id": "req_01K", "tenant_code": "default", "source_service": "hotgo-admin", "target_service": "im-server", "event_type": "group.member.mute", "biz_type": "group", "biz_id": "grp...", "idempotency_key": "cmd_01K_mute", "occurred_at": "2026-08-26T10:00:00.000Z", "payload": { "command_id": "cmd_01K_mute", "actor_user_id": "usr_admin", "topic": "grp...", "user_id": "usr_target", "reason": "刷屏", "expires_at": null } } ``` ### 11.7 IM Server 已接入的内部只读查询 API 为满足“Admin 不直连租户库”的边界,IM Server 提供内部只读查询接口给 HotGo/Admin 调用。接口默认关闭,通过 `internal_api.enabled=true` 开启。 请求统一鉴权头: | Header | 说明 | | --- | --- | | `X-Service-Name` | 调用方服务名,如 `hotgo-admin`。 | | `X-Timestamp` | Unix 秒级时间戳,默认 300 秒时间窗。 | | `X-Nonce` | 一次性随机串,用于防重放。 | | `X-Request-ID` | 调用链路 ID。 | | `X-Signature` | HMAC-SHA256 签名,支持 hex 或 base64,可带 `sha256=` 前缀。 | nonce 防重放优先使用 Redis 短状态 `SETNX + TTL`,Redis 未配置时退化为单节点进程内防重放;生产多节点必须配置 Redis。 签名明文: ```text METHOD + "\n" + PATH_WITH_QUERY + "\n" + SHA256_HEX(BODY) + "\n" + X-Timestamp + "\n" + X-Nonce ``` 当前首批接口: | 接口 | 说明 | | --- | --- | | `GET /internal/admin-query/tenants/{tenant_code}/users/{user_id}` | 查询租户内用户基础资料。 | | `GET /internal/admin-query/tenants/{tenant_code}/groups/{topic}` | 查询群资料和治理配置。 | | `GET /internal/admin-query/tenants/{tenant_code}/groups/{topic}/members` | 查询群成员分页快照,支持 `limit/cursor/role/eligible_role/q`。 | | `GET /internal/admin-query/tenants/{tenant_code}/messages/{topic}/{seq_id}` | 查询单条私聊或群聊消息,`seq_id` 为 `BIGINT`。 | | `GET /internal/admin-query/tenants/{tenant_code}/files/{file_id}` | 查询文件元信息和存储位置。 | 配置示例: ```json { "internal_api": { "enabled": true, "time_window_seconds": 300, "services": { "hotgo-admin": { "secret": "replace-with-random-secret", "allow": ["admin_query"] } } } } ``` Docker 环境变量: | 环境变量 | 说明 | | --- | --- | | `INTERNAL_API_ENABLED` | 是否开启内部接口,默认 `false`。 | | `INTERNAL_API_TIME_WINDOW_SECONDS` | 签名时间窗。 | | `INTERNAL_API_HOTGO_SERVICE_NAME` | Admin 服务名,默认 `hotgo-admin`。 | | `INTERNAL_API_HOTGO_SECRET` | Admin 与 IM 共享签名密钥。 | ### 11.8 IM 访问 Admin 配置查询接口 为满足“IM 服务不直连 hotgo 库”的目标,IM Server 增加 Admin 内部配置查询客户端。该客户端默认关闭,开启后 `store.Tenants`、注册短信开关、第三方配置读取会优先请求 Admin 内部接口,失败时仍可回落旧数据库适配器,便于灰度迁移。 配置示例: ```json { "admin_internal_api": { "enabled": true, "base_url": "http://hotgo-admin:8080", "service_name": "im-server", "secret": "replace-with-random-secret", "timeout_ms": 3000 } } ``` 接口约定: | 接口 | 用途 | | --- | --- | | `GET /internal/im-config/tenants/by-code/{tenant_code}` | 按企业码获取租户路由信息,需返回 `id/code/name/state/db_name/db_dsn`。 | | `GET /internal/im-config/tenants/by-id/{tenant_id}` | 按租户 ID 获取租户路由信息。 | | `GET /internal/im-config/tenants?state=active&limit=1000` | 获取活跃租户列表,用于 Kafka Outbox 扫描。 | | `GET /internal/im-config/registration/sms` | 获取注册短信开关,返回 `{"enabled": true}`。 | | `GET /internal/im-config/third-party/{group}?keys=a,b` | 获取第三方配置,返回 `{"values": {"a": "..."}}`。 | 鉴权沿用内部接口 HMAC 方案,请求头为 `X-Service-Name`、`X-Timestamp`、`X-Nonce`、`X-Signature`;签名文本为 `METHOD + "\n" + RequestURI + "\n" + BodySHA256 + "\n" + Timestamp + "\n" + Nonce`。 Docker 环境变量: | 环境变量 | 说明 | | --- | --- | | `ADMIN_INTERNAL_API_ENABLED` | 是否启用 IM 到 Admin 的配置查询客户端,默认 `false`。 | | `ADMIN_INTERNAL_API_BASE_URL` | Admin 内部接口根地址。 | | `ADMIN_INTERNAL_API_SERVICE_NAME` | IM 服务名,默认 `im-server`。 | | `ADMIN_INTERNAL_API_SECRET` | Admin 与 IM 共享签名密钥。 | | `ADMIN_INTERNAL_API_TIMEOUT_MS` | 请求超时时间。 | ### 11.9 IM Server 已接入的消息异步落库 IM Server 已接入 `im.message.persist`,用于把在线发送链路和最终消息落库解耦。 当前处理方式: 1. WS/HTTP 发送入口先完成权限校验,并由 topic 内存态分配 `seq_id`。 2. Kafka 开启时,私聊发送入口直接生产 `im.message.persist`,群聊发送入口直接生产 `im.group.message.persist`,不查库、不落库、不写 DB outbox;客户端在 Kafka 写入成功后拿到已分配的 `seq_id`。 3. Kafka 关闭时,继续走原同步落库路径,兼容本地和未接 Kafka 环境。 4. 消费者按 `message_kind` 区分私聊与群聊: - `p2p` 写入 `im_messages`。 - `group` 写入 `im_group_messages`。 5. 消费侧复用 `im_kafka_consume_log` 做 `event_id + consumer_group` 幂等。 6. 失败按统一退避重试 3 次,超过上限先投递 `im.dead.letter`,再由 dead letter consumer 写入 `im_kafka_dead_letters`。 ### 11.10 IM Server 已接入的缓存失效消费 IM Server 已订阅 `im.cache.invalidate`,用于接收 Admin 或其他节点发出的通用缓存失效事件。消费侧复用 `im_kafka_consume_log` 做幂等,失败按统一退避重试,超过上限进入死信。 事件体示例: ```json { "event_id": "cache:tenant:default:1720000000", "event_type": "cache.tenant.invalidate", "tenant_code": "default", "payload": { "tenant_code": "default", "tenant_id": 1, "namespace": "tenant" } } ``` 支持的失效范围: | `event_type` / `namespace` | 处理方式 | | --- | --- | | `cache.tenant.invalidate` / `tenant` | 清理租户注册表与租户配置进程内缓存。 | | `cache.registration.invalidate` / `registration` | 清理注册短信开关缓存。 | | `cache.third_party.invalidate` / `third_party` | 按 `group` 清理第三方配置缓存。 | | `cache.dictionary.invalidate` / `dictionary` | 按 `group` 清理字典/配置类缓存。 | | `cache.redis_object.invalidate` / 任意 Redis `namespace` | 按 `keys` 删除用户、群、关系、权限、未读等 Redis 对象缓存。 | | `cache.group_hot_messages.invalidate` / `group_hot_messages` | 按 `topic` 和可选 `ranges` 清理群热消息缓存。 | ### 11.11 IM Server 已接入的死信处理闭环 IM Server 已将死信从“只写表”补齐为“可查询、可忽略、可解决、可重放”的处理闭环。Admin 不直连租户库,列表和详情通过 IM 内部只读 API 查询;写操作仍通过 `im.admin.command` 投递给 IM。 Admin 查询接口: | 接口 | 说明 | | --- | --- | | `GET /internal/admin-query/tenants/{tenant_code}/kafka/dead-letters?status=dead&limit=50&cursor=` | 死信分页列表,支持 `status/biz_type/biz_id/event_type` 过滤。 | | `GET /internal/admin-query/tenants/{tenant_code}/kafka/dead-letters/{dead_id}` | 单条死信详情,包含原始 Kafka envelope。 | Admin 处理命令: | `event_type` | 说明 | Payload 关键字段 | | --- | --- | --- | | `kafka.dead_letter.replay` | 删除原消费日志并按原 `source_topic` 重投 envelope,成功后标记 `replayed`。 | `dead_id`、`actor_user_id`、`note` | | `kafka.dead_letter.ignore` | 人工确认无需处理,标记 `ignored`。 | `dead_id`、`actor_user_id`、`note` | | `kafka.dead_letter.resolve` | 线下修复后标记 `resolved`。 | `dead_id`、`actor_user_id`、`note` | 重放规则: 1. IM 先把 `im_kafka_dead_letters.status` 改为 `pending_replay`,并删除对应 `im_kafka_consume_log`。 2. IM 按死信记录中的 `source_topic` 重新投递原 envelope。 3. 投递成功后把死信标记为 `replayed`,并累计 `replay_count`。 4. 如果重放后仍失败,普通消费者会再次写入新的 dead 记录,保留完整处理痕迹。 ### 11.12 IM Server 已接入的用户治理命令 IM Server 已在 `im.admin.command` 中接入用户治理类后台动作。Admin 侧写 hotgo 操作单并发送 Kafka Command,IM 消费后修改租户库用户状态、踢在线连接、清理缓存并回传 `im.admin.command.result`。 支持命令: | `event_type` | 行为 | Payload 关键字段 | | --- | --- | --- | | `user.ban` | 封禁用户,设置 `StateSuspended` 并踢所有在线会话。 | `actor_user_id`、`user_id` 或 `target_user_id`、`reason` | | `user.unban` | 解封用户,设置 `StateOK`。 | `actor_user_id`、`user_id` 或 `target_user_id`、`reason` | | `user.freeze` | 冻结账号,设置 `StateSuspended` 并踢所有在线会话。 | `actor_user_id`、`user_id` 或 `target_user_id`、`reason` | | `user.unfreeze` | 恢复账号,设置 `StateOK`。 | `actor_user_id`、`user_id` 或 `target_user_id`、`reason` | | `user.device.kick` | 踢指定设备在线连接,并删除设备推送记录。 | `actor_user_id`、`user_id` 或 `target_user_id`、`device_id`、`reason` | | `user.logout_all` | 踢用户所有在线连接,不修改账号状态。 | `actor_user_id`、`user_id` 或 `target_user_id`、`reason` | | `user.account.cancel` | 注销账号,默认软删除;`hard=true` 时硬删除。 | `actor_user_id`、`user_id` 或 `target_user_id`、`hard`、`reason` | | `user.account.delete` | 删除账号,按硬删除处理。 | `actor_user_id`、`user_id` 或 `target_user_id`、`reason` | 命令示例: ```json { "event_id": "admin:user:ban:default:usrabc:001", "tenant_code": "default", "source_service": "hotgo-admin", "target_service": "im-server", "event_type": "user.ban", "biz_type": "user_governance", "biz_id": "usrabc", "idempotency_key": "admin-operation-id", "occurred_at": "2026-08-26T10:10:00.000Z", "payload": { "command_id": "admin-operation-id", "actor_user_id": "usrAdmin", "user_id": "usrabc", "reason": "违规处理" } } ``` 处理说明: 1. 封禁/冻结共用 Tinode 用户状态 `StateSuspended`;解封/恢复共用 `StateOK`。 2. 状态变更后 IM 会清理用户缓存,并向本节点 Hub 投递用户状态变更。 3. 踢设备只影响匹配 `device_id` 的在线连接,同时删除该设备 Push token 记录。 4. 全端登出只踢在线连接,不修改用户状态。 5. 注销/删除会停止用户相关 Topic、清理缓存并调用 `store.Users.Delete`。 6. 命令执行成功后统一回传 `im.admin.command.result`;跨系统审计通过 `im.audit.event` 投递给 Admin/HotGo 同步落表。 7. 群消息消费成功后继续写入 Redis 热消息缓存,便于 24 小时内热点拉取。 事件示例: ```json { "event_id": "msg.persist.1.grpabc.10086", "tenant_code": "default", "source_service": "im-server", "target_service": "im-server", "event_type": "message.persist.group", "biz_type": "message", "biz_id": "grpabc:10086", "idempotency_key": "client-mid-001", "version": 10086, "occurred_at": "2026-08-26T10:10:00.000Z", "actor": { "type": "user", "id": "usr..." }, "payload": { "message_kind": "group", "read_by_sender": true, "attachment_urls": [], "message": { "SeqId": 10086, "Topic": "grpabc", "From": "abcdef", "Head": { "x-im-client-mid": "client-mid-001" }, "Content": { "txt": "hello" } } } } ``` ### 11.9 IM Server 已移除 Kafka DB Outbox IM Server 不再维护租户库 `im_kafka_outbox`,Kafka 事件统一由发送入口直接生产。生产侧使用 Kafka writer 的 `max_retry_count` 控制发送重试;消费侧使用 `im_kafka_consume_log` 做处理幂等和失败重试,超过上限后进入 `im.dead.letter`。 本轮暂不实现 `im.message.push` 离线 Push Kafka 化,已记录到 [IM 第一至十一章后续增强项](./IM-未实现后续增强项.md)。 ### 11.10 IM Server 文件生命周期同步处理 IM Server 不再生产或消费 `im.file.lifecycle`。文件相关的小流量动作改为同步执行。 当前事件: | `event_type` | 生产时机 | 消费行为 | | --- | --- | --- | | `file.upload.completed` | HTTP/gRPC 上传成功并完成文件记录后。 | 当前无需 Kafka 消费。 | | `file.attachments.bind` | 消息落库 consumer 成功后,如果消息携带附件。 | 同步写 `im_filemsglinks` 或 `im_group_files`。 | | `group.file.created` | 群文件 API 登记成功后。 | HTTP 业务接口同步完成。 | | `group.file.deleted` | 群文件 API 删除成功后。 | HTTP 业务接口同步完成。 | 说明: 1. 消息附件绑定在消息落库 consumer 内同步完成。 2. Kafka 关闭时,仍沿用原同步消息落库与附件绑定路径,兼容本地环境。 3. 文件对象字节流仍走 OSS/文件服务,Kafka 不承载上传/下载内容。 4. 合规删除、临时文件 TTL 回收、OSS 删除动作仍保留为后续深化项。 ### 11.11 IM Server 工作台事件消费处理 工作台拆分为独立 `im_workbench` 服务后,工作台业务变更由 `im_workbench` 生产 `im.workbench.event`,IM Server 新增独立 Kafka consumer 消费该 Topic,并转为系统通知和 C 端业务实时事件。 说明:IM Server 当前保留旧工作台 HTTP 代码和同步处理函数,仅作为迁移过渡兼容。待 `im_workbench` 全部接口稳定后,再统一删除 IM Server 内的旧工作台业务代码。 当前生产范围: | 模块 | 事件 | | --- | --- | | 会议 | `meeting.created`、`meeting.updated`、`meeting.canceled`、`meeting.reminded`、`meeting.response.updated` | | 日报 | `daily_report.draft.created`、`daily_report.draft.saved`、`daily_report.submitted`、`daily_report.reminded` | | 月报 | `monthly_report.draft.created`、`monthly_report.draft.saved`、`monthly_report.submitted`、`monthly_report.reopened` | | 总经理信箱 | `mailbox.submitted` | 当前消费行为: 1. `im_workbench` 直接发送 JSON 事件到 `im.workbench.event`,Topic 配置键为 `workbench_external`。 2. IM Server 按 `event_id + consumer_group` 做消费幂等。 3. 按 `target_user_ids` 为非发布人生成系统通知。 4. 写入 `im_business_events_outbox`,由现有 WS 业务事件投递器下发在线客户端。 5. 消费失败按统一 Kafka 重试逻辑处理,超过最大重试次数后进入 `im.dead.letter`。 ### 11.12 IM Server 已接入的审计事件 IM Server 对跨系统审计仍生产 `im.audit.event`。该 topic 由 Admin/HotGo 消费并同步写审计表;IM Server 本地不消费该 topic,也不通过它做内部异步落库。 当前审计范围: | 类型 | 事件 | | --- | --- | | 工作台 | 会议、日报、月报、总经理信箱的状态变更。 | | Admin Command | 后台群治理命令执行成功。 | | Kafka 死信 | `kafka.dead_letter.created`。 | | 文件生命周期 | 上传完成、附件绑定、群文件新增/删除。 | 审计事件当前不在 IM 内部通过 Kafka 落审计表,只作为 IM -> Admin/HotGo 的跨系统写事件。 ## 12. 风险与约束 1. Kafka 是异步链路,Admin 后台命令不能默认立即成功,只能显示 `pending/running/succeeded/failed`。 2. 查询不能通过 Kafka 常规化实现,高频实时查询必须走内部只读 API 或 hotgo 摘要表。 3. 内部 API 不能暴露公网,签名、nonce、防重放和审计必须首批完成。 4. 配置事件可能丢失或延迟,IM 必须保留启动预热和缓存缺失时的 Admin 配置查询兜底。 5. Kafka 消费可能重复,所有消费者和业务写入都必须幂等。 6. Admin 和 IM 不跨库直连后,排障要依赖 `trace_id` 串联 hotgo 操作单、Kafka 事件、IM 执行日志和 WS 下发日志。