后端:
1. 阶段 5 schedule 首刀服务化落地,新增 `cmd/schedule`、`services/schedule/{dao,rpc,sv,core}`、`gateway/client/schedule`、`shared/contracts/schedule` 和 schedule port
2. gateway `/api/v1/schedule/*` 切到 schedule zrpc client,HTTP 门面只保留鉴权、参数绑定、超时和轻量转发
3. active-scheduler 的 schedule facts、feedback 和 confirm apply 改为调用 schedule RPC adapter,减少对 `schedule_events`、`schedules`、`task_classes`、`task_items` 的跨域 DB 依赖
4. 单体聊天主动调度 rerun 的 schedule 读写链路切到 schedule RPC,迁移期仅保留 task facts 直读 Gorm
5. 为 schedule zrpc 补充 `Ping` 启动健康检查,并在 gateway client 与 active-scheduler adapter 初始化时校验服务可用
6. `cmd/schedule` 独立初始化 DB / Redis,只 AutoMigrate schedule 自有表,并显式检查迁移期 task / task-class 依赖表
7. 更新 active-scheduler 依赖表检查和 preview confirm apply 抽象,保留旧 Gorm 实现作为迁移期回退路径
8. 补充 `schedule.rpc` 示例配置和 schedule HTTP RPC 超时配置
文档:
1. 更新微服务迁移计划,将阶段 5 schedule 首刀进展、当前切流点、旧实现保留范围和 active-scheduler DB 依赖收缩情况写入基线
128 lines
5.2 KiB
Go
128 lines
5.2 KiB
Go
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 / 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. schedule 读写已切到 schedule RPC;后续切到 task/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 / due job scanner 仍读取 task_pool 事实,下一轮切 task RPC 后移除"},
|
||
{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 事件"},
|
||
}
|
||
}
|