kra-new/app/system/internal/data/repository/integration_config.go

158 lines
5.2 KiB
Go

package system
import (
"context"
"encoding/json"
"errors"
"strings"
"time"
"kra/app/system/internal/biz"
"gorm.io/gorm"
)
type integrationConfigPO struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
Kind string `gorm:"size:32;not null;uniqueIndex:idx_integration_kind_provider"`
Provider string `gorm:"size:64;not null;uniqueIndex:idx_integration_kind_provider"`
Enabled bool
Config string `gorm:"type:text;not null"`
}
func (integrationConfigPO) TableName() string { return "sys_integration_configs" }
type integrationConfigRepo struct{ data Provider }
func NewIntegrationConfigRepo(data Provider) biz.IntegrationConfigRepo {
return &integrationConfigRepo{data: data}
}
func (r *integrationConfigRepo) ListIntegrationConfigs(ctx context.Context, kind string) ([]*biz.IntegrationConfig, error) {
var rows []integrationConfigPO
if err := r.data.DB().WithContext(ctx).Where("kind = ?", kind).Order("provider ASC").Find(&rows).Error; err != nil {
return nil, err
}
result := make([]*biz.IntegrationConfig, 0, len(rows))
for _, row := range rows {
result = append(result, integrationConfigFromPO(row))
}
return result, nil
}
func (r *integrationConfigRepo) FindIntegrationConfig(ctx context.Context, kind, provider string) (*biz.IntegrationConfig, error) {
var row integrationConfigPO
if err := r.data.DB().WithContext(ctx).Where("kind = ? AND provider = ?", kind, provider).First(&row).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("集成配置不存在")
}
return nil, err
}
return integrationConfigFromPO(row), nil
}
func (r *integrationConfigRepo) SaveIntegrationConfig(ctx context.Context, config *biz.IntegrationConfig) error {
db := r.data.DB().WithContext(ctx)
var row integrationConfigPO
err := db.Where("kind = ? AND provider = ?", config.Kind, config.Provider).First(&row).Error
values := integrationObject(config.Values)
if errors.Is(err, gorm.ErrRecordNotFound) {
if config.Enabled {
if err = biz.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil {
return err
}
}
encoded, _ := json.Marshal(values)
return db.Create(&integrationConfigPO{Kind: config.Kind, Provider: config.Provider, Enabled: config.Enabled, Config: string(encoded)}).Error
}
if err != nil {
return err
}
mergeIntegrationSecrets(config.Kind, config.Provider, values, integrationObject(json.RawMessage(row.Config)))
if config.Enabled {
if err = biz.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil {
return err
}
}
encoded, _ := json.Marshal(values)
return db.Model(&row).Updates(map[string]any{"enabled": config.Enabled, "config": string(encoded)}).Error
}
func (r *integrationConfigRepo) DeleteIntegrationConfig(ctx context.Context, kind, provider string) error {
return r.data.DB().WithContext(ctx).Where("kind = ? AND provider = ?", kind, provider).Delete(&integrationConfigPO{}).Error
}
func integrationConfigFromPO(row integrationConfigPO) *biz.IntegrationConfig {
values := integrationObject(json.RawMessage(row.Config))
maskIntegrationSecrets(row.Kind, row.Provider, values)
encoded, _ := json.Marshal(values)
return &biz.IntegrationConfig{Kind: row.Kind, Provider: row.Provider, Enabled: row.Enabled, Values: encoded}
}
func integrationObject(raw json.RawMessage) map[string]any {
values := map[string]any{}
_ = json.Unmarshal(raw, &values)
return values
}
func maskIntegrationSecrets(kind, provider string, values map[string]any) {
secretFields := integrationSecretFields(kind, provider)
for key, value := range values {
if secretFields[key] || likelyIntegrationSecret(key) {
if text, ok := value.(string); ok && text != "" {
values[key] = "******"
}
continue
}
if nested, ok := value.(map[string]any); ok {
maskIntegrationSecrets(kind, provider, nested)
}
}
}
func mergeIntegrationSecrets(kind, provider string, values, old map[string]any) {
secretFields := integrationSecretFields(kind, provider)
for key, value := range values {
if secretFields[key] || likelyIntegrationSecret(key) {
if text, ok := value.(string); ok && text == "******" {
if prior, exists := old[key]; exists {
values[key] = prior
}
}
continue
}
if nested, ok := value.(map[string]any); ok {
if prior, ok := old[key].(map[string]any); ok {
mergeIntegrationSecrets(kind, provider, nested, prior)
}
}
}
}
func integrationSecretFields(kind, provider string) map[string]bool {
result := map[string]bool{}
if definition, ok := biz.IntegrationDefinition(kind, provider); ok {
for _, field := range definition.Fields {
if field.Secret {
result[field.Key] = true
}
}
}
return result
}
func likelyIntegrationSecret(key string) bool {
normalized := strings.ToLower(strings.ReplaceAll(key, "-", "_"))
if strings.Contains(normalized, "secret") || strings.Contains(normalized, "private") || strings.Contains(normalized, "password") || strings.Contains(normalized, "credential") {
return true
}
for _, item := range []string{"key", "token", "access_token", "api_key", "mch_key", "client_key", "certificate", "cert", "p12", "pkcs12", "public_key", "platform_cert", "root_cert"} {
if normalized == item || strings.HasSuffix(normalized, "_"+item) {
return true
}
}
return false
}