This commit is contained in:
2026-08-16 20:58:57 +08:00
parent a85477be80
commit a9e1f9e6f8
42 changed files with 1569 additions and 156 deletions
+46 -25
View File
@@ -3,7 +3,7 @@ package data
import (
"context"
"fmt"
"log"
"log/slog"
"sync"
"time"
@@ -16,24 +16,33 @@ import (
var ProviderSet = wire.NewSet(NewData, NewRuntimeSettings, NewTokenIssuer, NewUserRepo, NewInitializationRepo, NewAuthorityAccessRepo, NewAPIRepo, NewPermissionRepo, NewMenuRepo, NewDepartmentRepo, NewPositionRepo, NewDictionaryRepo, NewParameterRepo, NewAPITokenRepo, NewSecurityRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewAuditRecorderRepo, NewLogFileRepo, NewTaskRepo, NewMediaRepo, NewAnnouncementRepo, NewEmailRepo, NewCache, NewFileStorage)
type Data struct {
initMu sync.Mutex
configMu sync.Mutex
gormDB *reloadableDB
redis *reloadableRedis
mongo *reloadableMongo
runtime *conf.Runtime
storage *reloadableStorage
dbListMu sync.RWMutex
dbList map[string]*gorm.DB
initMu sync.Mutex
configMu sync.Mutex
gormDB *reloadableDB
redis *reloadableRedis
mongo *reloadableMongo
runtime *conf.Runtime
storage *reloadableStorage
dbListMu sync.RWMutex
dbList map[string]*gorm.DB
appLogger *slog.Logger
auditLog *dataScopeAuditWriter
}
func openDatabaseList(configs []*conf.Data_Database) (map[string]*gorm.DB, error) {
func (d *Data) logger() *slog.Logger {
if d != nil && d.appLogger != nil {
return d.appLogger
}
return slog.Default()
}
func openDatabaseList(configs []*conf.Data_Database, appLogger ...*slog.Logger) (map[string]*gorm.DB, error) {
items := make(map[string]*gorm.DB)
for _, config := range configs {
if config == nil || config.Disable || config.AliasName == "" {
continue
}
db, err := openDatabase(config, false, "")
db, err := openDatabase(config, false, "", appLogger...)
if err != nil {
for _, opened := range items {
if sqlDB, dbErr := opened.DB(); dbErr == nil {
@@ -42,7 +51,6 @@ func openDatabaseList(configs []*conf.Data_Database) (map[string]*gorm.DB, error
}
return nil, fmt.Errorf("open database %q: %w", config.AliasName, err)
}
registerDataScopeCallbacks(db)
items[config.AliasName] = db
}
return items, nil
@@ -77,30 +85,38 @@ func (d *Data) database(name string) (*gorm.DB, error) {
return db, nil
}
func NewData(runtime *conf.Runtime) (*Data, func(), error) {
func NewData(runtime *conf.Runtime, appLogger *slog.Logger) (*Data, func(), error) {
if appLogger == nil {
appLogger = slog.Default()
}
c := runtime.Data()
if c == nil || c.Database == nil {
return nil, nil, fmt.Errorf("database configuration is required")
}
d := &Data{runtime: runtime}
db, err := openDatabase(c.Database, false, "")
d := &Data{runtime: runtime, appLogger: appLogger}
db, err := openDatabase(c.Database, false, "", appLogger)
usingFallback := false
if err != nil {
// The initialization endpoint must remain available when the configured
// target database has not been created yet.
log.Printf("configured database unavailable before initialization: %v", err)
db, err = openFallbackDatabase()
appLogger.Warn("configured database unavailable before initialization", "mod", "system", "error", err)
db, err = openFallbackDatabase(appLogger)
if err != nil {
return nil, nil, fmt.Errorf("open bootstrap database: %w", err)
}
usingFallback = true
}
d.gormDB = newReloadableDB(db)
d.dbList, err = openDatabaseList(c.DatabaseList)
d.gormDB = newReloadableDB(db, d.enqueueDataScopeAudit)
d.auditLog = newDataScopeAuditWriter(d, appLogger)
d.dbList, err = openDatabaseList(c.DatabaseList, appLogger)
if err != nil {
d.auditLog.Close()
d.gormDB.close()
return nil, nil, err
}
for _, item := range d.dbList {
registerDataScopeCallbacks(item, d.enqueueDataScopeAudit)
}
admin := runtime.Admin()
disableAutoMigrate := admin != nil && admin.System != nil && admin.System.DisableAutoMigrate
if !usingFallback && !disableAutoMigrate {
@@ -109,17 +125,18 @@ func NewData(runtime *conf.Runtime) (*Data, func(), error) {
}
}
useRedis := admin != nil && admin.System != nil && admin.System.UseRedis
d.redis = newReloadableRedis(openRedis(c.Redis, useRedis))
d.redis = newReloadableRedis(openRedis(c.Redis, useRedis, appLogger))
useMongo := admin != nil && admin.System != nil && admin.System.UseMongo
mongoClient, err := openMongo(c.Mongo, useMongo)
if err != nil {
log.Printf("mongo unavailable: %v", err)
appLogger.Error("mongo unavailable", "mod", "mongo", "error", err)
mongoClient = nil
}
d.mongo = newReloadableMongo(mongoClient)
stopConfigWatcher := d.watchConfig()
cleanup := func() {
stopConfigWatcher()
d.auditLog.Close()
d.gormDB.close()
closeDatabaseList(d.dbList)
d.redis.close()
@@ -128,7 +145,7 @@ func NewData(runtime *conf.Runtime) (*Data, func(), error) {
return d, cleanup, nil
}
func openRedis(config *conf.Data_Redis, enabled bool) redis.UniversalClient {
func openRedis(config *conf.Data_Redis, enabled bool, appLogger ...*slog.Logger) redis.UniversalClient {
if !enabled || config == nil || (config.Addr == "" && len(config.ClusterAddrs) == 0) {
return nil
}
@@ -152,7 +169,11 @@ func openRedis(config *conf.Data_Redis, enabled bool) redis.UniversalClient {
pingCtx, cancel := context.WithTimeout(context.Background(), 800*time.Millisecond)
defer cancel()
if err := candidate.Ping(pingCtx).Err(); err != nil {
log.Printf("redis unavailable, using in-memory cache: %v", err)
log := slog.Default()
if len(appLogger) > 0 && appLogger[0] != nil {
log = appLogger[0]
}
log.Warn("redis unavailable, using in-memory cache", "mod", "redis", "error", err)
_ = candidate.Close()
return nil
}
@@ -160,6 +181,6 @@ func openRedis(config *conf.Data_Redis, enabled bool) redis.UniversalClient {
}
func (d *Data) activateDatabase(db *gorm.DB, config *conf.Data_Database) {
d.gormDB.replace(db)
d.gormDB.replace(db, d.enqueueDataScopeAudit)
d.runtime.UpdateDatabase(config)
}