优化结构

This commit is contained in:
Yvan
2026-08-21 21:37:30 +08:00
parent a2ce3ae218
commit e4ba0dced9
41 changed files with 3646 additions and 364 deletions
+145
View File
@@ -0,0 +1,145 @@
// Package runtimeconfig keeps the active database-backed integration settings
// and notifies long-lived provider clients when they change.
package runtimeconfig
import (
"encoding/json"
"strings"
"sync"
)
type Config struct {
Kind string
Provider string
Enabled bool
Values json.RawMessage
}
type listener struct {
kind string
provider string
callback func(Config)
}
type Store struct {
mu sync.RWMutex
values map[string]Config
listeners map[uint64]listener
nextID uint64
}
func NewStore() *Store {
return &Store{values: make(map[string]Config), listeners: make(map[uint64]listener)}
}
func configKey(kind, provider string) string {
return strings.ToLower(strings.TrimSpace(kind)) + "/" + strings.ToLower(strings.TrimSpace(provider))
}
func cloneConfig(config Config) Config {
config.Values = append(json.RawMessage(nil), config.Values...)
return config
}
func (s *Store) Get(kind, provider string) (Config, bool) {
if s == nil {
return Config{}, false
}
s.mu.RLock()
config, ok := s.values[configKey(kind, provider)]
s.mu.RUnlock()
return cloneConfig(config), ok
}
func (s *Store) Set(config Config) {
if s == nil {
return
}
config.Kind = strings.ToLower(strings.TrimSpace(config.Kind))
config.Provider = strings.ToLower(strings.TrimSpace(config.Provider))
config = cloneConfig(config)
key := configKey(config.Kind, config.Provider)
s.mu.Lock()
s.values[key] = config
callbacks := s.matchingListenersLocked(config.Kind, config.Provider)
s.mu.Unlock()
for _, callback := range callbacks {
callback(cloneConfig(config))
}
}
func (s *Store) Delete(kind, provider string) {
if s == nil {
return
}
kind = strings.ToLower(strings.TrimSpace(kind))
provider = strings.ToLower(strings.TrimSpace(provider))
s.mu.Lock()
delete(s.values, configKey(kind, provider))
callbacks := s.matchingListenersLocked(kind, provider)
s.mu.Unlock()
config := Config{Kind: kind, Provider: provider}
for _, callback := range callbacks {
callback(config)
}
}
func (s *Store) Replace(configs []Config) {
if s == nil {
return
}
next := make(map[string]Config, len(configs))
for _, config := range configs {
config.Kind = strings.ToLower(strings.TrimSpace(config.Kind))
config.Provider = strings.ToLower(strings.TrimSpace(config.Provider))
config = cloneConfig(config)
next[configKey(config.Kind, config.Provider)] = config
}
s.mu.Lock()
previous := s.values
s.values = next
listeners := make([]listener, 0, len(s.listeners))
for _, item := range s.listeners {
listeners = append(listeners, item)
}
s.mu.Unlock()
changed := make(map[string]Config, len(previous)+len(next))
for key, config := range previous {
changed[key] = Config{Kind: config.Kind, Provider: config.Provider}
}
for key, config := range next {
changed[key] = config
}
for _, item := range listeners {
if config, ok := changed[configKey(item.kind, item.provider)]; ok {
item.callback(cloneConfig(config))
}
}
}
func (s *Store) Subscribe(kind, provider string, callback func(Config)) func() {
if s == nil || callback == nil {
return func() {}
}
s.mu.Lock()
s.nextID++
id := s.nextID
s.listeners[id] = listener{kind: strings.ToLower(strings.TrimSpace(kind)), provider: strings.ToLower(strings.TrimSpace(provider)), callback: callback}
s.mu.Unlock()
return func() {
s.mu.Lock()
delete(s.listeners, id)
s.mu.Unlock()
}
}
func (s *Store) matchingListenersLocked(kind, provider string) []func(Config) {
callbacks := make([]func(Config), 0)
for _, item := range s.listeners {
if item.kind == kind && item.provider == provider {
callbacks = append(callbacks, item.callback)
}
}
return callbacks
}
@@ -0,0 +1,30 @@
package runtimeconfig
import (
"encoding/json"
"testing"
)
func TestStoreSetDeleteAndSubscribe(t *testing.T) {
store := NewStore()
updates := make(chan Config, 2)
stop := store.Subscribe("mq", "rabbitmq", func(config Config) { updates <- config })
defer stop()
store.Set(Config{Kind: "MQ", Provider: "RabbitMQ", Enabled: true, Values: json.RawMessage(`{"host":"localhost"}`)})
loaded, ok := store.Get("mq", "rabbitmq")
if !ok || !loaded.Enabled || string(loaded.Values) != `{"host":"localhost"}` {
t.Fatalf("loaded config = %#v, ok=%v", loaded, ok)
}
if update := <-updates; !update.Enabled {
t.Fatalf("set update = %#v", update)
}
store.Delete("mq", "rabbitmq")
if _, ok = store.Get("mq", "rabbitmq"); ok {
t.Fatal("deleted config remained in store")
}
if update := <-updates; update.Enabled {
t.Fatalf("delete update = %#v", update)
}
}