// Package infrastructure 提供应用程序的基础设施层组件。 // 包括数据库连接初始化、ORM 客户端管理、Redis 连接、数据库迁移等核心功能。 package repository import ( "context" "database/sql" "time" "github.com/Wei-Shaw/sub2api/ent" "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/pkg/timezone" "github.com/Wei-Shaw/sub2api/migrations" "entgo.io/ent/dialect" entsql "entgo.io/ent/dialect/sql" _ "github.com/lib/pq" // PostgreSQL 驱动,通过副作用导入注册驱动 ) // InitEnt 初始化 Ent ORM 客户端并返回客户端实例和底层的 *sql.DB。 // // 该函数执行以下操作: // 1. 初始化全局时区设置,确保时间处理一致性 // 2. 建立 PostgreSQL 数据库连接 // 3. 自动执行数据库迁移,确保 schema 与代码同步 // 4. 创建并返回 Ent 客户端实例 // // 重要提示:调用者必须负责关闭返回的 ent.Client(关闭时会自动关闭底层的 driver/db)。 // // 参数: // - cfg: 应用程序配置,包含数据库连接信息和时区设置 // // 返回: // - *ent.Client: Ent ORM 客户端,用于执行数据库操作 // - *sql.DB: 底层的 SQL 数据库连接,可用于直接执行原生 SQL // - error: 初始化过程中的错误 func InitEnt(cfg *config.Config) (*ent.Client, *sql.DB, error) { // 优先初始化时区设置,确保所有时间操作使用统一的时区。 // 这对于跨时区部署和日志时间戳的一致性至关重要。 if err := timezone.Init(cfg.Timezone); err != nil { return nil, nil, err } // 构建包含时区信息的数据库连接字符串 (DSN)。 // 时区信息会传递给 PostgreSQL,确保数据库层面的时间处理正确。 dsn := cfg.Database.DSNWithTimezone(cfg.Timezone) // 使用 Ent 的 SQL 驱动打开 PostgreSQL 连接。 // dialect.Postgres 指定使用 PostgreSQL 方言进行 SQL 生成。 drv, err := entsql.Open(dialect.Postgres, dsn) if err != nil { return nil, nil, err } applyDBPoolSettings(drv.DB(), cfg) // 确保数据库 schema 已准备就绪。 // SQL 迁移文件是 schema 的权威来源(source of truth)。 // 这种方式比 Ent 的自动迁移更可控,支持复杂的迁移场景。 migrationCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() if err := applyMigrationsFS(migrationCtx, drv.DB(), migrations.FS); err != nil { _ = drv.Close() // 迁移失败时关闭驱动,避免资源泄露 return nil, nil, err } // 创建 Ent 客户端,绑定到已配置的数据库驱动。 client := ent.NewClient(ent.Driver(drv)) return client, drv.DB(), nil }