Version: 0.9.69.dev.260504

后端:
1. 阶段 4 active-scheduler 服务边界落地,新增 `cmd/active-scheduler`、`services/active_scheduler`、`shared/contracts/activescheduler` 和 active-scheduler port,迁移 dry-run、trigger、preview、confirm zrpc 能力
2. active-scheduler outbox consumer、relay、retry loop 和 due job scanner 迁入独立服务入口,gateway `/active-schedule/*` 改为通过 zrpc client 调用
3. gateway 目录收口为 `gateway/api` + `gateway/client`,统一归档 userauth、notification、active-scheduler 的 HTTP 门面和 zrpc client
4. 将旧 `backend/active_scheduler` 领域核心下沉到 `services/active_scheduler/core`,清退旧根目录活跃实现,并补充 active-scheduler 启动期跨域依赖表检查
5. 调整单体启动与 outbox 归属,`cmd/all` 不再启动 active-scheduler workflow、scanner 或 handler

文档:
1. 更新微服务迁移计划,将阶段 4 active-scheduler 标记为首轮收口完成,并明确下一阶段进入 schedule / task / course / task-class
This commit is contained in:
Losita
2026-05-04 21:01:00 +08:00
parent abe3b4960e
commit 4d9a5c4d30
66 changed files with 2048 additions and 466 deletions

View File

@@ -0,0 +1,131 @@
package dao
import (
"fmt"
outboxinfra "github.com/LoveLosita/smartflow/backend/infra/outbox"
coremodel "github.com/LoveLosita/smartflow/backend/model"
"github.com/spf13/viper"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// OpenDBFromConfig 创建 active-scheduler 服务自己的数据库句柄。
//
// 职责边界:
// 1. 只迁移 active-scheduler 拥有的 trigger / preview / job / session 表和本服务 outbox 表;
// 2. 不迁移 task、schedule、agent、notification 或 user/auth 表,避免独立进程越权管理其它服务模型;
// 3. 返回的 *gorm.DB 供服务内主链路、due job scanner 和 outbox consumer 复用。
func OpenDBFromConfig() (*gorm.DB, error) {
host := viper.GetString("database.host")
port := viper.GetString("database.port")
user := viper.GetString("database.user")
password := viper.GetString("database.password")
dbname := viper.GetString("database.dbname")
dsn := fmt.Sprintf(
"%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
user, password, host, port, dbname,
)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
return nil, err
}
if err = db.AutoMigrate(
&coremodel.ActiveScheduleJob{},
&coremodel.ActiveScheduleTrigger{},
&coremodel.ActiveSchedulePreview{},
&coremodel.ActiveScheduleSession{},
); err != nil {
return nil, fmt.Errorf("auto migrate active-scheduler tables failed: %w", err)
}
if err = autoMigrateActiveSchedulerOutboxTable(db); err != nil {
return nil, err
}
if err = ensureRuntimeDependencyTables(db); err != nil {
return nil, err
}
return db, nil
}
// autoMigrateActiveSchedulerOutboxTable 只迁移 active-scheduler 服务自己的 outbox 物理表。
//
// 职责边界:
// 1. 只负责 active-scheduler.outbox 对应表,不碰其它服务 outbox
// 2. 让独立 active-scheduler 服务可以单独发布 trigger 并消费 active_schedule.triggered
// 3. 若后续调整 outbox 表名,只改 service catalog不在这里硬编码。
func autoMigrateActiveSchedulerOutboxTable(db *gorm.DB) error {
cfg, ok := outboxinfra.ResolveServiceConfig(outboxinfra.ServiceActiveScheduler)
if !ok {
return fmt.Errorf("resolve active-scheduler outbox config failed")
}
if err := db.Table(cfg.TableName).AutoMigrate(&coremodel.AgentOutboxMessage{}); err != nil {
return fmt.Errorf("auto migrate active-scheduler outbox table failed for %s (%s): %w", cfg.Name, cfg.TableName, err)
}
return nil
}
type runtimeDependencyTable struct {
Name string
Reason string
}
// ensureRuntimeDependencyTables 在服务启动期校验迁移期共享主库依赖。
//
// 职责边界:
// 1. 只检查表是否存在,不 AutoMigrate、不补列、不修改任何跨域表
// 2. 把 active-scheduler 运行时仍然需要的 task / schedule / agent / notification outbox 边界显式化;
// 3. 若部署顺序、库权限或表结构归属不满足,启动阶段直接 fail fast避免第一次 trigger 才反复重试。
func ensureRuntimeDependencyTables(db *gorm.DB) error {
if db == nil {
return fmt.Errorf("active-scheduler runtime dependency check failed: db is nil")
}
for _, table := range activeSchedulerRuntimeDependencyTables() {
if err := ensureTableExists(db, table); err != nil {
return err
}
}
return nil
}
// ensureTableExists 只做存在性探测。
//
// 职责边界:
// 1. 不负责判断字段是否兼容,字段级契约由拥有该表的服务迁移脚本保证;
// 2. 不负责自动修复缺失表,避免 active-scheduler 越权创建其它服务的数据模型;
// 3. 返回错误会阻止服务启动,让部署问题尽早显现。
func ensureTableExists(db *gorm.DB, table runtimeDependencyTable) error {
if table.Name == "" {
return fmt.Errorf("active-scheduler runtime dependency table name is empty: %s", table.Reason)
}
if db.Migrator().HasTable(table.Name) {
return nil
}
return fmt.Errorf("active-scheduler runtime dependency table missing: %s (%s)", table.Name, table.Reason)
}
// activeSchedulerRuntimeDependencyTables 列出迁移期运行仍需共享主库访问的外部表。
//
// 说明:
// 1. active-scheduler 自有表在 OpenDBFromConfig 内迁移,这里只放跨域依赖;
// 2. notification outbox 表名来自 service catalog避免和 outbox 多表路由配置漂移;
// 3. 后续切到 task/schedule/agent/notification RPC 或 read model 后,应从这里移除对应表依赖。
func activeSchedulerRuntimeDependencyTables() []runtimeDependencyTable {
notificationOutboxTable := "notification_outbox_messages"
if cfg, ok := outboxinfra.ResolveServiceConfig(outboxinfra.ServiceNotification); ok && cfg.TableName != "" {
notificationOutboxTable = cfg.TableName
}
return []runtimeDependencyTable{
{Name: "tasks", Reason: "dry-run 读取 task_pool 事实confirm 时锁定 task_pool 目标"},
{Name: "schedule_events", Reason: "dry-run 读取日程事实confirm 时写入正式日程事件"},
{Name: "schedules", Reason: "dry-run 读取节次占用confirm 时写入正式节次"},
{Name: "task_classes", Reason: "confirm create_makeup 时校验 task_item 归属"},
{Name: "task_items", Reason: "confirm create_makeup 时锁定 task_item 目标"},
{Name: "agent_chats", Reason: "trigger 生成 preview 后预建主动调度会话"},
{Name: "chat_histories", Reason: "trigger 生成 preview 后写入会话首屏消息"},
{Name: "agent_timeline_events", Reason: "trigger 生成 preview 后写入主动调度时间线卡片"},
{Name: notificationOutboxTable, Reason: "ShouldNotify=true 时投递 notification.feishu.requested 事件"},
}
}