package services import ( "cmbone/internal/models" "database/sql" "fmt" "strconv" "strings" ) type AppConfigService struct { store *SuiStore } // 创建新的AppConfigService实例 func NewAppConfigService(store *SuiStore) *AppConfigService { return &AppConfigService{ store: store, } } // 根据key获取配置项 func (r *AppConfigService) GetAppConfig(key string) (string, error) { row := r.store.DB.QueryRow(` SELECT value FROM appconfig WHERE key = ? `, key) var value string err := row.Scan(&value) if err != nil { if err == sql.ErrNoRows { return "", nil } return "", err } return value, nil } // 设置或更新配置项 func (r *AppConfigService) SetAppConfig(key, value string) error { oldValue, _ := r.GetAppConfig(key) configType := "user" description := "" if definition, ok := settingDefinitionByKey(key); ok { if definition.ReadOnly { return fmt.Errorf("setting %q is read only", key) } if err := validateSettingValue(definition, value); err != nil { return err } configType = definition.Type description = definition.Description } _, err := r.store.DB.Exec(` INSERT INTO appconfig (key, type, value, description) VALUES (?, ?, ?, ?) ON CONFLICT(key) DO UPDATE SET type=excluded.type, value=excluded.value, description=excluded.description `, key, configType, value, description) if err == nil && oldValue != value && shouldAuditConfigChange(key) { detail := fmt.Sprintf("%s: %q -> %q", key, oldValue, value) _ = recordAuditLog(r.store, currentAuditActor(r.store), auditActionSettingsUpdate, "appconfig", key, detail) } return err } // 获取应用语言 func (s *AppConfigService) GetLanguage() string { lang, err := s.GetAppConfig("language") if err != nil || lang == "" { return "en" } return lang } // 设置应用语言 func (s *AppConfigService) SetLanguage(lang string) error { return s.SetAppConfig("language", lang) } func (s *AppConfigService) GetSettingsSchema() ([]models.AppSettingGroup, error) { values, err := s.getAppConfigValues() if err != nil { return nil, err } definitionsByCategory := make(map[string][]models.AppSettingDefinition) for _, definition := range settingsDefinitions() { value := values[definition.Key] if value == "" { value = definition.DefaultValue } definition.Value = value if definition.Options == nil { definition.Options = []models.AppSettingOption{} } definitionsByCategory[definition.Category] = append(definitionsByCategory[definition.Category], definition) } groups := make([]models.AppSettingGroup, 0, len(settingGroups)) for _, group := range settingGroups { group.Items = definitionsByCategory[group.Category] if len(group.Items) > 0 { groups = append(groups, group) } } return groups, nil } func (s *AppConfigService) getAppConfigValues() (map[string]string, error) { rows, err := s.store.DB.Query("SELECT key, value FROM appconfig") if err != nil { return nil, err } defer rows.Close() values := make(map[string]string) for rows.Next() { var key string var value string if err := rows.Scan(&key, &value); err != nil { return nil, err } values[key] = value } if err := rows.Err(); err != nil { return nil, err } return values, nil } func validateSettingValue(definition models.AppSettingDefinition, value string) error { switch definition.Type { case settingTypeSelect: for _, option := range definition.Options { if option.Value == value { return nil } } return fmt.Errorf("setting %q value %q is not allowed", definition.Key, value) case settingTypeBoolean: if value == "true" || value == "false" { return nil } return fmt.Errorf("setting %q expects boolean string", definition.Key) case settingTypeNumber: if _, err := strconv.Atoi(value); err != nil { return fmt.Errorf("setting %q expects number string: %w", definition.Key, err) } } return nil } func shouldAuditConfigChange(key string) bool { if strings.HasPrefix(key, "window.") || strings.HasPrefix(key, "auth.local.") { return false } definition, ok := settingDefinitionByKey(key) if !ok { return false } switch definition.Category { case "appearance", "data", "logs", "auth": return true default: return false } }