优化结构
This commit is contained in:
@@ -26,6 +26,73 @@ type ConfigPO struct {
|
||||
|
||||
func (ConfigPO) TableName() string { return "sys_integration_configs" }
|
||||
|
||||
// ConfigRow is one integration configuration row without its PO: the parent
|
||||
// data package owns the storage and email upgrade paths, but the table shape
|
||||
// stays private here.
|
||||
type ConfigRow struct {
|
||||
Provider string
|
||||
Enabled bool
|
||||
Config string
|
||||
}
|
||||
|
||||
// HasConfigTable reports whether the integration configuration table exists,
|
||||
// which callers use to tell "not migrated yet" from "no rows".
|
||||
func HasConfigTable(db *gorm.DB) bool {
|
||||
return db != nil && cleanSession(db).Migrator().HasTable(&ConfigPO{})
|
||||
}
|
||||
|
||||
// ListConfigs returns every row of a kind in insertion order.
|
||||
func ListConfigs(db *gorm.DB, kind string) ([]ConfigRow, error) {
|
||||
var rows []ConfigPO
|
||||
if err := cleanSession(db).Where("kind = ?", kind).Order("id ASC").Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]ConfigRow, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
result = append(result, ConfigRow{Provider: row.Provider, Enabled: row.Enabled, Config: row.Config})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindConfig returns one row and whether it exists. A missing row is not an
|
||||
// error because every caller treats it as "fall back to the legacy value".
|
||||
func FindConfig(db *gorm.DB, kind, provider string) (ConfigRow, bool, error) {
|
||||
var row ConfigPO
|
||||
err := cleanSession(db).Where("kind = ? AND provider = ?", kind, provider).First(&row).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ConfigRow{}, false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return ConfigRow{}, false, err
|
||||
}
|
||||
return ConfigRow{Provider: row.Provider, Enabled: row.Enabled, Config: row.Config}, true, nil
|
||||
}
|
||||
|
||||
// UpsertConfigs writes every given row of a kind in one transaction, inserting
|
||||
// the ones that do not exist yet and updating the rest in place.
|
||||
func UpsertConfigs(db *gorm.DB, kind string, rows []ConfigRow) error {
|
||||
return cleanSession(db).Transaction(func(tx *gorm.DB) error {
|
||||
for _, item := range rows {
|
||||
var current ConfigPO
|
||||
err := tx.Where("kind = ? AND provider = ?", kind, item.Provider).First(¤t).Error
|
||||
switch {
|
||||
case errors.Is(err, gorm.ErrRecordNotFound):
|
||||
err = tx.Create(&ConfigPO{Kind: kind, Provider: item.Provider, Enabled: item.Enabled, Config: item.Config}).Error
|
||||
case err == nil:
|
||||
err = tx.Model(¤t).Updates(map[string]any{"enabled": item.Enabled, "config": item.Config}).Error
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// cleanSession drops conditions inherited from the caller's handle so a scoped
|
||||
// query cannot leak a WHERE clause into these statements.
|
||||
func cleanSession(db *gorm.DB) *gorm.DB { return db.Session(&gorm.Session{NewDB: true}) }
|
||||
|
||||
type integrationConfigRepo struct{ data Provider }
|
||||
|
||||
type paymentConfigReader struct{ data Provider }
|
||||
|
||||
@@ -29,7 +29,7 @@ func ensureCommunicationIntegrationConfigs(db *gorm.DB) error {
|
||||
for _, item := range defaults {
|
||||
var row ConfigPO
|
||||
err := db.Where("kind = ? AND provider = ?", item.kind, item.provider).First(&row).Error
|
||||
if gorm.ErrRecordNotFound == err {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
values, marshalErr := json.Marshal(integrationbiz.DefaultIntegrationConfig(item.kind, item.provider))
|
||||
if marshalErr != nil {
|
||||
return marshalErr
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"kra/internal/config"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
kindStorage = "storage"
|
||||
kindEmail = "email"
|
||||
providerSMTP = "smtp"
|
||||
)
|
||||
|
||||
var storageProviders = []string{
|
||||
"local", "qiniu", "aliyun-oss", "huawei-obs", "tencent-cos", "aws-s3", "cloudflare-r2", "minio",
|
||||
}
|
||||
|
||||
func normalizeStorageType(value string) string {
|
||||
value = strings.ToLower(strings.TrimSpace(value))
|
||||
if value == "" {
|
||||
return "local"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func storageProviderValue(storage *config.Storage, provider string) any {
|
||||
if storage == nil {
|
||||
storage = &config.Storage{}
|
||||
}
|
||||
switch provider {
|
||||
case "qiniu":
|
||||
if storage.Qiniu == nil {
|
||||
storage.Qiniu = &config.Qiniu{}
|
||||
}
|
||||
return storage.Qiniu
|
||||
case "aliyun-oss":
|
||||
return ensureObjectStore(&storage.AliyunOSS)
|
||||
case "huawei-obs":
|
||||
return ensureObjectStore(&storage.HuaweiOBS)
|
||||
case "tencent-cos":
|
||||
return ensureObjectStore(&storage.TencentCOS)
|
||||
case "aws-s3":
|
||||
return ensureObjectStore(&storage.AWSS3)
|
||||
case "cloudflare-r2":
|
||||
return ensureObjectStore(&storage.CloudflareR2)
|
||||
case "minio":
|
||||
return ensureObjectStore(&storage.Minio)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func ensureObjectStore(value **config.ObjectStore) *config.ObjectStore {
|
||||
if *value == nil {
|
||||
*value = &config.ObjectStore{}
|
||||
}
|
||||
return *value
|
||||
}
|
||||
|
||||
func marshalStorageProvider(storage *config.Storage, provider string) (string, error) {
|
||||
value := storageProviderValue(storage, provider)
|
||||
if value == nil {
|
||||
return "{}", nil
|
||||
}
|
||||
raw, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
func unmarshalStorageProvider(storage *config.Storage, provider, value string) error {
|
||||
if provider == "local" || strings.TrimSpace(value) == "" {
|
||||
return nil
|
||||
}
|
||||
target := storageProviderValue(storage, provider)
|
||||
if target == nil {
|
||||
return nil
|
||||
}
|
||||
if !json.Valid([]byte(value)) {
|
||||
return fmt.Errorf("invalid %s integration configuration", provider)
|
||||
}
|
||||
if err := json.Unmarshal([]byte(value), target); err != nil {
|
||||
return fmt.Errorf("decode %s integration configuration: %w", provider, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SaveStorageConfig(db *gorm.DB, storage *config.Storage) error {
|
||||
if storage == nil {
|
||||
storage = &config.Storage{}
|
||||
}
|
||||
active := normalizeStorageType(storage.Type)
|
||||
known := false
|
||||
for _, provider := range storageProviders {
|
||||
if provider == active {
|
||||
known = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !known {
|
||||
return fmt.Errorf("unsupported storage type %q", active)
|
||||
}
|
||||
|
||||
rows := make([]ConfigRow, 0, len(storageProviders))
|
||||
for _, provider := range storageProviders {
|
||||
value, err := marshalStorageProvider(storage, provider)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode %s integration configuration: %w", provider, err)
|
||||
}
|
||||
rows = append(rows, ConfigRow{Provider: provider, Enabled: provider == active, Config: value})
|
||||
}
|
||||
return UpsertConfigs(db, kindStorage, rows)
|
||||
}
|
||||
|
||||
func LoadStorageConfig(db *gorm.DB) (*config.Storage, bool, error) {
|
||||
rows, err := ListConfigs(db, kindStorage)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
storage := &config.Storage{Type: "local"}
|
||||
for _, row := range rows {
|
||||
if err = unmarshalStorageProvider(storage, row.Provider, row.Config); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if row.Enabled {
|
||||
storage.Type = row.Provider
|
||||
}
|
||||
}
|
||||
return storage, true, nil
|
||||
}
|
||||
|
||||
// ResolveStorageConfig imports a legacy YAML value only when the database has
|
||||
// no storage configuration yet. The database is authoritative afterwards.
|
||||
func ResolveStorageConfig(db *gorm.DB, legacy *config.Storage) (*config.Storage, error) {
|
||||
if !HasConfigTable(db) {
|
||||
if legacy == nil {
|
||||
return &config.Storage{Type: "local"}, nil
|
||||
}
|
||||
return config.CloneStorage(legacy), nil
|
||||
}
|
||||
storage, found, err := LoadStorageConfig(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if found {
|
||||
return storage, nil
|
||||
}
|
||||
if legacy == nil {
|
||||
legacy = &config.Storage{Type: "local"}
|
||||
}
|
||||
if err = SaveStorageConfig(db, legacy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
storage, _, err = LoadStorageConfig(db)
|
||||
return storage, err
|
||||
}
|
||||
|
||||
func defaultEmailConfig() *config.Email { return &config.Email{Port: 465, IsSSL: true} }
|
||||
|
||||
func SaveEmailConfig(db *gorm.DB, email *config.Email) error {
|
||||
if email == nil {
|
||||
email = defaultEmailConfig()
|
||||
}
|
||||
raw, err := json.Marshal(email)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode smtp integration configuration: %w", err)
|
||||
}
|
||||
enabled := email.Host != "" && email.From != "" && email.Secret != "" && email.Port > 0
|
||||
return UpsertConfigs(db, kindEmail, []ConfigRow{{Provider: providerSMTP, Enabled: enabled, Config: string(raw)}})
|
||||
}
|
||||
|
||||
func LoadEmailConfig(db *gorm.DB) (*config.Email, bool, error) {
|
||||
row, found, err := FindConfig(db, kindEmail, providerSMTP)
|
||||
if err != nil || !found {
|
||||
return nil, false, err
|
||||
}
|
||||
if !json.Valid([]byte(row.Config)) {
|
||||
return nil, false, errors.New("invalid smtp integration configuration")
|
||||
}
|
||||
email := defaultEmailConfig()
|
||||
if err = json.Unmarshal([]byte(row.Config), email); err != nil {
|
||||
return nil, false, fmt.Errorf("decode smtp integration configuration: %w", err)
|
||||
}
|
||||
return email, true, nil
|
||||
}
|
||||
|
||||
func ResolveEmailConfig(db *gorm.DB, legacy *config.Email) (*config.Email, error) {
|
||||
if !HasConfigTable(db) {
|
||||
if legacy == nil {
|
||||
return defaultEmailConfig(), nil
|
||||
}
|
||||
return config.CloneEmail(legacy), nil
|
||||
}
|
||||
email, found, err := LoadEmailConfig(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if found {
|
||||
return email, nil
|
||||
}
|
||||
if legacy == nil {
|
||||
legacy = defaultEmailConfig()
|
||||
}
|
||||
if err = SaveEmailConfig(db, legacy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
email, _, err = LoadEmailConfig(db)
|
||||
return email, err
|
||||
}
|
||||
Reference in New Issue
Block a user