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 }