mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-02 14:56:38 +08:00
feat(core): implement cordis configuration extension and migrate all plugins
This commit is contained in:
@@ -5,7 +5,6 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"Wavelet/pkg/config"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -34,35 +33,35 @@ var (
|
||||
chDB *gorm.DB
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !config.Config.ClickHouse.Enabled {
|
||||
return
|
||||
// InitClickHouseWithConfig initializes the ClickHouse connection using the provided configuration.
|
||||
func InitClickHouseWithConfig(cfg ClickHouseConfig) error {
|
||||
if !cfg.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.Config.ClickHouse
|
||||
if cfg.Database == "" {
|
||||
log.Fatalf("[ClickHouse] database name is required (expected: wavelet)\n")
|
||||
return fmt.Errorf("[ClickHouse] database name is required (expected: wavelet)")
|
||||
}
|
||||
|
||||
opts := buildClickHouseOptions()
|
||||
opts := buildClickHouseOptions(cfg)
|
||||
|
||||
var err error
|
||||
ChConn, err = clickhouse.Open(opts)
|
||||
if err != nil {
|
||||
log.Fatalf("[ClickHouse] init connection failed: %v\n", err)
|
||||
return fmt.Errorf("[ClickHouse] init connection failed: %w", err)
|
||||
}
|
||||
|
||||
if err = ChConn.Ping(context.Background()); err != nil {
|
||||
log.Fatalf("[ClickHouse] ping failed: %v\n", err)
|
||||
return fmt.Errorf("[ClickHouse] ping failed: %w", err)
|
||||
}
|
||||
|
||||
chDB, err = gorm.Open(clickhouseDriver.New(clickhouseDriver.Config{
|
||||
DSN: buildClickHouseDSN(),
|
||||
DSN: buildClickHouseDSN(cfg),
|
||||
}), &gorm.Config{
|
||||
SkipDefaultTransaction: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("[ClickHouse] init gorm connection failed: %v\n", err)
|
||||
return fmt.Errorf("[ClickHouse] init gorm connection failed: %w", err)
|
||||
}
|
||||
|
||||
if err = chDB.Use(
|
||||
@@ -74,12 +73,12 @@ func init() {
|
||||
),
|
||||
),
|
||||
); err != nil {
|
||||
log.Fatalf("[ClickHouse] init trace failed: %v\n", err)
|
||||
return fmt.Errorf("[ClickHouse] init trace failed: %w", err)
|
||||
}
|
||||
|
||||
sqlDB, err := chDB.DB()
|
||||
if err != nil {
|
||||
log.Fatalf("[ClickHouse] load sql db failed: %v\n", err)
|
||||
return fmt.Errorf("[ClickHouse] load sql db failed: %w", err)
|
||||
}
|
||||
|
||||
sqlDB.SetMaxIdleConns(cfg.MaxIdleConn)
|
||||
@@ -87,11 +86,10 @@ func init() {
|
||||
sqlDB.SetConnMaxLifetime(time.Duration(cfg.ConnMaxLifetime) * time.Second)
|
||||
|
||||
log.Println("[ClickHouse] connection established successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildClickHouseOptions() *clickhouse.Options {
|
||||
cfg := config.Config.ClickHouse
|
||||
|
||||
func buildClickHouseOptions(cfg ClickHouseConfig) *clickhouse.Options {
|
||||
return &clickhouse.Options{
|
||||
Addr: cfg.Hosts,
|
||||
Auth: clickhouse.Auth{
|
||||
@@ -114,9 +112,7 @@ func buildClickHouseOptions() *clickhouse.Options {
|
||||
}
|
||||
}
|
||||
|
||||
func buildClickHouseDSN() string {
|
||||
cfg := config.Config.ClickHouse
|
||||
|
||||
func buildClickHouseDSN(cfg ClickHouseConfig) string {
|
||||
chURL := &url.URL{
|
||||
Scheme: "clickhouse",
|
||||
Host: strings.Join(cfg.Hosts, ","),
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package database
|
||||
|
||||
import "time"
|
||||
|
||||
// ReplicaConfig declares a single read-replica configuration for PostgreSQL.
|
||||
type ReplicaConfig struct {
|
||||
Host string `config:"host"`
|
||||
Port int `config:"port"`
|
||||
Username string `config:"username"`
|
||||
Password string `config:"password" secret:"true"`
|
||||
}
|
||||
|
||||
// Config declares PostgreSQL and SQLite database configuration.
|
||||
type Config struct {
|
||||
Enabled bool `config:"enabled" env:"DB_ENABLED" default:"false" autoEnable:"DB_HOST"`
|
||||
SQLitePath string `config:"sqlite_path" env:"SQLITE_PATH"`
|
||||
Host string `config:"host" env:"DB_HOST"`
|
||||
Port int `config:"port" env:"DB_PORT" default:"5432"`
|
||||
Username string `config:"username" env:"DB_USERNAME"`
|
||||
Password string `config:"password" env:"DB_PASSWORD" secret:"true"`
|
||||
Database string `config:"database" env:"DB_NAME" default:"wavelet"`
|
||||
MaxIdleConn int `config:"max_idle_conn" env:"DB_MAX_IDLE_CONN" default:"10"`
|
||||
MaxOpenConn int `config:"max_open_conn" env:"DB_MAX_OPEN_CONN" default:"100"`
|
||||
ConnMaxLifetime int `config:"conn_max_lifetime" env:"DB_CONN_MAX_LIFETIME" default:"3600"`
|
||||
ConnMaxIdleTime int `config:"conn_max_idle_time" env:"DB_CONN_MAX_IDLE_TIME" default:"600"`
|
||||
LogLevel string `config:"log_level" env:"DB_LOG_LEVEL" default:"warn"`
|
||||
SSLMode string `config:"ssl_mode" env:"DB_SSL_MODE" default:"disable"`
|
||||
TimeZone string `config:"time_zone" env:"DB_TIMEZONE" default:"UTC"`
|
||||
ApplicationName string `config:"application_name" env:"DB_APPLICATION_NAME" default:"wavelet"`
|
||||
SearchPath string `config:"search_path" env:"DB_SEARCH_PATH" default:"public"`
|
||||
PreferSimpleProtocol bool `config:"prefer_simple_protocol" env:"DB_PREFER_SIMPLE_PROTOCOL"`
|
||||
StatementCacheCapacity int `config:"statement_cache_capacity" env:"DB_STATEMENT_CACHE_CAPACITY"`
|
||||
DefaultQueryExecMode string `config:"default_query_exec_mode" env:"DB_DEFAULT_QUERY_EXEC_MODE"`
|
||||
Replicas []ReplicaConfig `config:"replicas"`
|
||||
SlowThreshold time.Duration `config:"slow_threshold" env:"DB_SLOW_THRESHOLD" default:"200ms"`
|
||||
}
|
||||
|
||||
// ClickHouseConfig declares the configuration for ClickHouse analytical storage.
|
||||
type ClickHouseConfig struct {
|
||||
Enabled bool `config:"enabled" env:"CLICKHOUSE_ENABLED" default:"false" autoEnable:"CLICKHOUSE_HOST"`
|
||||
Hosts []string `config:"hosts" env:"CLICKHOUSE_HOST"`
|
||||
Username string `config:"username" env:"CLICKHOUSE_USERNAME"`
|
||||
Password string `config:"password" env:"CLICKHOUSE_PASSWORD" secret:"true"`
|
||||
Database string `config:"database" env:"CLICKHOUSE_NAME" default:"wavelet"`
|
||||
MaxIdleConn int `config:"max_idle_conn" env:"CLICKHOUSE_MAX_IDLE_CONN" default:"10"`
|
||||
MaxOpenConn int `config:"max_open_conn" env:"CLICKHOUSE_MAX_OPEN_CONN" default:"50"`
|
||||
ConnMaxLifetime int `config:"conn_max_lifetime" env:"CLICKHOUSE_CONN_MAX_LIFETIME" default:"3600"`
|
||||
DialTimeout int `config:"dial_timeout" env:"CLICKHOUSE_DIAL_TIMEOUT" default:"10"`
|
||||
BlockBufferSize uint8 `config:"block_buffer_size" env:"CLICKHOUSE_BLOCK_BUFFER_SIZE" default:"10"`
|
||||
}
|
||||
|
||||
type appEnvConfig struct {
|
||||
Env string `config:"env" env:"APP_ENV" default:"development"`
|
||||
}
|
||||
@@ -56,17 +56,45 @@ func (p *Plugin) Name() string {
|
||||
return "database"
|
||||
}
|
||||
|
||||
// DeclareConfig declares database configuration keys.
|
||||
func (p *Plugin) DeclareConfig() []core.ConfigBinding {
|
||||
return []core.ConfigBinding{
|
||||
{Prefix: "database", Target: &Config{}},
|
||||
{Prefix: "clickhouse", Target: &ClickHouseConfig{}},
|
||||
{Prefix: "app", Target: &appEnvConfig{}},
|
||||
}
|
||||
}
|
||||
|
||||
// Apply mounts the database service into the Context.
|
||||
func (p *Plugin) Apply(ctx *core.Context) error {
|
||||
var dbCfg Config
|
||||
if err := ctx.Config().Bind("database", &dbCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var chCfg ClickHouseConfig
|
||||
if err := ctx.Config().Bind("clickhouse", &chCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var appCfg appEnvConfig
|
||||
_ = ctx.Config().Bind("app", &appCfg)
|
||||
|
||||
targetDB := p.db
|
||||
if targetDB == nil {
|
||||
var err error
|
||||
targetDB, err = InitDB()
|
||||
targetDB, err = InitDBWithConfig(dbCfg, appCfg.Env == "production" || appCfg.Env == "prod")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if chCfg.Enabled {
|
||||
if err := InitClickHouseWithConfig(chCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
svc := &dbServiceImpl{
|
||||
primary: targetDB,
|
||||
namedDBs: p.namedDBs,
|
||||
|
||||
@@ -37,9 +37,11 @@ func TestDatabasePlugin(t *testing.T) {
|
||||
database.WithDB(gdb),
|
||||
database.WithNamedDB("analytics", namedDB),
|
||||
)
|
||||
assert.Equal(t, "database", p.Name())
|
||||
|
||||
ctx := core.NewContext(context.Background())
|
||||
ctx.Config().SetSource(core.NewMapSource(map[string]any{
|
||||
"database.enabled": false,
|
||||
}))
|
||||
require.NoError(t, ctx.Config().Resolve())
|
||||
require.NoError(t, p.Apply(ctx))
|
||||
|
||||
svc, err := core.Inject[contracts.DBService](ctx)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"Wavelet/pkg/config"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -30,15 +29,19 @@ const sqliteDirMode = 0o750
|
||||
|
||||
// InitDB 初始化主数据库实例(支持 PostgreSQL / SQLite)
|
||||
func InitDB() (*gorm.DB, error) {
|
||||
if !config.Config.Database.Enabled {
|
||||
return initSQLite()
|
||||
}
|
||||
return initPostgres()
|
||||
return InitDBWithConfig(Config{}, false)
|
||||
}
|
||||
|
||||
// initSQLite 初始化 SQLite 数据库(PostgreSQL 禁用时的后备方案)
|
||||
func initSQLite() (*gorm.DB, error) {
|
||||
sqlitePath := config.Config.Database.SQLitePath
|
||||
// InitDBWithConfig initializes the main database with the provided config.
|
||||
func InitDBWithConfig(cfg Config, isProd bool) (*gorm.DB, error) {
|
||||
if !cfg.Enabled {
|
||||
return initSQLiteWithConfig(cfg, isProd)
|
||||
}
|
||||
return initPostgresWithConfig(cfg, isProd)
|
||||
}
|
||||
|
||||
func initSQLiteWithConfig(cfg Config, isProd bool) (*gorm.DB, error) {
|
||||
sqlitePath := cfg.SQLitePath
|
||||
if sqlitePath == "" {
|
||||
sqlitePath = "./data/wavelet.db"
|
||||
}
|
||||
@@ -54,9 +57,9 @@ func initSQLite() (*gorm.DB, error) {
|
||||
targetDB, err := gorm.Open(sqlite.Open(sqlitePath), &gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
Logger: &gormZapLogger{
|
||||
logLevel: parseLogLevel(config.Config.Database.LogLevel),
|
||||
slowThreshold: config.Config.Database.SlowThreshold,
|
||||
ignoreRecordNotFoundError: config.Config.App.IsProduction(),
|
||||
logLevel: parseLogLevel(cfg.LogLevel),
|
||||
slowThreshold: cfg.SlowThreshold,
|
||||
ignoreRecordNotFoundError: isProd,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -81,24 +84,21 @@ func initSQLite() (*gorm.DB, error) {
|
||||
return targetDB, nil
|
||||
}
|
||||
|
||||
// initPostgres 初始化 PostgreSQL 数据库
|
||||
func initPostgres() (*gorm.DB, error) {
|
||||
dbConfig := config.Config.Database
|
||||
|
||||
func initPostgresWithConfig(cfg Config, isProd bool) (*gorm.DB, error) {
|
||||
// 构建主库 DSN 并连接
|
||||
primaryDSN := buildDSN(dbConfig.Host, dbConfig.Port, dbConfig.Username, dbConfig.Password)
|
||||
primaryDSN := buildDSN(cfg, cfg.Host, cfg.Port, cfg.Username, cfg.Password)
|
||||
|
||||
pgConfig := postgres.Config{
|
||||
DSN: primaryDSN,
|
||||
PreferSimpleProtocol: dbConfig.PreferSimpleProtocol,
|
||||
PreferSimpleProtocol: cfg.PreferSimpleProtocol,
|
||||
}
|
||||
|
||||
targetDB, err := gorm.Open(postgres.New(pgConfig), &gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
Logger: &gormZapLogger{
|
||||
logLevel: parseLogLevel(config.Config.Database.LogLevel),
|
||||
slowThreshold: config.Config.Database.SlowThreshold,
|
||||
ignoreRecordNotFoundError: config.Config.App.IsProduction(),
|
||||
logLevel: parseLogLevel(cfg.LogLevel),
|
||||
slowThreshold: cfg.SlowThreshold,
|
||||
ignoreRecordNotFoundError: isProd,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -110,9 +110,9 @@ func initPostgres() (*gorm.DB, error) {
|
||||
tracing.NewPlugin(
|
||||
tracing.WithoutMetrics(),
|
||||
tracing.WithAttributes(
|
||||
attribute.String("db.instance", dbConfig.Database),
|
||||
attribute.String("db.ip", dbConfig.Host),
|
||||
attribute.String("server.address", net.JoinHostPort(dbConfig.Host, strconv.Itoa(dbConfig.Port))),
|
||||
attribute.String("db.instance", cfg.Database),
|
||||
attribute.String("db.ip", cfg.Host),
|
||||
attribute.String("server.address", net.JoinHostPort(cfg.Host, strconv.Itoa(cfg.Port))),
|
||||
attribute.String("db.system", "PostgreSQL"),
|
||||
),
|
||||
),
|
||||
@@ -120,21 +120,21 @@ func initPostgres() (*gorm.DB, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(dbConfig.Replicas) > 0 {
|
||||
if len(cfg.Replicas) > 0 {
|
||||
var replicaDialectors []gorm.Dialector
|
||||
for _, replica := range dbConfig.Replicas {
|
||||
for _, replica := range cfg.Replicas {
|
||||
username := replica.Username
|
||||
if username == "" {
|
||||
username = dbConfig.Username
|
||||
username = cfg.Username
|
||||
}
|
||||
password := replica.Password
|
||||
if password == "" {
|
||||
password = dbConfig.Password
|
||||
password = cfg.Password
|
||||
}
|
||||
replicaDSN := buildDSN(replica.Host, replica.Port, username, password)
|
||||
replicaDSN := buildDSN(cfg, replica.Host, replica.Port, username, password)
|
||||
replicaDialectors = append(replicaDialectors, postgres.New(postgres.Config{
|
||||
DSN: replicaDSN,
|
||||
PreferSimpleProtocol: dbConfig.PreferSimpleProtocol,
|
||||
PreferSimpleProtocol: cfg.PreferSimpleProtocol,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -143,15 +143,15 @@ func initPostgres() (*gorm.DB, error) {
|
||||
Policy: dbresolver.RandomPolicy{},
|
||||
})
|
||||
|
||||
resolver.SetMaxIdleConns(dbConfig.MaxIdleConn).
|
||||
SetMaxOpenConns(dbConfig.MaxOpenConn).
|
||||
SetConnMaxLifetime(time.Duration(dbConfig.ConnMaxLifetime) * time.Second).
|
||||
SetConnMaxIdleTime(time.Duration(dbConfig.ConnMaxIdleTime) * time.Second)
|
||||
resolver.SetMaxIdleConns(cfg.MaxIdleConn).
|
||||
SetMaxOpenConns(cfg.MaxOpenConn).
|
||||
SetConnMaxLifetime(time.Duration(cfg.ConnMaxLifetime) * time.Second).
|
||||
SetConnMaxIdleTime(time.Duration(cfg.ConnMaxIdleTime) * time.Second)
|
||||
|
||||
if err = targetDB.Use(resolver); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("[PostgreSQL] initialized in Primary-Replica mode (%d replicas)\n", len(dbConfig.Replicas))
|
||||
log.Printf("[PostgreSQL] initialized in Primary-Replica mode (%d replicas)\n", len(cfg.Replicas))
|
||||
} else {
|
||||
log.Println("[PostgreSQL] initialized in Standalone mode")
|
||||
}
|
||||
@@ -162,18 +162,17 @@ func initPostgres() (*gorm.DB, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sqlDB.SetMaxIdleConns(dbConfig.MaxIdleConn)
|
||||
sqlDB.SetMaxOpenConns(dbConfig.MaxOpenConn)
|
||||
sqlDB.SetConnMaxLifetime(time.Duration(dbConfig.ConnMaxLifetime) * time.Second)
|
||||
sqlDB.SetConnMaxIdleTime(time.Duration(dbConfig.ConnMaxIdleTime) * time.Second)
|
||||
sqlDB.SetMaxIdleConns(cfg.MaxIdleConn)
|
||||
sqlDB.SetMaxOpenConns(cfg.MaxOpenConn)
|
||||
sqlDB.SetConnMaxLifetime(time.Duration(cfg.ConnMaxLifetime) * time.Second)
|
||||
sqlDB.SetConnMaxIdleTime(time.Duration(cfg.ConnMaxIdleTime) * time.Second)
|
||||
|
||||
db = targetDB
|
||||
return targetDB, nil
|
||||
}
|
||||
|
||||
// buildDSN 构建 PostgreSQL DSN
|
||||
func buildDSN(host string, port int, username, password string) string {
|
||||
cfg := config.Config.Database
|
||||
func buildDSN(cfg Config, host string, port int, username, password string) string {
|
||||
pqURL := &url.URL{
|
||||
Scheme: "postgres",
|
||||
Host: net.JoinHostPort(host, strconv.Itoa(port)),
|
||||
|
||||
Reference in New Issue
Block a user