优化结构
This commit is contained in:
@@ -303,7 +303,7 @@ func (d *Data) reloadConfig(ctx context.Context) error {
|
||||
if configPath == "" {
|
||||
return fmt.Errorf("configuration path is not set")
|
||||
}
|
||||
next, err := readBootstrap(configPath)
|
||||
next, err := config.Load(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -406,7 +406,7 @@ func (d *Data) reloadConfig(ctx context.Context) error {
|
||||
closeDatabaseList(candidateDBList)
|
||||
}
|
||||
}()
|
||||
integrationConfigs, err := readIntegrationRuntime(candidateDB)
|
||||
integrationConfigs, err := readIntegrationRuntime(candidateDB.WithContext(ctx))
|
||||
if err != nil {
|
||||
return fmt.Errorf("reload integration runtime: %w", err)
|
||||
}
|
||||
@@ -417,8 +417,20 @@ func (d *Data) reloadConfig(ctx context.Context) error {
|
||||
registerDataScopeCallbacks(item, d.enqueueDataScopeAudit)
|
||||
}
|
||||
d.replaceDatabaseList(candidateDBList)
|
||||
d.redis.replace(candidateRedis)
|
||||
d.replaceRedisList(candidateRedisList)
|
||||
// openRedis and openRedisList return nil when a ping fails, so replacing
|
||||
// unconditionally would let a transient Redis outage during an unrelated
|
||||
// configuration reload retire the still-healthy clients and silently downgrade
|
||||
// the process to the in-memory cache until the next reload.
|
||||
if candidateRedis != nil || !useRedis || !redisConnectionConfigured(next.Data.Redis) {
|
||||
d.redis.replace(candidateRedis)
|
||||
} else {
|
||||
d.logger().Warn("keeping the previous redis client because the reloaded configuration failed to connect", "mod", "redis")
|
||||
}
|
||||
if candidateRedisList != nil || !useRedisList || len(next.Data.RedisList) == 0 {
|
||||
d.replaceRedisList(candidateRedisList)
|
||||
} else {
|
||||
d.logger().Warn("keeping the previous redis list because the reloaded configuration failed to connect", "mod", "redis")
|
||||
}
|
||||
if mongoErr == nil {
|
||||
d.mongo.replace(candidateMongo)
|
||||
mongoAccepted = true
|
||||
@@ -437,7 +449,3 @@ func (d *Data) reloadConfig(ctx context.Context) error {
|
||||
d.notifyResources()
|
||||
return nil
|
||||
}
|
||||
|
||||
func readBootstrap(configPath string) (*config.Config, error) {
|
||||
return config.Load(configPath)
|
||||
}
|
||||
|
||||
+60
-33
@@ -58,10 +58,14 @@ type Data struct {
|
||||
storage *storage.Reloadable
|
||||
dbListMu sync.RWMutex
|
||||
dbList map[string]*gorm.DB
|
||||
appLogger *slog.Logger
|
||||
auditLog *dataScopeAuditWriter
|
||||
catalog module.Catalog
|
||||
resourceHook func()
|
||||
// Handles replaced by a hot reload of the named lists. They follow the same
|
||||
// grace period as the primary pool so in-flight queries are not cut off.
|
||||
retiredDBList retiredSet[*gorm.DB]
|
||||
retiredRedisList retiredSet[redis.UniversalClient]
|
||||
appLogger *slog.Logger
|
||||
auditLog *dataScopeAuditWriter
|
||||
catalog module.Catalog
|
||||
resourceHook func()
|
||||
}
|
||||
|
||||
// DB exposes the active primary database to narrowly scoped data submodules.
|
||||
@@ -100,7 +104,16 @@ func (d *Data) IntegrationRuntime() *runtimeconfig.Store {
|
||||
// Database resolves the primary or a named database for repositories such as
|
||||
// the system export module.
|
||||
func (d *Data) Database(name string) (*gorm.DB, error) {
|
||||
return d.database(name)
|
||||
if name == "" {
|
||||
return d.gormDB.DB(), nil
|
||||
}
|
||||
d.dbListMu.RLock()
|
||||
db := d.dbList[name]
|
||||
d.dbListMu.RUnlock()
|
||||
if db == nil {
|
||||
return nil, fmt.Errorf("database %q not found", name)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func (d *Data) RedisClient() redis.UniversalClient {
|
||||
@@ -190,13 +203,13 @@ func (d *Data) logger() *slog.Logger {
|
||||
return slog.Default()
|
||||
}
|
||||
|
||||
func openDatabaseList(configs []*config.Database, appLogger ...*slog.Logger) (map[string]*gorm.DB, error) {
|
||||
func openDatabaseList(configs []*config.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, "", appLogger...)
|
||||
db, err := openDatabase(config, false, "", appLogger)
|
||||
if err != nil {
|
||||
for _, opened := range items {
|
||||
if sqlDB, dbErr := opened.DB(); dbErr == nil {
|
||||
@@ -212,31 +225,26 @@ func openDatabaseList(configs []*config.Database, appLogger ...*slog.Logger) (ma
|
||||
|
||||
func closeDatabaseList(items map[string]*gorm.DB) {
|
||||
for _, db := range items {
|
||||
if sqlDB, err := db.DB(); err == nil {
|
||||
_ = sqlDB.Close()
|
||||
if db != nil {
|
||||
closeGormDB(db)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// replaceDatabaseList swaps the named database handles. Superseded handles are
|
||||
// retired instead of closed on the spot: a request that already resolved a
|
||||
// handle would otherwise fail with "sql: database is closed" mid-flight.
|
||||
func (d *Data) replaceDatabaseList(items map[string]*gorm.DB) {
|
||||
d.dbListMu.Lock()
|
||||
old := d.dbList
|
||||
d.dbList = items
|
||||
d.dbListMu.Unlock()
|
||||
closeDatabaseList(old)
|
||||
}
|
||||
|
||||
func (d *Data) database(name string) (*gorm.DB, error) {
|
||||
if name == "" {
|
||||
return d.gormDB.DB(), nil
|
||||
for name, db := range old {
|
||||
if db == nil || db == items[name] {
|
||||
continue
|
||||
}
|
||||
d.retiredDBList.retire(db, closeGormDB)
|
||||
}
|
||||
d.dbListMu.RLock()
|
||||
db := d.dbList[name]
|
||||
d.dbListMu.RUnlock()
|
||||
if db == nil {
|
||||
return nil, fmt.Errorf("database %q not found", name)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func NewData(runtime *config.Store, appLogger *slog.Logger, storageManager *storage.Reloadable, catalog module.Catalog) (*Data, func(), error) {
|
||||
@@ -264,10 +272,16 @@ func NewData(runtime *config.Store, appLogger *slog.Logger, storageManager *stor
|
||||
d.gormDB.close()
|
||||
}
|
||||
closeDatabaseList(d.dbList)
|
||||
for _, db := range d.retiredDBList.drain() {
|
||||
closeGormDB(db)
|
||||
}
|
||||
if d.redis != nil {
|
||||
d.redis.close()
|
||||
}
|
||||
closeRedisList(d.redisList)
|
||||
for _, client := range d.retiredRedisList.drain() {
|
||||
closeRedisClient(client)
|
||||
}
|
||||
if d.mongo != nil {
|
||||
d.mongo.close()
|
||||
}
|
||||
@@ -363,10 +377,20 @@ func NewData(runtime *config.Store, appLogger *slog.Logger, storageManager *stor
|
||||
return d, cleanup, nil
|
||||
}
|
||||
|
||||
func openRedis(config *config.Redis, enabled bool, appLogger ...*slog.Logger) redis.UniversalClient {
|
||||
if !enabled || config == nil || (config.Addr == "" && len(config.ClusterAddrs) == 0) {
|
||||
// redisConnectionConfigured reports whether a Redis block names an endpoint.
|
||||
// The reload path shares it with openRedis so "no endpoint configured" is never
|
||||
// confused with "configured endpoint failed to answer".
|
||||
func redisConnectionConfigured(config *config.Redis) bool {
|
||||
return config != nil && (config.Addr != "" || len(config.ClusterAddrs) > 0)
|
||||
}
|
||||
|
||||
func openRedis(config *config.Redis, enabled bool, appLogger *slog.Logger) redis.UniversalClient {
|
||||
if !enabled || !redisConnectionConfigured(config) {
|
||||
return nil
|
||||
}
|
||||
if appLogger == nil {
|
||||
appLogger = slog.Default()
|
||||
}
|
||||
var candidate redis.UniversalClient
|
||||
if config.UseCluster {
|
||||
addresses := config.ClusterAddrs
|
||||
@@ -387,18 +411,14 @@ func openRedis(config *config.Redis, enabled bool, appLogger ...*slog.Logger) re
|
||||
pingCtx, cancel := context.WithTimeout(context.Background(), 800*time.Millisecond)
|
||||
defer cancel()
|
||||
if err := candidate.Ping(pingCtx).Err(); err != nil {
|
||||
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)
|
||||
appLogger.Warn("redis unavailable, using in-memory cache", "mod", "redis", "error", err)
|
||||
_ = candidate.Close()
|
||||
return nil
|
||||
}
|
||||
return candidate
|
||||
}
|
||||
|
||||
func openRedisList(configs []*config.Redis, enabled bool, appLogger ...*slog.Logger) map[string]redis.UniversalClient {
|
||||
func openRedisList(configs []*config.Redis, enabled bool, appLogger *slog.Logger) map[string]redis.UniversalClient {
|
||||
if !enabled || len(configs) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -407,7 +427,7 @@ func openRedisList(configs []*config.Redis, enabled bool, appLogger ...*slog.Log
|
||||
if item == nil || item.Name == "" {
|
||||
continue
|
||||
}
|
||||
if client := openRedis(item, true, appLogger...); client != nil {
|
||||
if client := openRedis(item, true, appLogger); client != nil {
|
||||
clients[item.Name] = client
|
||||
}
|
||||
}
|
||||
@@ -420,11 +440,13 @@ func openRedisList(configs []*config.Redis, enabled bool, appLogger ...*slog.Log
|
||||
func closeRedisList(clients map[string]redis.UniversalClient) {
|
||||
for _, client := range clients {
|
||||
if client != nil {
|
||||
_ = client.Close()
|
||||
closeRedisClient(client)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// replaceRedisList mirrors replaceDatabaseList: superseded clients stay open for
|
||||
// the retire grace period so in-flight commands are not aborted.
|
||||
func (d *Data) replaceRedisList(clients map[string]redis.UniversalClient) {
|
||||
if d == nil {
|
||||
return
|
||||
@@ -433,7 +455,12 @@ func (d *Data) replaceRedisList(clients map[string]redis.UniversalClient) {
|
||||
old := d.redisList
|
||||
d.redisList = clients
|
||||
d.redisListMu.Unlock()
|
||||
closeRedisList(old)
|
||||
for name, client := range old {
|
||||
if client == nil || client == clients[name] {
|
||||
continue
|
||||
}
|
||||
d.retiredRedisList.retire(client, closeRedisClient)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Data) activateDatabase(db *gorm.DB, config *config.Database) {
|
||||
|
||||
@@ -20,7 +20,7 @@ func (dataScopeRecord) TableName() string { return "business_scope_records" }
|
||||
|
||||
func newDataScopeTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared")
|
||||
db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+12
-16
@@ -118,7 +118,7 @@ func databaseDSN(c *config.Database, name string) (string, error) {
|
||||
return "", fmt.Errorf("unsupported database driver %q", c.Driver)
|
||||
}
|
||||
|
||||
func gormConfig(config *config.Database, appLogger ...*slog.Logger) *gorm.Config {
|
||||
func gormConfig(config *config.Database, appLogger *slog.Logger) *gorm.Config {
|
||||
level := logger.Info
|
||||
switch strings.ToLower(config.LogMode) {
|
||||
case "silent":
|
||||
@@ -128,19 +128,15 @@ func gormConfig(config *config.Database, appLogger ...*slog.Logger) *gorm.Config
|
||||
case "warn":
|
||||
level = logger.Warn
|
||||
}
|
||||
var log *slog.Logger
|
||||
if len(appLogger) > 0 {
|
||||
log = appLogger[0]
|
||||
}
|
||||
return &gorm.Config{Logger: gormkit.NewLogger(log, level), NamingStrategy: schema.NamingStrategy{TablePrefix: config.Prefix, SingularTable: config.Singular}}
|
||||
return &gorm.Config{Logger: gormkit.NewLogger(appLogger, level), NamingStrategy: schema.NamingStrategy{TablePrefix: config.Prefix, SingularTable: config.Singular}}
|
||||
}
|
||||
|
||||
func openWithDriver(driver, dsn string, appLogger ...*slog.Logger) (*gorm.DB, error) {
|
||||
return openWithDriverConfig(driver, dsn, &config.Database{Driver: driver}, appLogger...)
|
||||
func openWithDriver(driver, dsn string, appLogger *slog.Logger) (*gorm.DB, error) {
|
||||
return openWithDriverConfig(driver, dsn, &config.Database{Driver: driver}, appLogger)
|
||||
}
|
||||
|
||||
func openWithDriverConfig(driver, dsn string, config *config.Database, appLogger ...*slog.Logger) (*gorm.DB, error) {
|
||||
gormConfig := gormConfig(config, appLogger...)
|
||||
func openWithDriverConfig(driver, dsn string, config *config.Database, appLogger *slog.Logger) (*gorm.DB, error) {
|
||||
gormConfig := gormConfig(config, appLogger)
|
||||
var db *gorm.DB
|
||||
var err error
|
||||
switch normalizedDriver(driver) {
|
||||
@@ -179,7 +175,7 @@ func openWithDriverConfig(driver, dsn string, config *config.Database, appLogger
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func openDatabase(c *config.Database, create bool, template string, appLogger ...*slog.Logger) (*gorm.DB, error) {
|
||||
func openDatabase(c *config.Database, create bool, template string, appLogger *slog.Logger) (*gorm.DB, error) {
|
||||
driver := normalizedDriver(c.Driver)
|
||||
if driver == "" {
|
||||
return nil, fmt.Errorf("unsupported database driver %q", c.Driver)
|
||||
@@ -192,7 +188,7 @@ func openDatabase(c *config.Database, create bool, template string, appLogger ..
|
||||
if err = os.MkdirAll(filepath.Dir(dsn), 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return openWithDriverConfig(driver, dsn, c, appLogger...)
|
||||
return openWithDriverConfig(driver, dsn, c, appLogger)
|
||||
}
|
||||
if create && driver != "oracle" {
|
||||
if !databaseNamePattern.MatchString(c.Name) {
|
||||
@@ -209,7 +205,7 @@ func openDatabase(c *config.Database, create bool, template string, appLogger ..
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
adminDB, err := openWithDriverConfig(driver, dsn, c, appLogger...)
|
||||
adminDB, err := openWithDriverConfig(driver, dsn, c, appLogger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connect database server: %w", err)
|
||||
}
|
||||
@@ -245,9 +241,9 @@ func openDatabase(c *config.Database, create bool, template string, appLogger ..
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return openWithDriverConfig(driver, dsn, c, appLogger...)
|
||||
return openWithDriverConfig(driver, dsn, c, appLogger)
|
||||
}
|
||||
|
||||
func openFallbackDatabase(appLogger ...*slog.Logger) (*gorm.DB, error) {
|
||||
return openWithDriver("sqlite", "file:kra-bootstrap?mode=memory&cache=shared", appLogger...)
|
||||
func openFallbackDatabase(appLogger *slog.Logger) (*gorm.DB, error) {
|
||||
return openWithDriver("sqlite", "file:kra-bootstrap?mode=memory&cache=shared", appLogger)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
|
||||
func openIntegrationConfigTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared")
|
||||
db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -34,7 +34,7 @@ func openIntegrationConfigTestDB(t *testing.T) *gorm.DB {
|
||||
}
|
||||
|
||||
func TestMigrateAllCreatesIntegrationConfigTable(t *testing.T) {
|
||||
db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared")
|
||||
db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ func testCatalog() platformmodule.Catalog {
|
||||
}
|
||||
|
||||
func TestMigrateAllRunsModuleSchemasWithoutBootstrapSeed(t *testing.T) {
|
||||
db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared")
|
||||
db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -4,60 +4,63 @@ import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// reloadableDB makes the pointer swap atomic. Replaced pools are retained
|
||||
// until application shutdown so in-flight GORM operations remain valid.
|
||||
type reloadableDB struct {
|
||||
current atomic.Pointer[gorm.DB]
|
||||
mu sync.Mutex
|
||||
retired []*gorm.DB
|
||||
// retireGrace bounds how long a client replaced by a hot reload stays open.
|
||||
// In-flight operations still hold the old handle, so it cannot be closed at
|
||||
// swap time; keeping it until shutdown would leak one full pool per reload.
|
||||
const retireGrace = 5 * time.Minute
|
||||
|
||||
// retiredSet holds clients replaced by a hot reload and closes each of them
|
||||
// once the grace period expires. Whatever is still pending at shutdown is
|
||||
// drained by the owner's close.
|
||||
type retiredSet[T comparable] struct {
|
||||
mu sync.Mutex
|
||||
items []T
|
||||
}
|
||||
|
||||
type reloadableMongo struct {
|
||||
mu sync.RWMutex
|
||||
current *mongo.Client
|
||||
retired []*mongo.Client
|
||||
func (s *retiredSet[T]) retire(item T, closeItem func(T)) {
|
||||
s.mu.Lock()
|
||||
s.items = append(s.items, item)
|
||||
s.mu.Unlock()
|
||||
time.AfterFunc(retireGrace, func() {
|
||||
if s.take(item) {
|
||||
closeItem(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func newReloadableMongo(client *mongo.Client) *reloadableMongo {
|
||||
return &reloadableMongo{current: client}
|
||||
}
|
||||
func (r *reloadableMongo) replace(client *mongo.Client) {
|
||||
r.mu.Lock()
|
||||
old := r.current
|
||||
r.current = client
|
||||
if old != nil && old != client {
|
||||
r.retired = append(r.retired, old)
|
||||
}
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
func (r *reloadableMongo) load() *mongo.Client {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
r.mu.RLock()
|
||||
client := r.current
|
||||
r.mu.RUnlock()
|
||||
return client
|
||||
}
|
||||
|
||||
func (r *reloadableMongo) close() {
|
||||
r.mu.Lock()
|
||||
all := append([]*mongo.Client{r.current}, r.retired...)
|
||||
r.current = nil
|
||||
r.retired = nil
|
||||
r.mu.Unlock()
|
||||
for _, client := range all {
|
||||
if client != nil {
|
||||
_ = client.Disconnect(context.Background())
|
||||
// take removes item and reports whether this caller now owns closing it.
|
||||
func (s *retiredSet[T]) take(item T) bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for index, existing := range s.items {
|
||||
if existing == item {
|
||||
s.items = append(s.items[:index], s.items[index+1:]...)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *retiredSet[T]) drain() []T {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
items := s.items
|
||||
s.items = nil
|
||||
return items
|
||||
}
|
||||
|
||||
// reloadableDB makes the pointer swap atomic. Replaced pools stay open for
|
||||
// retireGrace so in-flight GORM operations remain valid.
|
||||
type reloadableDB struct {
|
||||
current atomic.Pointer[gorm.DB]
|
||||
retired retiredSet[*gorm.DB]
|
||||
}
|
||||
|
||||
func newReloadableDB(db *gorm.DB, enqueue dataScopeAuditEnqueue) *reloadableDB {
|
||||
@@ -77,18 +80,12 @@ func (r *reloadableDB) replace(db *gorm.DB, enqueue dataScopeAuditEnqueue) {
|
||||
registerDataScopeCallbacks(db, enqueue)
|
||||
old := r.current.Swap(db)
|
||||
if old != nil && old != db {
|
||||
r.mu.Lock()
|
||||
r.retired = append(r.retired, old)
|
||||
r.mu.Unlock()
|
||||
r.retired.retire(old, closeGormDB)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reloadableDB) close() {
|
||||
current := r.current.Load()
|
||||
r.mu.Lock()
|
||||
all := append([]*gorm.DB{current}, r.retired...)
|
||||
r.retired = nil
|
||||
r.mu.Unlock()
|
||||
all := append([]*gorm.DB{r.current.Load()}, r.retired.drain()...)
|
||||
seen := map[*gorm.DB]struct{}{}
|
||||
for _, db := range all {
|
||||
if db == nil {
|
||||
@@ -98,16 +95,65 @@ func (r *reloadableDB) close() {
|
||||
continue
|
||||
}
|
||||
seen[db] = struct{}{}
|
||||
if sqlDB, err := db.DB(); err == nil {
|
||||
_ = sqlDB.Close()
|
||||
closeGormDB(db)
|
||||
}
|
||||
}
|
||||
|
||||
func closeGormDB(db *gorm.DB) {
|
||||
if sqlDB, err := db.DB(); err == nil {
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
}
|
||||
|
||||
type reloadableMongo struct {
|
||||
mu sync.RWMutex
|
||||
current *mongo.Client
|
||||
retired retiredSet[*mongo.Client]
|
||||
}
|
||||
|
||||
func newReloadableMongo(client *mongo.Client) *reloadableMongo {
|
||||
return &reloadableMongo{current: client}
|
||||
}
|
||||
|
||||
func (r *reloadableMongo) replace(client *mongo.Client) {
|
||||
r.mu.Lock()
|
||||
old := r.current
|
||||
r.current = client
|
||||
r.mu.Unlock()
|
||||
if old != nil && old != client {
|
||||
r.retired.retire(old, closeMongoClient)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reloadableMongo) load() *mongo.Client {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return r.current
|
||||
}
|
||||
|
||||
func (r *reloadableMongo) close() {
|
||||
r.mu.Lock()
|
||||
current := r.current
|
||||
r.current = nil
|
||||
r.mu.Unlock()
|
||||
for _, client := range append([]*mongo.Client{current}, r.retired.drain()...) {
|
||||
if client != nil {
|
||||
closeMongoClient(client)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func closeMongoClient(client *mongo.Client) {
|
||||
_ = client.Disconnect(context.Background())
|
||||
}
|
||||
|
||||
type reloadableRedis struct {
|
||||
mu sync.RWMutex
|
||||
current redis.UniversalClient
|
||||
retired []redis.UniversalClient
|
||||
retired retiredSet[redis.UniversalClient]
|
||||
}
|
||||
|
||||
func newReloadableRedis(client redis.UniversalClient) *reloadableRedis {
|
||||
@@ -124,22 +170,24 @@ func (r *reloadableRedis) replace(client redis.UniversalClient) {
|
||||
r.mu.Lock()
|
||||
old := r.current
|
||||
r.current = client
|
||||
if old != nil && old != client {
|
||||
r.retired = append(r.retired, old)
|
||||
}
|
||||
r.mu.Unlock()
|
||||
if old != nil && old != client {
|
||||
r.retired.retire(old, closeRedisClient)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reloadableRedis) close() {
|
||||
r.mu.Lock()
|
||||
all := append([]redis.UniversalClient{r.current}, r.retired...)
|
||||
current := r.current
|
||||
r.current = nil
|
||||
r.retired = nil
|
||||
r.mu.Unlock()
|
||||
for _, client := range all {
|
||||
if client == nil {
|
||||
continue
|
||||
for _, client := range append([]redis.UniversalClient{current}, r.retired.drain()...) {
|
||||
if client != nil {
|
||||
closeRedisClient(client)
|
||||
}
|
||||
_ = client.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func closeRedisClient(client redis.UniversalClient) {
|
||||
_ = client.Close()
|
||||
}
|
||||
|
||||
@@ -208,5 +208,37 @@ func defaultMenus() []menuPO {
|
||||
value.KeepAlive = true
|
||||
return value
|
||||
}
|
||||
return []menuPO{{Path: "dashboard", Name: "dashboard", Component: "view/dashboard/index.vue", Title: "仪表盘", Icon: "odometer", Sort: 1}, root("permission", "permission", "权限管理", "perm-kra", 2), root("org", "org", "组织管理", "share", 3), root("systemConfig", "systemConfig", "系统设置", "config-kra", 4), root("monitor", "monitor", "运维监控", "monitor-kra", 5), root("media", "media", "媒体管理", "folder-opened", 6), root("extensions", "extensions", "扩展功能", "cherry", 10), {Path: "person", Name: "person", Component: "view/person/person.vue", Title: "个人信息", Icon: "postcard", Hidden: true, Sort: 13}, child("permission", "authority", "authority", "view/superAdmin/authority/authority.vue", "角色管理", "role-kra", 1), cachedChild("permission", "menu", "menu", "view/superAdmin/menu/menu.vue", "菜单管理", "tickets", 2), cachedChild("permission", "api", "api", "view/superAdmin/api/api.vue", "api管理", "api-kra", 3), child("permission", "apiToken", "apiToken", "view/systemTools/apiToken/index.vue", "API Token", "key", 4), child("org", "user", "user", "view/superAdmin/user/user.vue", "用户管理", "user", 1), child("org", "department", "department", "view/superAdmin/department/department.vue", "部门管理", "office-building", 2), child("org", "position", "position", "view/superAdmin/position/position.vue", "岗位管理", "postcard", 3), child("systemConfig", "system", "system", "view/systemTools/system/system.vue", "配置文件", "config-file-kra", 1), child("systemConfig", "dictionary", "dictionary", "view/superAdmin/dictionary/sysDictionary.vue", "字典管理", "notebook", 2), child("systemConfig", "sysParams", "sysParams", "view/superAdmin/params/sysParams.vue", "参数管理", "set-up", 3), child("systemConfig", "security", "security", "view/system/security/index.vue", "安全配置", "security-kra", 4), child("monitor", "operation", "operation", "view/superAdmin/operation/sysOperationRecord.vue", "操作历史", "document", 1), child("monitor", "loginLog", "loginLog", "view/systemTools/loginLog/index.vue", "登录日志", "clock", 2), child("monitor", "sysError", "sysError", "view/systemTools/sysError/sysError.vue", "错误日志", "error-kra", 3), child("monitor", "sysVersion", "sysVersion", "view/systemTools/version/version.vue", "版本管理", "version-kra", 4), child("monitor", "state", "state", "view/system/state.vue", "服务器状态", "server", 5), child("monitor", "dataAccessLog", "dataAccessLog", "view/superAdmin/dataAccessLog/dataAccessLog.vue", "数据权限审计", "warning", 6), child("monitor", "timedTask", "timedTask", "view/systemTools/timedTask/index.vue", "定时任务", "timer", 7), child("monitor", "logViewer", "logViewer", "view/systemTools/logViewer/index.vue", "文件日志", "document", 8), child("media", "upload", "upload", "view/media/upload.vue", "媒体库(上传下载)", "upload", 1), child("media", "chunkUpload", "chunkUpload", "view/media/chunkUpload.vue", "大文件上传", "folder-add", 2), child("extensions", "email", "email", "modules/email/view/index.vue", "邮件发送", "message", 4), child("extensions", "anInfo", "anInfo", "modules/announcement/view/info.vue", "公告管理", "bell", 5)}
|
||||
return []menuPO{
|
||||
{Path: "dashboard", Name: "dashboard", Component: "view/dashboard/index.vue", Title: "仪表盘", Icon: "odometer", Sort: 1},
|
||||
root("permission", "permission", "权限管理", "perm-kra", 2),
|
||||
root("org", "org", "组织管理", "share", 3),
|
||||
root("systemConfig", "systemConfig", "系统设置", "config-kra", 4),
|
||||
root("monitor", "monitor", "运维监控", "monitor-kra", 5),
|
||||
root("media", "media", "媒体管理", "folder-opened", 6),
|
||||
root("extensions", "extensions", "扩展功能", "cherry", 10),
|
||||
{Path: "person", Name: "person", Component: "view/person/person.vue", Title: "个人信息", Icon: "postcard", Hidden: true, Sort: 13},
|
||||
child("permission", "authority", "authority", "view/superAdmin/authority/authority.vue", "角色管理", "role-kra", 1),
|
||||
cachedChild("permission", "menu", "menu", "view/superAdmin/menu/menu.vue", "菜单管理", "tickets", 2),
|
||||
cachedChild("permission", "api", "api", "view/superAdmin/api/api.vue", "api管理", "api-kra", 3),
|
||||
child("permission", "apiToken", "apiToken", "view/systemTools/apiToken/index.vue", "API Token", "key", 4),
|
||||
child("org", "user", "user", "view/superAdmin/user/user.vue", "用户管理", "user", 1),
|
||||
child("org", "department", "department", "view/superAdmin/department/department.vue", "部门管理", "office-building", 2),
|
||||
child("org", "position", "position", "view/superAdmin/position/position.vue", "岗位管理", "postcard", 3),
|
||||
child("systemConfig", "system", "system", "view/systemTools/system/system.vue", "配置文件", "config-file-kra", 1),
|
||||
child("systemConfig", "dictionary", "dictionary", "view/superAdmin/dictionary/sysDictionary.vue", "字典管理", "notebook", 2),
|
||||
child("systemConfig", "sysParams", "sysParams", "view/superAdmin/params/sysParams.vue", "参数管理", "set-up", 3),
|
||||
child("systemConfig", "security", "security", "view/system/security/index.vue", "安全配置", "security-kra", 4),
|
||||
child("monitor", "operation", "operation", "view/superAdmin/operation/sysOperationRecord.vue", "操作历史", "document", 1),
|
||||
child("monitor", "loginLog", "loginLog", "view/systemTools/loginLog/index.vue", "登录日志", "clock", 2),
|
||||
child("monitor", "sysError", "sysError", "view/systemTools/sysError/sysError.vue", "错误日志", "error-kra", 3),
|
||||
child("monitor", "sysVersion", "sysVersion", "view/systemTools/version/version.vue", "版本管理", "version-kra", 4),
|
||||
child("monitor", "state", "state", "view/system/state.vue", "服务器状态", "server", 5),
|
||||
child("monitor", "dataAccessLog", "dataAccessLog", "view/superAdmin/dataAccessLog/dataAccessLog.vue", "数据权限审计", "warning", 6),
|
||||
child("monitor", "timedTask", "timedTask", "view/systemTools/timedTask/index.vue", "定时任务", "timer", 7),
|
||||
child("monitor", "logViewer", "logViewer", "view/systemTools/logViewer/index.vue", "文件日志", "document", 8),
|
||||
child("media", "upload", "upload", "view/media/upload.vue", "媒体库(上传下载)", "upload", 1),
|
||||
child("media", "chunkUpload", "chunkUpload", "view/media/chunkUpload.vue", "大文件上传", "folder-add", 2),
|
||||
child("extensions", "email", "email", "modules/email/view/index.vue", "邮件发送", "message", 4),
|
||||
child("extensions", "anInfo", "anInfo", "modules/announcement/view/info.vue", "公告管理", "bell", 5),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user