Files
im_server/docs/IM-服务端架构与新功能开发指南.md
T

471 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# IM 服务端架构与新功能开发指南
> 文档状态:按 2026-09-08 代码实现整理
> 适用项目:`im_server`
> 文档目标:让新接手的开发者能够快速判断一个功能应放在哪里、如何调用、如何存储、如何做多节点一致性,以及如何验收。
## 1. 系统定位
`im_server` 是基于 Tinode 扩展的多租户 IM 业务服务,同时承担两类职责:
1. IM 实时核心:WebSocket 连接、登录会话、Topic 订阅、消息路由、在线状态、音视频信令。
2. C 端产品业务:注册、安全、好友、群治理、收藏、文件、工作台、会议、日报、举报等 HTTP API。
系统不是传统的 `controller -> service -> repository` 三层单体。它的核心是“Topic Actor 实时模型 + HTTP 业务扩展层 + Store 统一持久化边界”。开发新功能时必须先判断它属于实时 Topic 链路,还是 HTTP 业务链路,或者同时涉及两者。
## 2. 系统边界
### 2.1 服务与数据库归属
| 服务 | 直连数据库 | 负责数据 |
| --- | --- | --- |
| IM Server | 租户业务库,默认库为 `tinode` | 用户、认证、Topic、订阅、私聊/群聊消息、群业务、好友关系、文件元数据、C 端业务数据 |
| Admin / HotGo | `hotgo` | 租户注册表、系统配置、字典、后台账号、菜单权限、后台命令 Outbox、管理端摘要数据 |
原则:服务与库一一对应,Admin 不直连租户库,IM 不直连 `hotgo`。跨系统写操作通过 Kafka 命令/事件,必须同步返回的少量查询通过受控的内部 HTTP API。
### 2.2 租户隔离
目标模型是“一个租户一个业务库”:
- `tenant_id` 是请求上下文和路由键,不要因为租户库内没有 `tenant_id` 列就在代码中丢掉租户上下文。
- 所有业务 Store 调用必须显式传递 `types.TenantID`。
- `store.ForTenant` 和 `tenantBusinessAdapter` 负责验证租户与选择租户适配器。
- MySQL 的 `TenantRoutedAdapter.TenantAdapterFor` 负责将租户路由到对应连接池。
- 任何无租户上下文的业务查询都应失败关闭,不允许回退到旧的无租户查询。
## 3. 技术栈
| 领域 | 当前实现 | 用途 |
| --- | --- | --- |
| 语言 | Go 1.26 | 服务端主体 |
| 实时协议 | Tinode JSON Protocol、WebSocket、Long Polling、gRPC | 长连接、Topic 订阅、消息和集群通信 |
| HTTP | `net/http` + `http.ServeMux` | C 端 REST API、文件 API、内部 API |
| 数据库 | MySQL + `database/sql` + `sqlx` | 租户业务最终状态 |
| 热状态/缓存 | Redis 7,`go-redis/v9` | 在线状态、Seq、幂等、群热消息、对象缓存、关系 Set、限流、跨节点 fanout |
| 异步事件 | Kafka,`segmentio/kafka-go` | 跨系统命令、消息异步持久化、审计、缓存失效、失败重试与死信 |
| 文件 | 阿里云 OSS,保留 FS/S3 适配器 | 文件对象存储,业务库只存元数据和引用 |
| 文件安全 | 阿里云内容安全 | 上传完成后的异步审核 |
| 离线推送 | JPush,同时保留 FCM/TNPG 插件能力 | 离线提醒;客户端重连后按 Topic seq 补偿 |
| 音视频 | Tinode 通话信令 + 声网 RTC Token | 呼叫邀请/接听/挂断与 RTC 加入 |
| 认证 | basic/token/code/rest 可插拔认证器 | 密码、Token、验证码和外部认证 |
| 可观测性 | Go `log`、expvar、Prometheus exporter、pprof | 日志、运行指标和性能诊断 |
## 4. 运行时架构
```text
Client
|-- WebSocket /v0/channels
| -> Session -> Hub -> Topic actor -> Store/Redis
| |-> local sessions
| `-> Redis fanout -> other IM nodes -> WebSocket
|
|-- HTTP /v1/im/*
| -> protocol middleware -> bearer/API key/tenant
| -> handler -> Store mapper -> tenant adapter -> tenant MySQL
| |-> Redis cache/short state
| `-> business event -> online WebSocket/Kafka
|
`-- Upload/Download
-> media handler -> OSS
-> file metadata/link -> tenant MySQL
`-> file security task -> Kafka -> Aliyun security
Admin / HotGo
|-- Kafka admin command/config event -> IM consumer -> tenant business mutation
`-- signed internal HTTP <-------> IM internal read API
```
### 4.1 启动顺序
`server/main.go` 是服务入口,主要顺序为:
1. 读取 JSON with comments 格式的 `meetingim.conf`/`tinode.conf`。
2. 初始化日志、expvar、pprof 和集群 worker ID。
3. 打开 Store,校验数据库版本与租户适配能力,同时初始化 Redis runtime。
4. 初始化 Kafka、IM 内部 API 和 Admin 内部配置 API 客户端。
5. 初始化认证器、验证器、文件处理器、Push 和音视频。
6. 创建 `SessionStore` 和 `Hub`,启动 Redis 群消息 fanout 订阅。
7. 启动 Kafka Admin Command、Config Event、Cache Invalidate、Message Persist 和 Dead Letter 消费者。
8. 启动集群、gRPC、HTTP/WebSocket 路由和优雅退出。
任何新增的基础组件都应明确放在这个顺序中,初始化失败时应快速终止启动,不要让必需依赖在不可用状态下继续运行。
## 5. 核心业务模型
### 5.1 Session
`Session` 表示一条客户端连接,保存租户、用户、设备、认证等级、已订阅 Topic 和输出队列。一个用户可以同时有多个 Session。
登录和断线时需同步 Redis 在线状态;心跳用于刷新 presence TTL。不应仅依赖本进程 Session 判断全局在线状态。
### 5.2 Hub
`Hub` 是 Topic 路由中心,核心通道包括:
| 通道 | 用途 |
| --- | --- |
| `join` | 加载/创建 Topic,将 Session 附加到 Topic |
| `routeCli` | 客户端消息路由到 Topic |
| `routeSrv` | 服务端业务消息路由到 Topic/Session |
| `meta` | 未订阅 Topic 的元数据查询 |
| `unreg` | 卸载或删除 Topic |
| `userStatus` | 用户停用/恢复等状态变更 |
Topic 索引不是纯字符串,而是 `types.TopicKey{TenantID, Topic}`,以防止不同租户的同名 Topic 串数据。
### 5.3 Topic Actor
`Topic` 是会话和消息处理单元,通过 channel 串行处理加入、退出、客户端消息、服务端消息和元数据操作。主要分类包括:
| Topic | 含义 |
| --- | --- |
| `me` | 用户主 Topic,会话列表和用户级 presence |
| `fnd` | 搜索/发现 Topic |
| `usr...` | 客户端可见的 P2P 对端标识,入库前需归一化为真实 P2P Topic |
| `grp...` | 群聊 Topic |
| `chn...` | 频道 Topic |
| `slf...` | 个人收藏/保存消息 Topic |
| `sys...` | 系统通知 Topic |
涉及消息发送、已读、撤回、编辑、通话信令、在线投递时,优先复用 Topic/Session 链路,不要另造一套无状态 HTTP 投递逻辑。
## 6. 四类标准调用链路
### 6.1 WebSocket 实时消息
```text
Client packet
-> hdl_websock.go 解码
-> Session 认证/租户上下文
-> Hub.join / Hub.routeCli
-> Topic 权限和业务校验
-> Redis 原子 Seq + client_mid 幂等
-> 本地 Session 实时投递
-> Redis Pub/Sub 跨节点 fanout
-> Kafka persist 异步落 MySQL
```
群消息使用 `im_group_messages`,私聊仍使用私聊消息表。`SeqId` 全链路必须使用 `int64/BIGINT`,群 Topic seq 由 Redis 原子递增,数据库只作基线与最终持久化。
群热消息可在 Redis 保留 24 小时左右,历史补偿最终以 MySQL 为准。离线用户只接收 Push 提醒,重连后按 Topic seq 拉取缺口。
### 6.2 C 端 HTTP 业务请求
```text
/v1/im/*
-> withIMHTTPProtocol
-> API key + X-IM-Schema + X-Request-ID
-> Bearer token -> principal{tenant_id, uid}
-> 写请求 Idempotency-Key 中间件
-> serveIM... handler
-> store.<Domain> mapper
-> TenantBusinessAdapter
-> tenant MySQL / Redis
-> writeIMSuccess / writeIMError
```
所有已认证的 `POST/PUT/PATCH/DELETE` 默认要求 `Idempotency-Key`,幂等结果保留 24 小时。同一 key 使用不同请求体必须返回冲突,已完成请求应重放原响应。
HTTP 统一响应包含 `code/data/reason/message/details/request_id/server_time`。业务错误应返回稳定 `reason`,不应将 SQL 或内部异常直接暴露给客户端。
### 6.3 Admin 后台写操作
```text
Admin API
-> hotgo 本地权限校验
-> 同事务写 command + outbox
-> Kafka im.admin.command
-> IM consumer 校验 tenant/event/actor
-> event_id + consumer_group 幂等抢占
-> 租户库业务变更
-> 缓存失效 + Topic/Session 同步
-> Kafka im.admin.command.result
-> Admin 更新命令结果
```
不可重试的参数、权限、版本冲突应直接返回稳定失败原因。临时故障按退避策略重试,超过上限写 `im.dead.letter` 和租户失败表。人工重放必须生成新 `event_id`,并使用 `parent_event_id` 串起审计链。
### 6.4 文件链路
```text
Client request upload
-> IM 创建文件元数据
-> OSS 上传/回调完成
-> 租户库更新文件状态
-> 绑定消息/群文件/业务引用
-> Kafka im.file.security
-> 阿里云安全检测
-> 更新审核状态并通知用户
```
文件字节流不经 Kafka,Kafka 只传递文件 ID、OSS 对象信息和审核任务。任何业务文件都必须建立引用记录,否则垃圾回收可能将它当作孤儿文件删除。
## 7. 数据分层与一致性
### 7.1 进程内缓存
适合低频变更、全局或租户级、可短时间最终一致的数据:
- 租户基本配置和状态。
- 注册短信开关。
- 字典和第三方配置(OSS、文件安全、短信、JPush、Agora)。
更新来源为定时刷新和 Kafka `im.config.event`/`im.cache.invalidate`。进程内缓存不应与 Redis 重复缓存同一份高频业务对象。
### 7.2 Redis
Redis 用于跨节点共享且高频的数据:
- 用户在线状态、Session 和群成员节点索引。
- 群消息原子 `SeqId`、`client_mid` 唯一占位和状态。
- 24 小时左右的群热消息。
- 用户、Topic、Subscription、群设置、群成员、成员分页快照和未读数缓存。
- 好友/黑名单 Set。
- 验证码、Action Token、临时占位、限流等 TTL 状态。
- 群消息 Redis Pub/Sub 跨节点实时 fanout。
Redis key 必须包含统一前缀和租户维度,不允许跨租户共用业务 key。写 MySQL 成功后必须失效或刷新对应 Redis 数据,多节点通过 Kafka 失效事件收敛。
### 7.3 MySQL
MySQL 是最终业务状态、历史查询和审计依据。以下数据不得仅存 Redis:
- 用户、凭据、好友、黑名单、Topic、订阅和群治理最终状态。
- 私聊/群聊历史消息、撤回和编辑版本。
- 文件元数据、业务引用和安全审核结果。
- HTTP 幂等结果、Kafka 消费幂等、死信和审计数据。
### 7.4 一致性顺序
普通业务写的推荐顺序:
1. 校验租户、身份、权限、幂等键和业务版本。
2. 在同一事务中更新 MySQL 业务状态及 outbox(需要可靠事件时)。
3. 提交事务。
4. 失效本节点 Redis/进程内缓存。
5. 投递 Kafka 失效或业务事件,通知其他节点和客户端。
如果业务要求“先实时到达,再异步落库”,则必须先完成 Redis 幂等占位和 seq 分配,投递 Kafka 持久化事件,并为失败准备重试和死信补偿。
## 8. Kafka 责任边界
| Topic | 主要用途 |
| --- | --- |
| `im.admin.command` | Admin 到 IM 的业务写命令 |
| `im.admin.command.result` | IM 回传命令结果 |
| `im.config.event` | 租户、字典、第三方配置变更 |
| `im.business.event` | 好友、群、会议、工作台等 C 端业务事件 |
| `im.message.persist` | 私聊消息异步落库 |
| `im.group.message.persist` | 群聊消息异步落库 |
| `im.message.push` | 离线 Push 任务,属增强链路 |
| `im.file.security` | 文件安全检测 |
| `im.cache.invalidate` | 多节点缓存失效 |
| `im.audit.event` | 跨系统审计 |
| `im.dead.letter` | 超过重试上限的事件 |
Kafka 不用于 WebSocket 建连、验证码、在线状态、实时群消息 fanout、OSS 字节流或必须当场返回结果的查询。
统一 Envelope 至少包含:`event_id`、`trace_id`、`tenant_id/tenant_code`、`source_service`、`target_service`、`event_type`、`biz_type`、`biz_id`、`idempotency_key`、`version`、`occurred_at`、`actor`、`payload`。
## 9. 代码组织
| 路径 | 职责 | 新功能常见修改 |
| --- | --- | --- |
| `server/main.go` | 进程入口、组件初始化、HTTP 路由注册 | 新增路由或新基础组件启动 |
| `server/session.go` | 连接、登录、请求分发、设备和 Session 状态 | 账号登录或连接级功能 |
| `server/hub.go` | Topic 加载与全局消息路由 | 路由、Topic 生命周期 |
| `server/topic.go` / `init_topic.go` | Topic actor、权限、订阅和消息处理 | 消息、群权限、Topic 内实时行为 |
| `server/datamodel.go` | WebSocket 客户端/服务端协议结构 | 协议字段或服务端推送类型 |
| `server/im_http.go` | C 端产品 HTTP API 的路由处理与通用约定 | 大部分 C 端业务入口 |
| `server/internal_api.go` | IM 向 Admin 提供的签名内部查询 API | 后台需要同步读取租户数据 |
| `server/admin_internal_config.go` | IM 调用 Admin 内部配置 API | IM 需要 hotgo 配置兜底查询 |
| `server/kafka_*.go` | Kafka runtime、生产、消费、重试、死信和各业务 handler | 跨系统或异步事件 |
| `server/im_business_events.go` | 业务事件持久化与在线投递 | 需主动推给 C 端的状态变更 |
| `server/redis_fanout.go` | 群消息跨节点实时投递 | 群大并发/未加载 Topic 投递 |
| `server/store/types/` | 业务实体、值对象、查询参数和稳定错误 | 先定义领域数据结构 |
| `server/store/store.go` | Store mapper、参数验证、租户边界、缓存编排 | 对 handler 提供统一持久化 API |
| `server/store/infrastructure.go` | HTTP 幂等、业务事件、Kafka 幂等/死信 Store | 可靠基础链路 |
| `server/store/group_message_runtime.go` | Redis seq、唯一 key、热消息、presence、缓存和限流 | Redis 新 key 或新的 TTL 状态 |
| `server/db/adapter.go` | 持久化能力接口 | 新增 Store 数据能力 |
| `server/db/mysql/tenant_adapter.go` | MySQL 租户业务 SQL 实现 | 业务 CRUD/事务 |
| `server/db/mysql/*_schema.go` | 扩展业务表建表/升级逻辑 | 新表、列、索引 |
| `server/media/` | FS/OSS/S3 文件处理插件 | 文件存储能力 |
| `server/filesafety/` | 文件安全适配器 | 文件检测能力 |
| `server/push/` | 离线 Push 插件 | 厂商 Push 接入 |
| `server/auth/` / `server/validate/` | 认证和凭据验证插件 | 登录或验证方式 |
| `server/*_test.go` | 单元与链路测试 | handler、Topic、Kafka、Redis 逻辑测试 |
| `server/db/mysql/*_test.go` | MySQL schema 和 SQL 回归 | 数据库变更测试 |
## 10. 新功能放置决策
| 问题 | 选择 |
| --- | --- |
| 是否是客户端建连、订阅、发消息或通话实时信令? | WebSocket + Session/Hub/Topic |
| 是否是 C 端业务 CRUD、列表、搜索、设置或审批? | `/v1/im/*` HTTP + Store |
| 是否是 Admin 对租户业务的写操作? | Admin 本地 command/outbox + Kafka + IM consumer |
| Admin 是否必须当场读取租户详情? | IM 受控内部 GET API |
| IM 是否需要读取 hotgo 的低频配置? | 进程内缓存 + Kafka 刷新 + Admin 内部 API 兜底 |
| 是否需要跨节点高频共享、TTL 或原子计数? | Redis |
| 是否需要最终可追溯的业务状态? | 租户 MySQL |
| 是否跨系统、可异步、需重试/削峰/审计? | Kafka |
| 是否为在线群消息实时扩散? | Redis Pub/Sub + WS,不用 Kafka 做主链路 |
| 是否为文件本体? | OSS;MySQL 只存元数据/引用 |
## 11. 新功能标准开发流程
### 11.1 需求分析
1. 从前后端对接文档提取请求、响应、权限、状态机、幂等、并发和通知要求。
2. 标出数据归属:`hotgo` 公共数据或租户库业务数据。
3. 选择入口:HTTP、WebSocket、Kafka Admin Command 或内部 API。
4. 列出需要同步的状态:MySQL、Redis、Topic 内存、进程内缓存、C 端推送。
5. 明确失败类型:可重试、不可重试、并发冲突、部分成功和补偿。
### 11.2 领域与数据库
1. 在 `server/store/types/` 按业务域定义实体、状态常量、查询参数和结果。
2. 在 `server/db/adapter.go` 增加最小必要的租户持久化接口,方法必须携带 `TenantID`。
3. 在 MySQL 适配器实现 SQL;复杂写操作使用事务,状态迁移在 SQL `WHERE` 中校验旧状态/版本。
4. 在对应 `*_schema.go` 添加幂等建表/升级 SQL,为高频查询、唯一幂等键和状态扫描建索引。
5. 在 `server/store/store.go` 增加 mapper,负责参数验证、调用 adapter 和缓存编排;handler 不应直接拼 SQL。
表命名遵循当前租户库风格,类型必须与 Go 全链路一致,尤其是 ID、seq、version 使用 `BIGINT/int64`。面向 JavaScript 客户端的 64 位 ID 优先以字符串输出,避免精度丢失。
### 11.3 API 或协议层
HTTP 功能:
1. 在 `server/im_http.go` 定义独立 request/response 结构,不要直接暴露数据库 row。
2. 使用 `decodeIMJSONBody`、`authenticateIMHTTPBearer`、`requireIMGroupMember` 等公共函数做解码、认证和权限检查。
3. 在 `server/main.go` 注册具体路由,路由顺序要避免通配前缀吞掉更具体的路径。
4. 使用 `writeIMSuccess`/`writeIMError` 统一输出,新增稳定业务 `reason`。
5. 资源更新需并发保护时使用 version/`If-Match`,不允许用 `version=0` 绕过乐观锁。
WebSocket 功能:
1. 在 `datamodel.go` 增加最小协议字段,考虑 JSON 和 protobuf 转换兼容。
2. 在 Session 做连接级验证,在 Topic 做资源权限与业务状态验证。
3. 所有可重发消息必须有 `client_mid`/业务幂等键。
4. 成功后同时考虑本节点 Session、跨节点 fanout、离线 Push 与历史补偿。
### 11.4 缓存、事件与审计
1. 对高频读对象选择 cache-aside:先 Redis,miss 后读 MySQL 并回填。
2. 所有写方法列出需失效的 key,包括对象 key、关系 Set、分页快照和未读数。
3. 需可靠异步处理时同事务写 outbox,消费端使用 `event_id + consumer_group` 幂等。
4. 影响管理权限、账号安全、消息内容、文件处置或人工重放的操作要写审计。
5. 业务状态变更需立即告知 C 端时,创建业务事件并通过已认证服务端链路投递,不信任客户端伪造的系统消息。
### 11.5 测试与文档
每个功能至少覆盖:
1. 正常路径、无认证、跨租户、无权限、资源不存在和方法错误。
2. 重复 `Idempotency-Key`/`client_mid` 不产生重复数据。
3. 同一幂等键配不同 payload 返回冲突。
4. version 冲突和并发状态迁移。
5. MySQL 成功后 Redis/Topic 缓存及在线客户端状态正确刷新。
6. Kafka 重复消费、临时失败重试、不可重试失败和死信重放。
7. 64 位 ID/seq 在 JSON 编解码时不丢失精度。
8. 更新前后端对接文档、SQL 变更集和未实现增强项。
## 12. 关键约定
### 12.1 ID 与时间
- 用户 ID 由 Snowflake UID 体系生成,对外使用 `usr...` 编码形式或协议规定的字符串。
- 业务 ID 必须全局或租户内唯一,不能用秒级时间戳直接代替唯一 ID。
- `SeqId`、数据库主键和 version 使用 `int64/BIGINT`。
- 持久化与对外时间使用 UTC,HTTP 输出 RFC3339/RFC3339Nano。
### 12.2 Topic 归一化
客户端传入的 `usr...`、`slf` 等可见 Topic 不一定是数据库真实 Topic。历史查询、上下文、收藏、转发、媒体、回执等接口在查库前必须统一归一化,并在鉴权、查询和响应之间保持明确的 Topic 语义。
### 12.3 权限
- HTTP handler 负责验证主体和请求参数,Store/adapter 仍应使用租户和业务条件限制写入。
- Topic 管理员、群主、普通成员权限以服务端为准,不接受客户端声明的角色。
- 群主转让、设管理员、禁言、踢人等变更后,必须刷新 Topic 内 `perUser` 权限快照和 Redis 订阅/成员缓存,不能等待重连后生效。
- 封禁、冻结、踢设备、全端退出和注销等账号治理必须同步处理已存在 Session。
### 12.4 可观测性
- 日志使用 `server/logs` 的 `Info/Warn/Err`,默认输出 stderr,由容器日志系统收集。
- 日志行前缀为 `I/W/E` 加 Go log flags 生成的日期时间;敏感值、Token、密码、验证码、证件号和第三方密钥不得记录。
- HTTP 日志和异步事件应携带 `request_id/trace_id`、租户、业务类型和业务 ID。
- 新增核心链路应增加请求数、失败数、延迟、队列深度/积压和死信指标。
## 13. 禁止的实现方式
1. 在 HTTP handler 内直接打开 MySQL 或拼接 SQL。
2. 因租户库表没有 `tenant_id` 列,就不向 Store 传递 `TenantID`。
3. Admin 直连 `tinode` 或 IM 直连 `hotgo`。
4. 将 Kafka 当作 HTTP RPC 或实时 WebSocket fanout 使用。
5. 将业务最终状态只存在 Redis。
6. 为同一份高频数据同时增加进程内缓存和 Redis 缓存。
7. 业务写成功后不失效 Redis、Topic 权限快照和成员分页快照。
8. 重试请求没有 `Idempotency-Key/client_mid/event_id`。
9. 使用 `int` 或 JSON number 承载可能超过 JavaScript 安全范围的 ID/seq。
10. 直接信任客户端构造的系统通知、管理员角色或审计信息。
11. 文件上传成功后不创建业务引用和安全审核任务。
12. 新增数据表只在开发者本机手工执行,不写幂等 schema SQL、升级逻辑和回归测试。
## 14. 开发与验收命令
MySQL 构建使用 build tag:
```bash
go build -tags mysql -o bin/tinode ./server
go build -tags mysql -o bin/init-db ./tinode-db
```
建议测试顺序:
```bash
# 先跑修改模块的定向测试
go test ./server -run 'TestName'
# Store 与 MySQL schema/adapter
go test -tags mysql ./server/store/... ./server/db/mysql/...
# 完整服务端回归
go test -tags mysql ./server/...
```
发版前至少检查:
- 配置文件可解析,MySQL、Redis、Kafka、OSS 等必需依赖可连通。
- 数据库版本与代码一致,所有新表/列/索引已在目标环境幂等执行。
- Kafka Topic、消费组、重试和死信链路可用。
- 单节点和多节点下的在线状态、群消息、权限变更均一致。
- 新增接口与前后端对接文档一致,无密钥、密码或测试 Token 进入 Git。
## 15. 新功能提交清单
开发者在提交 PR/代码前应逐项确认:
- [ ] 已确定功能入口是 HTTP、WebSocket、Kafka 还是内部 API。
- [ ] 已确定数据归属 `hotgo` 还是租户库。
- [ ] 所有 Store/adapter 调用都显式携带 `TenantID`。
- [ ] 已设计幂等键、唯一索引、业务版本和状态机。
- [ ] 已明确 MySQL、Redis、进程内缓存的使用和失效顺序。
- [ ] 需跨系统可靠投递时使用 outbox + Kafka,消费端已幂等。
- [ ] 已处理 Topic 内存权限快照和多节点同步。
- [ ] 文件已存 OSS,业务引用和安全审核已建立。
- [ ] `SeqId`/ID/version 已在 Go、JSON、Kafka 和 MySQL 全链路使用 64 位安全类型。
- [ ] 已增加正常、权限、跨租户、幂等、并发、缓存和重试测试。
- [ ] 已补充 API 对接文档、SQL 变更和未完成增强项。
## 16. 相关文档
- `docs/IM-前后端对接文档.md`:C 端业务 API 和交互协议。
- `docs/IM-系统设计文档.md`:原始产品系统设计。
- `docs/IM-Redis三层数据分层改造方案.md`:进程内、Redis、MySQL 分层细节。
- `docs/IM-Kafka接入方案.md`:Admin/IM 边界、Topic、幂等、重试和死信。
- `docs/IM-Kafka接入范围识别.md`:按业务场景判断 Kafka 接入范围。
- `docs/IM-Kafka接入联调测试文档.md`:Admin 与 IM 联调用例。
- `docs/third/IM-文件安全第三方接入记录.md`:OSS 和文件安全链路。
- `docs/IM-未实现后续增强项.md`:已知未完成能力和深度优化项。