Version: 0.9.64.dev.260503

后端:
1. 服务级 outbox 基础设施全量落地——新增 service route / service catalog / route registry,重构 outbox engine、repository、event bus 和 model,按 `event_type -> service -> table/topic/group` 统一写入与投递,保留 `agent` 兼容壳但不再依赖共享 outbox
2. Kafka 投递、消费与启动装配同步切换——更新 kafka config、consumer、envelope,接入服务级 topic 与 consumer group,并同步调整 mysql 初始化、start/main/router 装配,保证各服务 relay / consumer 独立装配
3. 业务事件处理器按服务归属重接新 bus——`active-scheduler` 触发链路,以及 `agent` / `memory` / `notification` / `task` 相关 outbox handler 统一切到新路由注册与服务目录,避免新流量回流共享表
4. 同步更新《微服务四步迁移与第二阶段并行开发计划》,把阶段 1 改成当前基线并补齐结构图、阶段快照、风险回退和多代理执行口径
This commit is contained in:
Losita
2026-05-03 20:29:00 +08:00
parent 166fb1b507
commit a6c1e5d077
28 changed files with 1631 additions and 340 deletions

View File

@@ -64,7 +64,7 @@ type appRuntime struct {
agentCache *dao.AgentCache
manager *dao.RepoManager
outboxRepo *outboxinfra.Repository
eventBus *outboxinfra.EventBus
eventBus eventsvc.OutboxBus
memoryModule *memory.Module
activeJobScanner *activejob.Scanner
activeTriggerWorkflow *activesvc.TriggerWorkflowService
@@ -88,46 +88,39 @@ func loadConfig() error {
return nil
}
// Start 保留历史入口,默认仍按 all 模式启动
//
// 职责边界:
// 1. 兼容 backend/main.go 以及旧部署命令;
// 2. 不新增业务语义,只委托给 StartAll
// 3. 后续若部署全面切到独立 api/worker可逐步废弃该兼容入口。
// Start 保留历史兼容入口,当前默认等价于 StartAll
// 1. 兼容 backend/main.go 和旧部署命令。
// 2. 不新增业务语义,只转发给 StartAll。
// 3. 后续若全面切到独立 api/worker 启动,本入口只保留过渡兼容。
func Start() {
StartAll()
}
// StartAll 启动迁移期兼容模式HTTP API 后台 worker 在同一进程内运行
// StartAll 启动当前仓库的完整运行态HTTP API + 后台 worker。
// 这仍然是迁移期的兼容装配,不是终态的“一个服务一个 main.go”模型。
func StartAll() {
ctx := context.Background()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
runtime := mustBuildRuntime(ctx)
defer runtime.close()
runtime.startWorkers(ctx)
runtime.startHTTP()
runtime.startHTTP(ctx)
}
// StartAPI 只启动 Gin API 其同步 service/dao 依赖,不启动后台 worker。
//
// 说明:
// 1. 该模式仍是“带 service/dao 的 API 单体”,不是最终 API Gateway
// 2. API 可以继续写入 outbox但不负责消费 outbox也不启动 memory worker
// 3. worker 停止时API 仍可提供同步接口,只是异步能力会延迟处理。
// StartAPI 只启动 Gin API 其同步依赖,不启动后台 worker。
// 这仍是迁移期的单体 API 模式,不是终态的独立网关。
func StartAPI() {
ctx := context.Background()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
runtime := mustBuildRuntime(ctx)
defer runtime.close()
runtime.startHTTP()
runtime.startHTTP(ctx)
}
// StartWorker 只启动后台异步能力,不注册 Gin 路由。
//
// 运行内容:
// 1. outbox relay扫描 pending 消息并投递 Kafka
// 2. Kafka consumer消费事件并分发到业务 handler
// 3. memory worker处理 memory_jobs 后台任务。
// 当前包含 outbox relay / Kafka consumer / memory worker / 主动调度扫描 / 通知重试。
func StartWorker() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
@@ -305,7 +298,7 @@ func buildRuntime(ctx context.Context) (*appRuntime, error) {
}
handlers := buildAPIHandlers(userService, taskSv, taskClassService, courseService, scheduleService, agentService, memoryModule, activeScheduleDryRun, activeSchedulePreviewConfirm, activeScheduleTrigger, notificationChannelService)
return &appRuntime{
runtime := &appRuntime{
db: db,
redisClient: rdb,
cacheRepo: cacheRepo,
@@ -321,7 +314,13 @@ func buildRuntime(ctx context.Context) (*appRuntime, error) {
notificationService: notificationService,
limiter: limiter,
handlers: handlers,
}, nil
}
if runtime.eventBus != nil {
if err := runtime.registerEventHandlers(); err != nil {
return nil, err
}
}
return runtime, nil
}
func buildRAGRuntime(ctx context.Context) (infrarag.Runtime, error) {
@@ -346,16 +345,24 @@ func buildRAGRuntime(ctx context.Context) (infrarag.Runtime, error) {
return ragRuntime, nil
}
func buildEventBus(outboxRepo *outboxinfra.Repository) (*outboxinfra.EventBus, error) {
// outbox 通用事件总线接线
// 1. API 模式只使用 Publish 写入 outbox不启动后台循环
// 2. worker/all 模式再显式注册 handler 并启动后台循环
func buildEventBus(outboxRepo *outboxinfra.Repository) (eventsvc.OutboxBus, error) {
// outbox 多 service 门面装配
// 1. 按 service 维度创建独立 enginetopic / group 由 service 名称推导
// 2. 对外仍然只暴露一个 Publish / Start / Close 门面
// 3. kafka.enabled=false 时返回 nil业务按既有降级策略执行。
kafkaCfg := kafkabus.LoadConfig()
eventBus, err := outboxinfra.NewEventBus(outboxRepo, kafkaCfg)
if err != nil {
return nil, fmt.Errorf("failed to initialize outbox event bus: %w", err)
serviceBuses := make(map[string]eventsvc.OutboxBus, len(eventsvc.OutboxServiceNames()))
for _, serviceName := range eventsvc.OutboxServiceNames() {
bus, err := eventsvc.NewServiceOutboxBus(outboxRepo, kafkaCfg, serviceName)
if err != nil {
return nil, fmt.Errorf("failed to initialize outbox event bus for service %s: %w", serviceName, err)
}
if bus != nil {
serviceBuses[serviceName] = bus
}
}
eventBus := eventsvc.NewRoutedOutboxBus(serviceBuses)
if eventBus == nil {
log.Println("Outbox event bus is disabled")
}
@@ -855,9 +862,6 @@ func (r *appRuntime) startWorkers(ctx context.Context) {
}
if r.eventBus != nil {
if err := r.registerEventHandlers(); err != nil {
log.Fatalf("Failed to register outbox event handlers: %v", err)
}
r.eventBus.Start(ctx)
log.Println("Outbox event bus started")
} else {
@@ -878,24 +882,25 @@ func (r *appRuntime) startWorkers(ctx context.Context) {
}
func (r *appRuntime) registerEventHandlers() error {
// 调用目的:worker/all 启动时复用同一套核心事件注册顺序,避免未来新增入口后复制多份 handler 接线
if err := eventsvc.RegisterCoreOutboxHandlers(r.eventBus, r.outboxRepo, r.manager, r.agentRepo, r.cacheRepo, r.memoryModule); err != nil {
// 调用目的:在运行时启动前一次性完成“事件类型 -> 服务归属 -> handler”的显式接线避免 API 模式发布事件时拿不到路由表
if err := eventsvc.RegisterAllOutboxHandlers(
r.eventBus,
r.outboxRepo,
r.manager,
r.agentRepo,
r.cacheRepo,
r.memoryModule,
r.activeTriggerWorkflow,
r.notificationService,
); err != nil {
return err
}
if err := eventsvc.RegisterActiveScheduleTriggeredHandler(r.eventBus, r.outboxRepo, r.activeTriggerWorkflow); err != nil {
return fmt.Errorf("注册主动调度触发 handler 失败: %w", err)
}
// 调用目的:飞书通知事件消费与 notification retry loop 复用同一个服务实例,
// 保证后续接入真实 provider 时只需要替换启动期注入配置。
if err := eventsvc.RegisterFeishuNotificationHandler(r.eventBus, r.outboxRepo, r.notificationService); err != nil {
return fmt.Errorf("注册飞书通知 handler 失败: %w", err)
}
return nil
}
func (r *appRuntime) startHTTP() {
func (r *appRuntime) startHTTP(ctx context.Context) {
router := routers.RegisterRouters(r.handlers, r.cacheRepo, r.userRepo, r.limiter)
routers.StartEngine(router)
routers.StartEngine(ctx, router)
}
func (r *appRuntime) close() {