# IM 工作台服务拆分迁移稳定方案 > 目标:将 `im_server` 中的工作台业务拆到独立仓库 `/Users/youshunlai/go_workspace/im_workbench`,先形成独立服务和独立发布边界;数据库暂不迁移,等接口全部稳定后再单独做工作台库迁移。 ## 1. 结论 建议采用“独立仓库 + 分阶段迁移 + Nginx 分流 + Redis 统一 Token 鉴权”的方式: 1. `im_server` 保留 IM 核心能力:登录态校验、聊天协议、单聊/群聊、消息存储、长连接、在线状态、Redis 实时 fanout、Kafka 消息消费。 2. `im_workbench` 承接工作台能力:工作台入口、组织架构、考勤、会议、会议笔记、日报、月报、总经理信箱、工作台红点。 3. 当前阶段不迁移数据库,`im_workbench` 先连接现有租户库读取和写入工作台相关表。 4. 等 `im_workbench` 接口、鉴权、Kafka、监控全部稳定后,再单独启动数据库迁移专项。 5. 工作台接口直接切换为 `/v1/workbench/*`,由 Nginx/API Gateway 按路径分发到 `im_workbench`;`im_server` 不再代理工作台请求。 6. 迁移采用“先复制迁出、旧代码保留”的方式;只有 `im_workbench` 全部接口、数据库、Kafka、监控联调通过后,才删除 `im_server` 中的工作台旧代码。 一句话:网关分流,Redis 鉴权,先拆服务和接口,数据库后迁,最后清理 `im_server` 中的工作台旧代码。 ## 2. 拆分范围 ### 2.1 迁出到 im_workbench 以下业务从 `im_server` 迁移到 `im_workbench`: | 模块 | 当前接口示例 | 迁移归属 | |---|---|---| | 工作台入口 | `GET /v1/workbench` | im_workbench | | 组织架构 | `/v1/workbench/organization/*` | im_workbench | | 考勤打卡 | `/v1/workbench/attendance/*` | im_workbench | | 会议通知 | `/v1/workbench/meetings*` | im_workbench | | 会议笔记 | `/v1/workbench/meeting-notes`、`/v1/workbench/meetings/{id}/notes*` | im_workbench | | 日报 | `/v1/workbench/reports/daily*` | im_workbench | | 月报 | `/v1/workbench/reports/monthly*` | im_workbench | | 总经理信箱 | `/v1/workbench/mailbox*` | im_workbench | | 工作台红点 | `workbench_total_unread`、模块未读 | im_workbench 计算,im_server 聚合透出 | ### 2.2 留在 im_server 以下能力继续留在 `im_server`: | 模块 | 说明 | |---|---| | 登录与 Token 签发 | C 端登录仍由 IM 负责,登录成功后把 Token 会话写入 Redis。 | | 聊天消息 | 单聊、群聊、消息 seq、消息落库、Redis fanout、WS 下发。 | | 群治理 | 群资料、群成员、禁言、进群审批、群公告、群文件。 | | 文件上传入口 | `POST /v0/file/u/` 暂留 im_server,工作台只保存业务引用;后续可单独拆文件服务。 | | 系统通知投递 | 最终给 C 端的在线通知仍由 im_server 通过 WS 下发。 | | Kafka 消费到 C 端 | Admin 或 Workbench 产生的业务事件,由 im_server 消费后找在线用户下发。 | ## 3. 目标架构 ```text C 端 App | | HTTPS v Nginx / API Gateway |-- /v1/im/*、/v0/channels、/v0/file/u/ -> im_server |-- /v1/workbench/* -> im_workbench im_workbench | | MySQL v 现有租户库中的工作台表 im_workbench | | Kafka: workbench 业务事件 v im_server | | WebSocket v C 端在线用户 ``` 稳定边界: 1. `im_workbench` 不直接操作 IM 消息表、在线连接、WebSocket。 2. `im_server` 不直接写工作台业务表,也不代理工作台 HTTP 请求。 3. `im_server` 登录成功后将 Token 会话写入 Redis,`im_workbench` 每次请求从 Redis 校验 Token。 4. 两边共享用户 ID 口径:内部管理和工作台接口使用数据库 `BIGINT/int64`;聊天协议层仍可使用 `usr...`。 5. 工作台需要通知 C 端时投 Kafka 事件,由 `im_server` 统一下发。 ## 4. 数据库处理方案 ### 4.0 当前阶段原则 当前阶段数据库先不迁移。`im_workbench` 独立服务启动后,仍访问现有租户库中的工作台相关表,先保证新服务接口、Redis Token 鉴权、Nginx 路由、Kafka 事件和监控全部跑通。 代码层面禁止服务启动时自动建库建表。`im_server` 和 `im_workbench` 都只负责连接已有数据库和执行正常业务 SQL;所有 `CREATE DATABASE`、`CREATE TABLE`、`ALTER TABLE`、索引变更、初始化字典和种子数据,统一整理为 SQL 文件,经审核后由人工或运维发布流程执行。 数据库迁移作为后置专项处理,触发条件是: 1. `/v1/workbench/*` 全部接口在本地、测试环境、前端联调中稳定。 2. `im_server` 中工作台旧接口确认没有客户端继续访问。 3. 工作台事件、通知、红点、文件引用链路全部验收通过。 4. 运维确认新库命名、备份、监控、账号权限和迁移窗口。 ### 4.1 当前工作台相关表 当前这些表仍保留在租户库 `tinode` 中,暂不迁移;后续数据库专项时再纳入迁移范围: | 表 | 用途 | 迁移建议 | |---|---|---| | `im_workbench_apps` | 工作台模块配置、排序、入口 | 暂留租户库;后续迁到工作台库或由 Admin 配置后同步。 | | `im_org_departments` | 组织部门树 | 暂留租户库;后续迁到工作台库。 | | `im_org_members` | 组织成员、岗位、联系方式 | 暂留租户库;保留 `user_id BIGINT` 关联 IM 用户。 | | `im_org_visibility_overrides` | 组织可见范围覆盖 | 暂留租户库。 | | `im_attendance_rules` | 考勤规则 | 暂留租户库。 | | `im_attendance_shifts` | 考勤班次 | 暂留租户库。 | | `im_attendance_records` | 考勤日记录 | 暂留租户库。 | | `im_attendance_checkins` | 打卡流水 | 暂留租户库。 | | `im_attendance_appeals` | 补卡/异常申诉 | 暂留租户库。 | | `im_meetings` | 会议主表 | 暂留租户库。 | | `im_meeting_participants` | 会议参与人 | 暂留租户库。 | | `im_meeting_note_files` | 会议笔记文件引用 | 暂留租户库;文件实体仍在文件表/OSS。 | | `im_daily_reports` | 日报 | 暂留租户库。 | | `im_monthly_reports` | 月报 | 暂留租户库。 | | `im_mailbox_config` | 总经理信箱配置 | 暂留租户库;后续可迁到工作台库或 Admin 公共库配置后同步。 | | `im_mailbox_submissions` | 总经理信箱提交 | 暂留租户库。 | ### 4.2 不迁出的表 | 表/数据 | 原因 | |---|---| | `users`、认证、会话、设备 | 属于 IM 登录和账号基础能力。 | | `topics`、`subscriptions`、`messages`、`im_group_messages` | 属于聊天主链路。 | | 好友、黑名单、群成员、群治理表 | 属于 IM 社交关系和群聊能力。 | | 文件实体表、OSS 元数据 | 暂不迁移,工作台只保存 `file_id` 引用。 | | `im_system_notifications` | 建议先留在 im_server,作为统一 C 端通知中心。后续如要拆,可单独设计通知服务。 | | `im_business_events_outbox` | 已废弃,不纳入迁移;当前代码不再自动建表,工作台拆分后事件统一走 Kafka 或实时投递链路。 | ### 4.3 后续目标库建议 后续接口稳定后,再给工作台单独库,命名按环境区分: ```text 本地:im_workbench 测试:im_workbench_test 或按运维规范命名 生产:im_workbench ``` 如果继续保持“一个租户一个库”,则工作台也应支持租户库路由: ```text hotgo.im_tenant - code - im_db_name - workbench_db_name - im_internal_base_url - workbench_internal_base_url ``` 正式方向建议在 `hotgo.im_tenant` 或租户配置表里增加工作台库配置,而不是让 `im_server` 读取工作台库 DSN。 ### 4.4 后续数据迁移顺序 迁库等接口全部稳定后再做,不要和本轮服务拆分混在一起。后续建议分四步: 1. 建新库新表:在 `im_workbench` 库手动执行审核后的 SQL,创建同结构表,先不接线上流量。 2. 全量复制:从租户库复制历史工作台表数据到工作台库。 3. 增量同步:迁移窗口内对工作台写操作做双写,或由旧库 binlog/脚本同步到新库。 4. 切读切写:先灰度读新库,再切写新库,最后关闭旧库工作台写入。 迁移校验至少包括: | 校验项 | 说明 | |---|---| | 行数校验 | 每张表按租户统计行数。 | | 主键/业务唯一键校验 | `meeting_id`、`report_id`、`submission_id`、`department_id` 等必须一致。 | | 更新时间校验 | 对比 `updated_at` 最大值,确认增量无遗漏。 | | 抽样内容校验 | 抽样会议、日报、月报、考勤记录详情。 | | 外键引用校验 | 会议参与人、会议笔记文件、组织成员部门引用不能断。 | ### 4.5 回滚原则 当前阶段没有数据库迁移,回滚以服务和路由为主。后续真正迁库时,旧库数据至少保留一个发版周期,不删除旧表。 回滚策略: 1. 如果服务拆分异常:关闭 `/v1/workbench/*` 路由,前端回退旧路径或旧版本。 2. 如果 `im_workbench` 业务异常:保留 `im_server` 旧工作台代码作为临时回退能力。 3. 如果 Redis 鉴权异常:停止工作台新路由,优先恢复登录态校验。 4. 如果 Kafka 通知异常:业务写入不回滚,只降级为客户端主动拉取和后台补偿推送。 5. 后续如果新库读写异常:再按迁库专项方案切回旧库。 ## 5. 路由与鉴权迁移方案 ### 5.1 Nginx 分流规则 工作台接口直接使用新路径 `/v1/workbench/*`,Nginx/API Gateway 按路径分流: ```text /v1/im/* -> im_server /v0/channels -> im_server /v0/file/u/ -> im_server /v1/workbench/* -> im_workbench ``` 建议工作台目标路由: | 原 IM 路径 | 新 Workbench 路径 | |---|---| | `GET /v1/im/workbench` | `GET /v1/workbench` | | `/v1/im/organization/*` | `/v1/workbench/organization/*` | | `/v1/im/attendance/*` | `/v1/workbench/attendance/*` | | `/v1/im/meetings*` | `/v1/workbench/meetings*` | | `/v1/im/meeting-notes*` | `/v1/workbench/meeting-notes*` | | `/v1/im/meetings/{id}/notes*` | `/v1/workbench/meetings/{id}/notes*` | | `/v1/im/reports/daily*` | `/v1/workbench/reports/daily*` | | `/v1/im/reports/monthly*` | `/v1/workbench/reports/monthly*` | | `/v1/im/mailbox*` | `/v1/workbench/mailbox*` | 不建议让 `im_server` 继续代理工作台请求。路由由 Nginx 分流,服务各自鉴权、各自访问自己的数据库。 ### 5.2 Redis Token 鉴权 登录仍由 `im_server` 负责。登录成功后,`im_server` 将 Token 会话写入 Redis,`im_workbench` 直接从 Redis 校验登录态。 Redis Key 建议: ```text im:session:token:{token_hash} ``` Value 建议使用 JSON: ```json { "user_id": 42, "tenant_id": 1, "tenant_code": "default", "device_id": "ios-device-1", "auth_level": "auth", "state": "active", "expires_at": "2026-09-13T18:00:00+08:00", "issued_at": "2026-09-13T10:00:00+08:00" } ``` 请求 Header 保持 C 端统一: | Header | 说明 | |---|---| | `Authorization` | `Bearer {token}`,两个服务都从 Redis 校验。 | | `X-MeetingIM-APIKey` | 应用 API Key。 | | `X-Tenant-Code` | 租户编码。 | | `X-IM-Schema` | 协议版本。 | | `X-Request-ID` | 链路追踪 ID。 | 校验流程: 1. 从 `Authorization` 取 Bearer Token,先做格式校验。 2. 对 Token 做 hash 后查 Redis,避免 Redis 明文 Key 暴露原始 Token。 3. 校验会话存在、未过期、`state=active`。 4. 校验 `X-Tenant-Code` 与 Redis 会话中的 `tenant_code` 一致。 5. 校验 API Key 是否可用于当前租户和客户端类型。 6. 将 `tenant_id`、`tenant_code`、`user_id`、`device_id` 写入请求上下文。 Token 失效场景: | 场景 | 处理 | |---|---| | 用户主动退出 | 删除当前 Token Key。 | | 踢设备 | 删除指定 `device_id` 对应 Token Key。 | | 冻结/封禁账号 | 删除用户全部 Token Key,并通过 Kafka 通知在线连接断开。 | | 修改密码 | 删除用户全部 Token Key。 | | Token 过期 | Redis TTL 自动过期。 | ### 5.3 共享鉴权代码 建议抽一个轻量公共包,两个服务复用: ```text meetingim_common/auth/session ``` 公共包只放稳定协议: 1. Redis Key 生成。 2. Token hash 规则。 3. Session JSON 结构。 4. Header 解析。 5. 错误码定义。 不要把数据库访问、HTTP handler、业务权限放进公共包,避免公共包变成新的耦合点。 ## 6. 事件与通知 工作台拆出后,工作台业务变更不再直接调用 `im_server` 代码函数,而是投 Kafka。 建议 Topic: | Topic | 生产者 | 消费者 | 用途 | |---|---|---|---| | `im.workbench.event` | im_workbench | im_server | 工作台事件、红点、系统通知、WS 下发。 | | `im.cache.invalidate` | im_workbench/Admin | im_server/im_workbench | 组织、工作台模块、配置变更后的缓存失效。 | | `im.audit.event` | im_workbench | Admin/审计消费者 | 工作台敏感操作审计。 | `im.workbench.event` 消息建议字段: ```json { "event_id": "wb_default_meeting_mtg_1_created_v1", "tenant_id": 1, "tenant_code": "default", "actor_user_id": 42, "module": "meeting", "event_type": "meeting.created", "resource_id": "mtg_1", "target_user_ids": [42, 43], "title": "会议通知", "content": "项目周会", "badge_delta": 1, "payload": { "meeting_id": "mtg_1", "version": 1 }, "occurred_at": "2026-09-13T13:30:00+08:00" } ``` 消费原则: 1. `event_id` 全局幂等,消费端记录处理日志。 2. 系统通知写入失败可重试,超过次数进入死信。 3. WS 下发失败不影响业务写入,客户端重连后通过工作台接口和 bootstrap 补偿。 4. 工作台红点以服务端查询为准,Kafka 只做实时增量提醒。 ## 7. 代码迁移步骤 ### 7.1 第一阶段:服务骨架 在 `/Users/youshunlai/go_workspace/im_workbench` 新建 Go 服务: ```text im_workbench/ cmd/workbench/ internal/http/ internal/store/ internal/kafka/ internal/config/ docs/ sql/ ``` 第一阶段只做: 1. 配置文件、日志、MySQL、Redis、Kafka 初始化。 2. 健康检查:`GET /healthz`、`GET /readyz`。 3. Redis Token 鉴权中间件。 4. 租户上下文解析。 5. 复制工作台接口 DTO 和响应结构。 ### 7.2 第二阶段:迁移业务代码 从 `im_server` 迁出以下代码: | 当前位置 | 迁移内容 | |---|---| | `server/im_http.go` | `serveIMWorkbench`、Organization、Attendance、Meetings、MeetingNotes、DailyReports、MonthlyReports、Mailbox 相关 handler。 | | `server/store/store.go` | 工作台相关 Persistence Interface 和 mapper。 | | `server/store/types/*.go` | `workbench.go`、`organization.go`、`attendance.go`、`meeting.go`、`reports.go`、`mailbox.go` 等类型。 | | `server/db/mysql/tenant_adapter.go` | 工作台相关 SQL 实现。 | | `server/im_business_events.go` | 工作台事件生产逻辑拆到 `im_workbench`,只保留 IM 消费下发。 | | `server/kafka_workbench_audit.go` | 工作台事件生产侧迁出,IM 侧保留消费或下发处理。 | 迁移时要保留测试: 1. 原 handler 单测迁到 `im_workbench`。 2. `im_server` 原有工作台测试暂时保留,作为旧链路回归保护。 3. `im_workbench` 新增同等接口测试,确认新路径行为一致。 4. Kafka 事件用例拆为 Workbench 生产端测试和 IM 消费端测试。 ### 7.3 第三阶段:Nginx 路由切换 `im_server` 中原工作台 handler 暂时不删除,不做主链路代理。新请求由 Nginx/API Gateway 直接分流到 `im_workbench`: ```text 客户端请求 /v1/workbench -> Nginx/API Gateway -> im_workbench Redis Token 鉴权 -> return response ``` 路由层必须支持: 1. `/v1/workbench/*` 全量转发到 `im_workbench`。 2. `/v1/im/*`、`/v0/channels`、`/v0/file/u/` 继续转发到 `im_server`。 3. 透传 `Authorization`、`X-MeetingIM-APIKey`、`X-Tenant-Code`、`X-IM-Schema`、`X-Request-ID`。 4. 工作台服务超时建议 3 到 5 秒。 5. 工作台不可用时返回明确 `WORKBENCH_UNAVAILABLE` 或网关 502/504,不影响聊天主链路。 旧代码保留策略: 1. `im_server` 工作台旧 handler、store、SQL 先保留。 2. 前端切新路径后,测试环境持续观察一个发版周期。 3. `im_workbench` 全部接口、数据库、Kafka、监控确认稳定后,再从 `im_server` 删除旧工作台代码。 4. 删除前要确保没有客户端继续访问 `/v1/im/workbench`、`/v1/im/meetings*` 等旧路径。 ### 7.4 第四阶段:接口稳定验收 当前阶段只做接口稳定验收,不做数据库切换。建议保留以下服务开关: | 配置 | 说明 | |---|---| | `workbench.service.enabled` | 是否启用工作台独立服务。 | | `workbench.route.enabled` | Nginx/API Gateway 是否启用 `/v1/workbench/*` 路由。 | | `workbench.kafka.enabled` | 是否启用工作台事件 Kafka。 | 后续迁库专项再增加数据库切换开关: ```text workbench.db.read_from=old|new|shadow workbench.db.write_to=old|dual|new ``` ## 8. 发布与回归 ### 8.1 发布前检查 1. `im_workbench` 健康检查通过。 2. `im_workbench` 可连接现有租户库,并只访问工作台相关表。 3. Redis Token 鉴权与 `im_server` 登录态一致。 4. Nginx/API Gateway 工作台路由灰度开关默认关闭或仅测试环境开放。 5. Kafka Topic、消费组、死信队列已建。 6. 监控已覆盖 HTTP 成功率、耗时、Kafka 积压、DB 错误、网关转发失败。 7. 本阶段不要求新工作台库建表和数据复制。 ### 8.2 必测用例 | 模块 | 用例 | |---|---| | 工作台入口 | 登录后返回模块排序、开关、未读角标。 | | 组织架构 | 普通用户、手机号注册用户、邀请用户、测试账号可见范围正确。 | | 考勤 | 配置查询、打卡、重复幂等、记录列表、申诉创建。 | | 会议 | 创建、列表、详情、修改、取消、提醒、参会响应。 | | 会议笔记 | 上传文件、绑定文件、详情、替换、删除、权限校验。 | | 日报 | 创建草稿、保存、提交、列表、统计、提醒。 | | 月报 | 创建、保存、提交、reopen、详情、统计。 | | 总经理信箱 | 配置查询、匿名/实名提交、附件引用。 | | 通知 | 工作台事件生成系统通知,在线用户收到 WS,离线用户重登可补偿。 | | 回滚 | 关闭 `/v1/workbench/*` 路由后,前端回退旧版本或网关明确降级;旧代码未删前可切回旧路径。 | ## 9. 风险点 | 风险 | 处理 | |---|---| | 一次性拆代码和迁库导致回归面过大 | 本阶段不迁库,只拆服务和接口;数据库等接口稳定后单独迁移。 | | 工作台依赖 IM 用户资料 | 通过 IM 内部查询 API 获取用户摘要,禁止直连 IM 库。 | | IM 需要工作台红点 | 通过工作台内部接口或 Kafka 事件聚合,禁止 im_server 查工作台库。 | | 文件上传仍在 im_server | 工作台保存 `file_id` 引用,文件服务后续再独立拆。 | | Kafka 事件丢失 | 生产端直接投 Kafka,必要时使用业务失败表补偿;消费端使用幂等和死信队列。 | | 前端路径切换风险 | 前端切到 `/v1/workbench/*` 前必须完成接口对照、灰度和回归。 | | 数据不一致 | 当前阶段继续读写同一套租户库,避免双写不一致;后续迁库再做影子读和校验。 | ## 10. 推荐落地顺序 1. 在 `im_workbench` 建服务骨架、配置、健康检查、Redis Token 鉴权。 2. 迁移工作台类型、store interface、MySQL 实现和单测。 3. 迁移 `/v1/workbench`、组织架构、考勤接口。 4. 迁移会议、会议笔记、日报、月报、总经理信箱接口。 5. Nginx/API Gateway 增加 `/v1/workbench/*` 路由,默认关闭或只对测试环境开放。 6. 本地环境用新路径跑通全部工作台接口。 7. 测试环境启用新路由灰度,只切少量测试账号。 8. 全量接口稳定后,确认没有客户端访问旧 `/v1/im/*` 工作台路径。 9. 清理 `im_server` 内工作台业务实现,只保留 IM 主链路。 10. 单独启动数据库迁移专项,新建工作台库、复制数据、影子读、切读写。 ## 11. 当前建议先做的第一步 第一步不要急着删 `im_server` 代码,建议先在 `im_workbench` 完成最小可运行服务: 1. 监听端口建议:`7070`。 2. 支持 `GET /healthz`、`GET /readyz`。 3. 支持 Redis Token 鉴权和统一 Header 校验。 4. 接入本地 MySQL、Redis、Kafka 配置。 5. 先迁 `GET /v1/workbench` 一个读接口做样板。 6. Nginx/API Gateway 加 `/v1/workbench` 测试路由,只开放样板接口。 这样第一步上线风险最低,也能验证服务拆分、Redis 鉴权、链路追踪、配置和部署是否顺畅。