Version: 0.9.68.dev.260504

后端:
1. 阶段 3 notification 服务边界落地,新增 `cmd/notification`、`services/notification`、`gateway/notification`、`shared/contracts/notification` 和 notification port,按 userauth 同款最小手搓 zrpc 样板收口
2. notification outbox consumer、relay 和 retry loop 迁入独立服务入口,处理 `notification.feishu.requested`,gateway 改为通过 zrpc client 调用 notification
3. 清退旧单体 notification DAO/model/service/provider/runner 和 `service/events/notification_feishu.go`,旧实现不再作为活跃编译路径
4. 修复 outbox 路由归属、dispatch 启动扫描、Kafka topic 探测/投递超时、sending 租约恢复、毒消息 MarkDead 错误回传和 RPC timeout 边界
5. 同步调整 active-scheduler 触发通知事件、核心 outbox handler、MySQL 迁移边界和 notification 配置

文档:
1. 更新微服务迁移计划,将阶段 3 notification 标记为已完成,并明确下一阶段从 active-scheduler 开始
This commit is contained in:
Losita
2026-05-04 18:40:39 +08:00
parent 9742dc8b1c
commit abe3b4960e
41 changed files with 2178 additions and 889 deletions

View File

@@ -10,7 +10,7 @@
2. 阶段 1 已完成:当前基线已经切成服务级 outbox 表、服务级 Kafka topic、服务级 consumer group仍在单体进程内装配多个服务级 worker后续拆微服务时再物理迁出。
3. 阶段 1.5 / 1.6 已完成:`backend/services/llm``backend/services/rag` 已经是当前 canonical 入口,`backend/infra/llm``backend/infra/rag``.go` 旧实现已删除。
4. 阶段 2 已完成:`user/auth` 已经从 Gin 单体抽成 `cmd/userauth` + `services/userauth` 的 go-zero zrpc 服务边界gateway 只保留 user HTTP 入口、鉴权、额度门禁和轻量转发。
5. 下一轮从阶段 3 开始,默认目标是拆 `notification`;不要再把 outbox、llm-service、rag-service 或 user/auth 当成未完成待办。
5. 阶段 3 `notification` 服务化已完成实现、code review 修复和真实 smoke;不要再把 outbox、llm-service、rag-service 或 user/auth 当成未完成待办。
本计划遵守两个硬原则:
@@ -97,6 +97,8 @@ gozero 服务负责领域能力:
> 当前状态:`llm-service` / `rag-service` 这两个边界已经先做成 `backend/services/*` 的服务内模块,调用仍由 `backend/cmd/start.go` 在同一进程内装配,不是 gozero 独立进程。
>
> 当前状态:`user/auth` 已经完成 go-zero zrpc 独立进程拆分,是阶段 2 样板。服务端在 `backend/services/userauth`,进程入口在 `backend/cmd/userauth`gateway client 在 `backend/gateway/userauth`。
>
> 当前状态:`notification` 已经完成阶段 3 拆分。服务端在 `backend/services/notification`,进程入口在 `backend/cmd/notification`gateway client 在 `backend/gateway/notification`,服务级 outbox consumer 和 retry loop 已随服务入口迁出。
### 3.3 事件层
@@ -127,6 +129,7 @@ gozero 服务负责领域能力:
5. `infra` 也不应该是一个大公共篮子:像 `kafka``outbox` 这类跨服务底座可以放到 `shared/infra``llm-service``rag-service` 这类模型与检索能力要单独成基础设施服务,不要塞进 `shared``prompt``tooling` 这类强业务依赖的适配器则应跟着具体服务走。
6. 换句话说,`shared` 是“跨进程契约层 + 少量跨服务底座”,不是“公共业务层”。
7. 阶段 2 已经新增 `backend/shared/contracts/userauth``backend/shared/ports`只承载跨层契约和端口接口user/auth 的 JWT、DAO、额度治理、黑名单实现不进入 `shared`
8. 阶段 3 已经新增 `backend/shared/contracts/notification`,只承载 notification 跨层 DTO通知通道 DAO、投递状态机、provider、重试策略和 outbox handler 都留在 `backend/services/notification`
---
@@ -141,7 +144,7 @@ gozero 服务负责领域能力:
| 1.5 | 先抽 llm-service已完成 | 已完成,`backend/services/llm` 作为当前 canonical 入口 | `go test ./...` + course / active-scheduler / memory 模型调用 smoke |
| 1.6 | 再抽 rag-service已完成 | 已完成,`backend/services/rag` 作为当前 canonical 入口 | `go test ./...` + memory retrieve / rerank smoke |
| 2 | 先拆 user/auth已完成 | 已完成,阶段 2 样板 commit 点userauth zrpc、gateway userapi、JWT/黑名单/额度治理、启动与迁移边界已收口 | 已完成注册/登录/刷新/并发 refresh/登出/鉴权/token quota smoke |
| 3 | 再拆 notification下一阶段) | notification 服务能独立消费和重试后 commit | notification E2E smoke + worker-only smoke |
| 3 | 再拆 notification已完成) | 已完成,`cmd/notification` + `services/notification` zrpc / outbox consumer / retry loop 已收口,旧单体实现已删除;是否 commit 等用户明确要求 | 已完成 notification E2E smoke + worker-only smoke |
| 4 | 再拆 active-scheduler | 预览生成和确认链路通过 gozero 服务跑通后 commit | dry-run / preview / confirm smoke |
| 5 | 再拆 schedule / task / course / task-class | 每个领域完成一次切流就 commit 一次 | schedule/task/course/task-class 回归 + 全链路 smoke |
| 6 | 再拆 agent / memory | agent 编排服务、memory 支撑服务和后台 worker 独立后 commit | agent chat / SSE / memory extract / memory retrieve smoke |
@@ -373,6 +376,14 @@ flowchart LR
3. 重试扫描 smoke。
4. 停掉 notification 服务后,主动调度预览仍然可用的回归测试。
本轮收口状态2026-05-04
1. `cmd/notification` 已承载 notification zrpc 启动、DB 迁移、服务级 outbox consumer 和重试扫描。
2. `backend/services/notification` 已收进 DAO、model、sv、rpc、飞书 provider 和 outbox handlergateway 通过 `backend/gateway/notification` zrpc client 调用。
3. 主动调度侧只写入 `notification.feishu.requested`publisher 侧只注册事件归属到 `notification`,不再启动单体 notification consumer。
4.`backend/notification`、旧 DAO/model 和旧 `service/events/notification_feishu.go` 已删除review 发现的 sending 租约恢复和 RPC timeout 边界已修复。
5. 真实 smoke 已通过:`notification_outbox_messages.id=3` 已从 `pending` 推进到 `consumed``smartflow.notification.outbox` 已出现 `outbox_id=3`,对应 `notification_records` 生成并按未启用通道进入 `skipped`
---
### 4.8 阶段 4再拆 active-scheduler
@@ -498,20 +509,19 @@ flowchart LR
当前建议按这个顺序推进:
注:阶段 1.5 / 1.6 / 2 已完成,当前实际推进从阶段 3 `notification` 开始
注:阶段 1.5 / 1.6 / 2 / 3 已完成;`notification` 已完成实现、code review 修复和真实 smoke不再作为下一轮待办
1. 以阶段 1 的服务级 outbox 为当前基线,不再回头做共享 outbox 方案。
2. 保持 `backend/services/llm``backend/services/rag` 为 canonical 入口,不再把它们写成待办。
3. 保持 `backend/services/userauth` + `cmd/userauth` 为阶段 2 样板,不再回头恢复 Gin 单体 user/auth。
4. 下一步切 notification
5. 再切 active-scheduler
6. 然后切 schedule / task / course / task-class
7. 再切 agent / memory把聊天编排和记忆链路独立出去
8. 最后把 Gin 收口成纯 Gateway。
4. 下一步进入阶段 4优先切 `active-scheduler`
5. 然后切 schedule / task / course / task-class
6. 再切 agent / memory把聊天编排和记忆链路独立出去
7. 最后把 Gin 收口成纯 Gateway
一句话总结:
> outbox 的服务级基础设施、llm-service、rag-serviceuser/auth 样板服务都已经完成;下一轮从 notification 开始,把通知投递和重试切成独立服务;然后让 active-scheduler、schedule、task、course、task-class 按稳定边界逐步独立;再把 agent / memory 独立出来,完成聊天编排和记忆链路的服务化;最后把 Gin 收口成真正的 Gateway。
> outbox 的服务级基础设施、llm-service、rag-serviceuser/auth 样板服务 notification 阶段 3 都已经完成;下一步让 active-scheduler、schedule、task、course、task-class 按稳定边界逐步独立;再把 agent / memory 独立出来,完成聊天编排和记忆链路的服务化;最后把 Gin 收口成真正的 Gateway。
---
@@ -582,17 +592,14 @@ SmartFlow-Agent/
│ │ │ ├── batch/
│ │ │ └── item/
│ │ ├── notification/
│ │ │ ├── start.go
│ │ │ ├── handler.go
│ │ │ ├── sv/
│ │ │ ├── dao/
│ │ │ ├── model/
│ │ │ ── internal/
│ │ │ ── provider/
│ │ │ ├── runner/
│ │ │ ├── dedupe/
│ │ │ ── channel/
│ │ │ └── retry/
│ │ │ ── internal/
│ │ │ ── feishu/
│ │ │ └── rpc/
│ │ │ ├── pb/
│ │ │ ── notification.proto
│ │ ├── active-scheduler/
│ │ │ ├── start.go
│ │ │ ├── handler.go
@@ -698,19 +705,19 @@ SmartFlow-Agent/
> 2. `backend/gateway/userapi/*` 是 user HTTP 入口,`backend/gateway/userauth/*` 是 userauth zrpc client二者都属于 gateway 边缘层。
> 3. `backend/service/*.go` 这批现有业务逻辑,后面要分别迁到各自服务根目录下的 `sv/`。
> 4. `backend/service/agentsvc/*` 和 `backend/newAgent/*`,后面要收束到 `backend/services/agent/sv/` + `internal/{prompt,graph,stream,tool,session,router}`。
> 5. `backend/notification/*`,下一阶段要收束到 `backend/services/notification/`,其中 `runner/provider/dedupe/channel_service` 归入 `sv/` 或 `internal/notification/`
> 5. `backend/services/notification/*` 已经是阶段 3 终态样板;`backend/cmd/notification` 是独立进程入口,`backend/gateway/notification` 是 gateway 侧 zrpc client`backend/shared/contracts/notification` 只放跨层契约;旧 `backend/notification/*`、旧 DAO/model 和旧 `service/events/notification_feishu.go` 不再作为活跃实现
> 6. `backend/active_scheduler/*`,后面要收束到 `backend/services/active-scheduler/`,其中 `graph/selection/feedbacklocate/apply/job` 归入 `internal/`。
> 7. `backend/memory/*`,后面要收束到 `backend/services/memory/`;当前 `memory/service/*` 只是迁移过渡态,终态还是按 `sv/` 或 `internal/` 拆开。
>
> 说明 4`shared` 先保留 `events` 和少量跨服务底座型 `infra`。以后如果真的出现跨服务 DTO / 枚举 / 常量,再新增 `contracts` 一类目录,但不要把 `dao`、`model`、`sv`、`handler` 这类服务私有层塞进去。
> 说明 5`notification` `active-scheduler` 的服务内部建议继续收束成你熟悉的“服务内单体壳”风格,不要让一级目录一直长成一排小框架;复杂算法和编排细节可以继续拆文件,但尽量下沉到 `sv/` 或 `internal/` 下面
> 说明 5`notification` 已经按 `userauth` 同款最小手搓 zrpc 样板收口:`rpc/server.go`、`rpc/handler.go`、`rpc/errors.go` + `rpc/pb`,不是 goctl 自动脚手架;`active-scheduler` 后续也按服务内单体壳继续收束,不要让一级目录长期长成一排小框架。
>
> 说明 6`llm-service` 和 `rag-service` 是独立基础设施服务,不放进 `shared``rag-service` 依赖 `llm-service` 做 embedding / rerank不反向依赖业务服务。
>
> 说明 7目录树里如果暂时写成 `backend/services/llm/` 和 `backend/services/rag/`,那只是目录名写法;后文所有职责判断都以 `llm-service` / `rag-service` 这两个逻辑服务名为准。
>
> 说明 8阶段 2 已经采用 `backend/services/userauth/` 作为实际目录名,不再使用 `user-auth`。gateway 侧 zrpc client 放在 `backend/gateway/userauth/`,进程入口放在 `backend/cmd/userauth/`;不要把 rpc client 放进 `cmd`。
> 说明 8阶段 2 已经采用 `backend/services/userauth/` 作为实际目录名,不再使用 `user-auth`。阶段 3 已经采用 `backend/services/notification/` 作为实际目录名。gateway 侧 zrpc client 放在 `backend/gateway/{userauth,notification}/`,进程入口放在 `backend/cmd/{userauth,notification}/`;不要把 rpc client 放进 `cmd`。
### 6.3 哪些可以不用变
@@ -723,7 +730,7 @@ SmartFlow-Agent/
1. `backend/cmd/start.go` 这种“大装配入口”后面要逐步拆成 gateway 启动和各服务启动。
2. `api` 这一层会收缩成纯 Gateway 职责,不再承载核心领域逻辑。
3. 当前仓库里的 `backend/service` 目录和相关遗留入口,要按 `user/auth``course``task-class``notification``active-scheduler``schedule``task``agent``memory` 拆出去;其中 `notification` `active-scheduler` 最终都要收束成更像 seckill 的服务内单体壳,不要长期维持一串顶层小包
3. 当前仓库里的 `backend/service` 目录和相关遗留入口,要继续`course``task-class``active-scheduler``schedule``task``agent``memory` 拆出去;`user/auth` `notification` 已完成独立服务边界,后续不要回迁到单体
4. 当前单体里的共享启动方式 `api / worker / all`后面会拆成“gateway 进程 + 服务进程 + worker 进程”的组合。
5. 任何依赖 `users` 表直读、核心表直写的网关路径,都要迁到对应服务里。
6. 不再把服务私有的 `dao` / `model` / `sv` / `handler` 误放进 `shared`,避免它变成新的单体公共层。
@@ -847,7 +854,8 @@ graph TD
4. 图里的 outbox 是“每个服务自己的 outbox 表 + 专属 relay worker”的抽象不代表所有服务共用一张表。
5. 当前阶段 1 已完成 `agent``task``memory``active-scheduler``notification` 的服务级 outbox 表、topic 和 consumer group尚未物理拆出的服务后续沿用同一模式补齐。
6. 当前阶段 2 已完成 `user/auth` 物理拆分gateway 到 userauth 的调用已经通过 zrpc client不再通过本地 DAO/service。
7. Kafka 是共享运输层,不是共享业务 topic新流量不应再默认进入单一共享 topic
7. 当前阶段 3 已完成 `notification` 物理拆分gateway 到 notification 的调用已经通过 zrpc clientnotification outbox consumer、relay 和 retry loop 已迁入 `cmd/notification` 启动边界
8. Kafka 是共享运输层,不是共享业务 topic新流量不应再默认进入单一共享 topic。
### 6.9 切对话交接卡
@@ -857,11 +865,11 @@ graph TD
2. 已冻结的终态是 `Gin Gateway + gozero 服务群 + 服务级 outbox + Kafka 共享运输层`
3. 阶段 1 已完成,当前 outbox 基线是服务级表、服务级 topic、服务级 consumer groupworker 仍在单体内装配,后续随对应服务迁出。
4. 阶段 2 已完成,`user/auth` 已经是样板服务,不要再把它当成下一轮待办。
5. 下一轮默认从阶段 3 `notification` 开始`llm-service``rag-service` 也已完成,不要重新当成待办。
6. `notification` `active-scheduler` 后续要回到更像 seckill 的服务内单体壳。
5. 阶段 3 `notification` 已完成实现、code review 修复和真实 smoke`llm-service``rag-service` 也已完成,不要重新当成待办。
6. 下一轮默认从阶段 4 `active-scheduler` 开始;它后续要回到更像 seckill 的服务内单体壳。
7. `shared` 只保留跨进程契约和少量跨服务底座不承载业务逻辑、DAO、模型或状态机。
8. 如果后续要改目录,必须先回答“这个文件属于哪一个典型用例”,回答不清楚就先别动结构。
9. 当前文档已经可以作为切对话基线;后续代理默认按本文件推进。现阶段的迁移基线入口是 `backend/cmd/api``backend/cmd/worker``backend/cmd/all`,它们只是当前仓库的启动壳,不是终态。`backend/cmd/userauth` 是阶段 2 的独立服务入口。终态仍然是“一个服务一个独立 `main.go`”,只在出现新的契约风险、边界变化或业务语义变化时再重新讨论架构。
9. 当前文档已经可以作为切对话基线;后续代理默认按本文件推进。现阶段的迁移基线入口是 `backend/cmd/api``backend/cmd/worker``backend/cmd/all`,它们只是当前仓库的启动壳,不是终态。`backend/cmd/userauth` 是阶段 2 的独立服务入口`backend/cmd/notification` 是阶段 3 的独立服务入口。终态仍然是“一个服务一个独立 `main.go`”,只在出现新的契约风险、边界变化或业务语义变化时再重新讨论架构。
### 6.10 启动方式与进程模型
@@ -984,7 +992,7 @@ graph TD
这段用于避免后续代理重复踩阶段 2 已经纠偏过的问题。
1. 阶段 3 起步默认是 `notification`,不是 outbox、llm-service、rag-serviceuser/auth。
1. 阶段 3 `notification` 已完成;后续起步默认是阶段 4 `active-scheduler`,不是 outbox、llm-service、rag-serviceuser/auth 或 notification
2. 主代理负责 leader先读必要文档和代码拆任务关键阻塞任务自己做子代理只能承担并行、明确、非阻塞的侧翼任务。
3. 如果确实有会影响切分方向的不确定点,先总结成拍板点问用户;文档已经写清楚的内容不要重复问。
4. 查库一律用 `docker exec`。MySQL / Redis 都按这个规则走;不直接用本机客户端绕过容器。
@@ -999,7 +1007,7 @@ graph TD
---
### 6.14 阶段 0 历史基线与阶段 1/2 当前基线快照
### 6.14 阶段 0 历史基线与阶段 1/2/3 当前基线快照
阶段 0 历史基线:
@@ -1033,6 +1041,17 @@ graph TD
5. `cmd/all` 不再迁 `users``cmd/userauth` 自己迁 `users``user_token_usage_adjustments`
6. 完整本地 smoke 需要同时启动 `cmd/all``cmd/userauth`
阶段 3 当前基线:
1. `backend/cmd/notification/main.go` 是 notification 独立进程入口,负责 DB 迁移、zrpc server、notification outbox consumer 和 retry loop 的统一生命周期。
2. `backend/services/notification` 拥有 notification 核心业务、DAO、模型、飞书 provider、幂等、投递记录状态机、重试扫描和 outbox handler。
3. `backend/gateway/notification` 是 gateway 侧 zrpc clientgateway 只保留 notification HTTP 入口、鉴权和轻量组合逻辑,不再直连 notification DAO/service。
4. `backend/shared/contracts/notification``backend/shared/ports` 只承载跨层契约和端口接口,不承载服务私有业务实现。
5. notification 内部是 `userauth` 同款最小手搓 zrpc 框架,不使用 goctl 自动脚手架;`rpc` 只保留 `NewServer``cmd/notification` 管理 signal、outbox consumer、retry loop 和 server 生命周期。
6.`backend/notification/*`、旧 `backend/dao/notification_channel.go`、旧 `backend/model/notification_channel.go` 和旧 `backend/service/events/notification_feishu.go` 已删除;若 `backend/notification` 目录壳仍存在,它不参与编译,也不作为活跃实现。
7. notification outbox consumer 已迁入独立服务边界并处理 `notification.feishu.requested`,覆盖 payload/version 校验、dead/retry/consumed 状态推进和毒消息回退。
8. 已完成真实 smoke`notification_outbox_messages` 可从 `pending` 推进到 `consumed`Kafka `smartflow.notification.outbox` 可看到对应 outbox 消息,`notification_records` 可生成幂等记录并按通道状态进入预期状态。
---
## 7. 风险与回退