// Package store 封装 MainDB(PgSQL) 与 CacheDB(Redis) 的访问。 package store import ( "context" "errors" "log" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/schema" ) // errStoreDisabled 表示 Postgres 处于降级(未连接)模式,写操作无法进行。 var errStoreDisabled = errors.New("postgres store disabled") // Postgres 持有 MainDB 连接(Users / Billing / DSL)。 // db 为 nil 表示降级模式(连接失败时仍允许网关启动)。 type Postgres struct { db *gorm.DB } // OpenPostgres 用 GORM 连接 MainDB 并自动迁移表结构。 // 表名统一 sundynix_ 前缀 + 单数(User→sundynix_user, Task→sundynix_task)。 // 连接失败不 fatal:返回降级实例,网关仍可启动(无 Docker 跑 demo 时即此路径)。 func OpenPostgres(dsn string) *Postgres { db, err := gorm.Open(postgres.New(postgres.Config{DSN: dsn}), &gorm.Config{ NamingStrategy: schema.NamingStrategy{ TablePrefix: "sundynix_", // 所有表加前缀 SingularTable: true, // 单数表名 }, }) if err != nil { log.Printf("[store] postgres 不可用,降级运行(不持久化): %v", err) return &Postgres{} } if err := db.AutoMigrate(&User{}, &Task{}, &LLMModel{}, &KB{}, &Doc{}, &Agent{}); err != nil { log.Printf("[store] postgres AutoMigrate 失败,降级运行: %v", err) return &Postgres{} } // 回填:kind 列新增前的旧模型行默认归为 chat(幂等)。 db.Model(&LLMModel{}).Where("kind = '' OR kind IS NULL").Update("kind", "chat") log.Println("[store] postgres connected, migrated sundynix_user / sundynix_task") return &Postgres{db: db} } // Enabled 报告是否处于真实持久化模式。 func (p *Postgres) Enabled() bool { return p.db != nil } // SaveTask 持久化一次任务提交(best-effort:降级模式下静默跳过)。 func (p *Postgres) SaveTask(ctx context.Context, id, graph string) error { if p.db == nil { return nil } return p.db.WithContext(ctx).Create(&Task{ID: id, Graph: graph, Status: "submitted"}).Error } // CountTasks 返回已提交任务数(降级模式返回 0)。 func (p *Postgres) CountTasks(ctx context.Context) (int64, error) { if p.db == nil { return 0, nil } var n int64 err := p.db.WithContext(ctx).Model(&Task{}).Count(&n).Error return n, err } // Close 释放底层连接。 func (p *Postgres) Close() { if p.db == nil { return } if sqlDB, err := p.db.DB(); err == nil { _ = sqlDB.Close() } }