优化结构
This commit is contained in:
@@ -194,8 +194,6 @@ func (d *Data) persistConfigValuesLocked(dataConfig *conf.Data, adminConfig *con
|
||||
}
|
||||
deleteYAMLMapping(&document, "admin", "storage")
|
||||
deleteYAMLMapping(&document, "admin", "email")
|
||||
deleteYAMLMapping(&document, "admin", "websocket")
|
||||
deleteYAMLMapping(&document, "admin", "mq")
|
||||
if adminConfig.System != nil {
|
||||
if err = setServerHTTPPort(&document, adminConfig.System.Addr); err != nil {
|
||||
return err
|
||||
@@ -255,8 +253,6 @@ func (d *Data) persistDatabaseConfig(database *conf.Data_Database, signingKey st
|
||||
}
|
||||
deleteYAMLMapping(&document, "admin", "storage")
|
||||
deleteYAMLMapping(&document, "admin", "email")
|
||||
deleteYAMLMapping(&document, "admin", "websocket")
|
||||
deleteYAMLMapping(&document, "admin", "mq")
|
||||
return writeConfigDocument(configPath, &document)
|
||||
}
|
||||
|
||||
@@ -275,13 +271,11 @@ func (d *Data) removeIntegrationConfigFromFile() error {
|
||||
if err = yaml.Unmarshal(raw, &document); err != nil {
|
||||
return err
|
||||
}
|
||||
if yamlMappingValue(&document, "admin", "storage") == nil && yamlMappingValue(&document, "admin", "email") == nil && yamlMappingValue(&document, "admin", "websocket") == nil && yamlMappingValue(&document, "admin", "mq") == nil {
|
||||
if yamlMappingValue(&document, "admin", "storage") == nil && yamlMappingValue(&document, "admin", "email") == nil {
|
||||
return nil
|
||||
}
|
||||
deleteYAMLMapping(&document, "admin", "storage")
|
||||
deleteYAMLMapping(&document, "admin", "email")
|
||||
deleteYAMLMapping(&document, "admin", "websocket")
|
||||
deleteYAMLMapping(&document, "admin", "mq")
|
||||
return writeConfigDocument(configPath, &document)
|
||||
}
|
||||
|
||||
|
||||
+12
-4
@@ -14,13 +14,14 @@ import (
|
||||
"kra/internal/conf"
|
||||
datapayment "kra/internal/data/payment"
|
||||
datasystem "kra/internal/data/repository"
|
||||
"kra/internal/integration/runtimeconfig"
|
||||
"kra/internal/integration/storage"
|
||||
"kra/internal/integrationruntime"
|
||||
"kra/pkg/module"
|
||||
)
|
||||
|
||||
var ProviderSet = wire.NewSet(
|
||||
NewData,
|
||||
NewIntegrationRuntime,
|
||||
wire.Bind(new(datasystem.Provider), new(*Data)),
|
||||
wire.Bind(new(datasystem.DatabaseProvider), new(*Data)),
|
||||
wire.Bind(new(datapayment.Provider), new(*Data)),
|
||||
@@ -34,6 +35,13 @@ var ProviderSet = wire.NewSet(
|
||||
datasystem.NewIntegrationConfigRepo,
|
||||
)
|
||||
|
||||
func NewIntegrationRuntime(data *Data) *runtimeconfig.Store {
|
||||
if data == nil {
|
||||
return runtimeconfig.NewStore()
|
||||
}
|
||||
return data.IntegrationRuntime()
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
initMu sync.Mutex
|
||||
configMu sync.Mutex
|
||||
@@ -42,7 +50,7 @@ type Data struct {
|
||||
redis *reloadableRedis
|
||||
mongo *reloadableMongo
|
||||
runtime *conf.Runtime
|
||||
integrations *integrationruntime.Store
|
||||
integrations *runtimeconfig.Store
|
||||
storage *storage.Reloadable
|
||||
dbListMu sync.RWMutex
|
||||
dbList map[string]*gorm.DB
|
||||
@@ -77,7 +85,7 @@ func (d *Data) Runtime() *conf.Runtime {
|
||||
|
||||
// IntegrationRuntime exposes database-backed integration configuration to
|
||||
// long-lived adapters without making config.yaml part of their lifecycle.
|
||||
func (d *Data) IntegrationRuntime() *integrationruntime.Store {
|
||||
func (d *Data) IntegrationRuntime() *runtimeconfig.Store {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -167,7 +175,7 @@ func NewData(runtime *conf.Runtime, appLogger *slog.Logger, storageManager *stor
|
||||
// and /init/initdb remain available.
|
||||
c.Database = &conf.Data_Database{}
|
||||
}
|
||||
d := &Data{runtime: runtime, integrations: integrationruntime.NewStore(), appLogger: appLogger, storage: storageManager, catalog: catalog}
|
||||
d := &Data{runtime: runtime, integrations: runtimeconfig.NewStore(), appLogger: appLogger, storage: storageManager, catalog: catalog}
|
||||
usingFallback := !databaseConnectionConfigured(c.Database)
|
||||
var db *gorm.DB
|
||||
var err error
|
||||
|
||||
@@ -123,25 +123,6 @@ func TestStorageIntegrationConfigRoundTrip(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMQIntegrationConfigRoundTrip(t *testing.T) {
|
||||
db := openIntegrationConfigTestDB(t)
|
||||
legacy := &conf.AdminBackend_MQ{Enabled: true, Broker: "mqtt://emqx.example.com:1883", ClientId: "system", Username: "app", Password: "secret", KeepAlive: 45, CleanSession: true, ConnectTimeout: 12}
|
||||
if err := saveMQIntegrationConfig(db, legacy); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loaded, found, err := loadMQIntegrationConfig(db)
|
||||
if err != nil || !found {
|
||||
t.Fatalf("found=%v err=%v", found, err)
|
||||
}
|
||||
if loaded.Broker != legacy.Broker || loaded.Password != legacy.Password || loaded.KeepAlive != 45 {
|
||||
t.Fatalf("loaded mq = %#v", loaded)
|
||||
}
|
||||
loaded, err = resolveMQIntegrationConfig(db, &conf.AdminBackend_MQ{Broker: "must-not-replace"})
|
||||
if err != nil || loaded.Broker != legacy.Broker {
|
||||
t.Fatalf("database mq was replaced: %#v err=%v", loaded, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveStorageIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
|
||||
db := openIntegrationConfigTestDB(t)
|
||||
legacy := &conf.AdminBackend_Storage{
|
||||
@@ -205,7 +186,7 @@ func TestResolveEmailIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
|
||||
|
||||
func TestPersistConfigValuesRemovesStorageFromYAML(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "config.yaml")
|
||||
input := []byte("data: {}\nadmin:\n router_prefix: /old\n storage:\n type: qiniu\n qiniu:\n secret_key: legacy-secret\n email:\n host: smtp.legacy.example.com\n secret: legacy-email-secret\n mq:\n enabled: true\n broker: tcp://localhost:1883\n password: legacy-mq-secret\n extension_key: retained\n")
|
||||
input := []byte("data: {}\nadmin:\n router_prefix: /old\n storage:\n type: qiniu\n qiniu:\n secret_key: legacy-secret\n email:\n host: smtp.legacy.example.com\n secret: legacy-email-secret\n extension_key: retained\n")
|
||||
if err := os.WriteFile(path, input, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -218,7 +199,6 @@ func TestPersistConfigValuesRemovesStorageFromYAML(t *testing.T) {
|
||||
Qiniu: &conf.AdminBackend_Qiniu{SecretKey: "database-only-secret"},
|
||||
},
|
||||
Email: &conf.AdminBackend_Email{Host: "smtp.database.example.com", Secret: "database-only-email-secret"},
|
||||
Mq: &conf.AdminBackend_MQ{Enabled: true, Broker: "tcp://emqx:1883", Password: "database-only-mq-secret"},
|
||||
}
|
||||
if err := d.persistConfigValues(&conf.Data{}, admin); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -242,9 +222,6 @@ func TestPersistConfigValuesRemovesStorageFromYAML(t *testing.T) {
|
||||
if _, exists := adminValue["email"]; exists {
|
||||
t.Fatalf("email remained in YAML: %s", raw)
|
||||
}
|
||||
if _, exists := adminValue["mq"]; exists {
|
||||
t.Fatalf("mq remained in YAML: %s", raw)
|
||||
}
|
||||
if adminValue["extension_key"] != "retained" {
|
||||
t.Fatalf("extension key was not retained: %#v", adminValue)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"kra/internal/biz"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func ensureCommunicationIntegrationConfigs(db *gorm.DB) error {
|
||||
defaults := []struct {
|
||||
kind string
|
||||
provider string
|
||||
}{
|
||||
{kind: biz.IntegrationKindMQ, provider: "emqx"},
|
||||
{kind: biz.IntegrationKindMQ, provider: "rabbitmq"},
|
||||
{kind: biz.IntegrationKindWebSocket, provider: "melody"},
|
||||
}
|
||||
for _, item := range defaults {
|
||||
var row integrationConfigPO
|
||||
err := db.Where("kind = ? AND provider = ?", item.kind, item.provider).First(&row).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
values, marshalErr := json.Marshal(biz.DefaultIntegrationConfig(item.kind, item.provider))
|
||||
if marshalErr != nil {
|
||||
return marshalErr
|
||||
}
|
||||
if err = db.Create(&integrationConfigPO{Kind: item.kind, Provider: item.provider, Enabled: false, Config: string(values)}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -3,12 +3,12 @@ package data
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"kra/internal/integrationruntime"
|
||||
"kra/internal/integration/runtimeconfig"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func readIntegrationRuntime(db *gorm.DB) ([]integrationruntime.Config, error) {
|
||||
func readIntegrationRuntime(db *gorm.DB) ([]runtimeconfig.Config, error) {
|
||||
if db == nil || !db.Migrator().HasTable(&integrationConfigPO{}) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -19,9 +19,9 @@ func readIntegrationRuntime(db *gorm.DB) ([]integrationruntime.Config, error) {
|
||||
Find(&rows).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
configs := make([]integrationruntime.Config, 0, len(rows))
|
||||
configs := make([]runtimeconfig.Config, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
configs = append(configs, integrationruntime.Config{Kind: row.Kind, Provider: row.Provider, Enabled: row.Enabled, Values: []byte(row.Config)})
|
||||
configs = append(configs, runtimeconfig.Config{Kind: row.Kind, Provider: row.Provider, Enabled: row.Enabled, Values: []byte(row.Config)})
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
@@ -10,12 +10,15 @@ import (
|
||||
)
|
||||
|
||||
func InfrastructureMigrations() []migration.Step {
|
||||
return []migration.Step{{
|
||||
ID: "202608200001_data_infrastructure",
|
||||
Migrate: func(db *gorm.DB) error {
|
||||
return migration.CreateMissingTables(db, &integrationConfigPO{})
|
||||
return []migration.Step{
|
||||
{
|
||||
ID: "202608200001_data_infrastructure",
|
||||
Migrate: func(db *gorm.DB) error {
|
||||
return migration.CreateMissingTables(db, &integrationConfigPO{})
|
||||
},
|
||||
},
|
||||
}}
|
||||
{ID: "202608210001_communication_integration_defaults", Migrate: ensureCommunicationIntegrationConfigs},
|
||||
}
|
||||
}
|
||||
|
||||
// migrateAll is the single data-layer migration entry point. Module-specific
|
||||
|
||||
@@ -26,8 +26,20 @@ func TestMigrateAllRunsModuleSchemasWithoutBootstrapSeed(t *testing.T) {
|
||||
if err = db.Table(migration.TableName).Count(&versions).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if versions != 4 {
|
||||
t.Fatalf("migration versions = %d, want 4", versions)
|
||||
if versions != 6 {
|
||||
t.Fatalf("migration versions = %d, want 6", versions)
|
||||
}
|
||||
var communicationRows []integrationConfigPO
|
||||
if err = db.Where("kind IN ?", []string{"mq", "websocket"}).Order("kind, provider").Find(&communicationRows).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(communicationRows) != 3 {
|
||||
t.Fatalf("communication integration rows = %d, want 3", len(communicationRows))
|
||||
}
|
||||
for _, row := range communicationRows {
|
||||
if row.Enabled || row.Config == "" {
|
||||
t.Fatalf("default communication integration = %#v", row)
|
||||
}
|
||||
}
|
||||
for _, table := range []string{"sys_users", "sys_base_menus", "sys_apis"} {
|
||||
var count int64
|
||||
|
||||
@@ -26,12 +26,16 @@ func AdminSurface() platformmodule.Surface {
|
||||
{Name: "paymentConfig", Path: "paymentConfig", ParentName: "extensions", Component: "view/systemTools/payment/config.vue", Title: "支付配置", Icon: "credit-card", Sort: 7},
|
||||
},
|
||||
APIs: []platformmodule.API{
|
||||
{Path: "/integration/configs/:kind", Method: "GET", Group: "集成配置", Description: "按类型获取集成配置"},
|
||||
{Path: "/integration/configs/:kind/:provider", Method: "GET", Group: "集成配置", Description: "获取指定集成配置"},
|
||||
{Path: "/integration/configs/:kind/:provider", Method: "PUT", Group: "集成配置", Description: "保存集成配置"},
|
||||
{Path: "/integration/configs/:kind/:provider", Method: "DELETE", Group: "集成配置", Description: "删除集成配置"},
|
||||
{Path: "/payment/orders", Method: "GET", Group: "支付", Description: "分页查询支付订单"},
|
||||
{Path: "/payment/order", Method: "POST", Group: "支付", Description: "查询支付订单"},
|
||||
{Path: "/payment/orders/:provider/:tradeNo", Method: "GET", Group: "支付", Description: "按路径查询支付订单"},
|
||||
{Path: "/payment/create", Method: "POST", Group: "支付", Description: "创建支付订单"},
|
||||
{Path: "/payment/query", Method: "POST", Group: "支付", Description: "同步支付订单状态"},
|
||||
{Path: "/payment/refund", Method: "POST", Group: "支付", Description: "申请支付订单退款"},
|
||||
{Path: "/payment/orders/:provider/:tradeNo/refund", Method: "POST", Group: "支付", Description: "按路径申请支付订单退款"},
|
||||
{Path: "/payment/fulfill", Method: "POST", Group: "支付", Description: "重试支付订单发货"},
|
||||
{Path: "/payment/orders/:provider/:tradeNo/fulfill", Method: "POST", Group: "支付", Description: "按路径重试支付订单发货"},
|
||||
{Path: "/payment/providers/:provider/test", Method: "POST", Group: "支付", Description: "测试支付渠道"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/integrationruntime"
|
||||
"kra/internal/integration/runtimeconfig"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -27,6 +27,10 @@ func (integrationConfigPO) TableName() string { return "sys_integration_configs"
|
||||
|
||||
type integrationConfigRepo struct{ data Provider }
|
||||
|
||||
type integrationRuntimeProvider interface {
|
||||
IntegrationRuntime() *runtimeconfig.Store
|
||||
}
|
||||
|
||||
func NewIntegrationConfigRepo(data Provider) biz.IntegrationConfigRepo {
|
||||
return &integrationConfigRepo{data: data}
|
||||
}
|
||||
@@ -93,18 +97,25 @@ func (r *integrationConfigRepo) DeleteIntegrationConfig(ctx context.Context, kin
|
||||
if err := r.data.DB().WithContext(ctx).Where("kind = ? AND provider = ?", kind, provider).Delete(&integrationConfigPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if runtime := r.data.IntegrationRuntime(); runtime != nil {
|
||||
if runtime := integrationRuntime(r.data); runtime != nil {
|
||||
runtime.Delete(kind, provider)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *integrationConfigRepo) publish(kind, provider string, enabled bool, values []byte) {
|
||||
if runtime := r.data.IntegrationRuntime(); runtime != nil {
|
||||
runtime.Set(integrationruntime.Config{Kind: kind, Provider: provider, Enabled: enabled, Values: values})
|
||||
if runtime := integrationRuntime(r.data); runtime != nil {
|
||||
runtime.Set(runtimeconfig.Config{Kind: kind, Provider: provider, Enabled: enabled, Values: values})
|
||||
}
|
||||
}
|
||||
|
||||
func integrationRuntime(provider Provider) *runtimeconfig.Store {
|
||||
if value, ok := provider.(integrationRuntimeProvider); ok {
|
||||
return value.IntegrationRuntime()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func integrationConfigFromPO(row integrationConfigPO) *biz.IntegrationConfig {
|
||||
values := integrationObject(json.RawMessage(row.Config))
|
||||
maskIntegrationSecrets(row.Kind, row.Provider, values)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/integration/runtimeconfig"
|
||||
)
|
||||
|
||||
type integrationRuntimeTestProvider struct {
|
||||
*Data
|
||||
store *runtimeconfig.Store
|
||||
}
|
||||
|
||||
func (p *integrationRuntimeTestProvider) IntegrationRuntime() *runtimeconfig.Store { return p.store }
|
||||
|
||||
func TestIntegrationConfigSavePublishesUnmaskedRuntimeValues(t *testing.T) {
|
||||
db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = db.AutoMigrate(&integrationConfigPO{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
provider := &integrationRuntimeTestProvider{Data: &Data{gormDB: newReloadableDB(db, nil)}, store: runtimeconfig.NewStore()}
|
||||
repo := &integrationConfigRepo{data: provider}
|
||||
|
||||
values := biz.DefaultIntegrationConfig(biz.IntegrationKindMQ, "rabbitmq")
|
||||
values["password"] = "runtime-secret"
|
||||
raw, _ := json.Marshal(values)
|
||||
if err = repo.SaveIntegrationConfig(context.Background(), &biz.IntegrationConfig{Kind: biz.IntegrationKindMQ, Provider: "rabbitmq", Enabled: true, Values: raw}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
values["password"] = "******"
|
||||
raw, _ = json.Marshal(values)
|
||||
if err = repo.SaveIntegrationConfig(context.Background(), &biz.IntegrationConfig{Kind: biz.IntegrationKindMQ, Provider: "rabbitmq", Enabled: true, Values: raw}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
active, ok := provider.store.Get(biz.IntegrationKindMQ, "rabbitmq")
|
||||
if !ok || !active.Enabled {
|
||||
t.Fatalf("runtime config = %#v, ok=%v", active, ok)
|
||||
}
|
||||
stored := map[string]any{}
|
||||
if err = json.Unmarshal(active.Values, &stored); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stored["password"] != "runtime-secret" {
|
||||
t.Fatalf("runtime password = %#v", stored["password"])
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package system
|
||||
|
||||
import (
|
||||
"kra/internal/conf"
|
||||
"kra/internal/integrationruntime"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -14,5 +13,4 @@ type Provider interface {
|
||||
Database(name string) (*gorm.DB, error)
|
||||
DatabaseReady() bool
|
||||
Runtime() *conf.Runtime
|
||||
IntegrationRuntime() *integrationruntime.Store
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user