unit uConfigManager; interface uses Winapi.Windows, SysUtils, Classes, System.JSON, System.Generics.Collections, System.IniFiles, System.IOUtils, System.Rtti, System.StrUtils; type TConfigValueType = (cvString, cvInteger, cvBoolean, cvFloat, cvArray, cvObject); TConfigItem = record Key: string; Value: TValue; ValueType: TConfigValueType; DefaultValue: TValue; Description: string; Section: string; procedure Clear; class function Create(const AKey, ASection: string; AValue: TValue; ADescription: string = ''): TConfigItem; static; end; TConfigManager = class private FConfigFile: string; FConfigItems: TDictionary; FJSONFile: string; FIsReadOnly: Boolean; FAutoSave: Boolean; procedure LoadFromINI; procedure SaveToINI; procedure LoadFromJSON; procedure SaveToJSON; function GetFullKey(const Section, Key: string): string; function GetConfigValue(const Key: string; const Default: TValue): TValue; public constructor Create(const AConfigFile: string; AAutoSave: Boolean = True); destructor Destroy; override; procedure Load; procedure Save; function GetString(const Section, Key, Default: string): string; function GetInteger(const Section, Key: string; Default: Integer): Integer; function GetBoolean(const Section, Key: string; Default: Boolean): Boolean; function GetFloat(const Section, Key: string; Default: Double): Double; function GetArray(const Section, Key: string; Default: TArray): TArray; function GetObject(const Section, Key: string; Default: TJSONObject = nil): TJSONObject; procedure SetString(const Section, Key, Value: string); procedure SetInteger(const Section, Key: string; Value: Integer); procedure SetBoolean(const Section, Key: string; Value: Boolean); procedure SetFloat(const Section, Key: string; Value: Double); procedure SetArray(const Section, Key: string; Value: TArray); procedure SetObject(const Section, Key: string; Value: TJSONObject); procedure AddConfigItem(const Item: TConfigItem); function HasKey(const Section, Key: string): Boolean; procedure RemoveKey(const Section, Key: string); procedure Clear; function GetAllKeys(const Section: string): TArray; property ConfigFile: string read FConfigFile write FConfigFile; property IsReadOnly: Boolean read FIsReadOnly write FIsReadOnly; property AutoSave: Boolean read FAutoSave write FAutoSave; end; TAppConfig = class private FConfigManager: TConfigManager; public constructor Create; destructor Destroy; override; property ConfigMgr: TConfigManager read FConfigManager; function GetServerURL: string; function GetServerPort: Integer; function GetTimeout: Integer; function GetRetryCount: Integer; function GetPLCHost: string; function GetPLCPort: Integer; function GetSerialPort: string; function GetBaudRate: Integer; function GetBatchSize: Integer; function GetAutoSave: Boolean; function GetLogLevel: Integer; function GetFileServerPath: string; function GetBackupServerPath: string; function GetSyncInterval: Integer; function GetMaxRetryCount: Integer; function GetLogFileDir: string; function GetLogFileMaxSize: Integer; function GetCORSOrigin: string; function GetAPIPath: string; function GetAdminAPIPath: string; procedure Save; end; function Config: TAppConfig; implementation var FAppConfig: TAppConfig; function Config: TAppConfig; begin if FAppConfig = nil then FAppConfig := TAppConfig.Create; Result := FAppConfig; end; { TConfigItem } procedure TConfigItem.Clear; begin Key := ''; Value := TValue.Empty; ValueType := cvString; DefaultValue := TValue.Empty; Description := ''; Section := ''; end; class function TConfigItem.Create(const AKey, ASection: string; AValue: TValue; ADescription: string): TConfigItem; begin Result.Clear; Result.Key := AKey; Result.Section := ASection; Result.Value := AValue; Result.DefaultValue := AValue; Result.Description := ADescription; // 正确判断类型 XE11 兼容 if AValue.TypeInfo = TypeInfo(string) then Result.ValueType := cvString else if AValue.TypeInfo = TypeInfo(Integer) then Result.ValueType := cvInteger else if AValue.TypeInfo = TypeInfo(Boolean) then Result.ValueType := cvBoolean else if AValue.TypeInfo = TypeInfo(Double) then Result.ValueType := cvFloat else Result.ValueType := cvString; end; { TConfigManager } constructor TConfigManager.Create(const AConfigFile: string; AAutoSave: Boolean); begin inherited Create; FConfigFile := AConfigFile; FConfigItems := TDictionary.Create; FAutoSave := AAutoSave; FIsReadOnly := False; FJSONFile := ChangeFileExt(AConfigFile, '.json'); Load; end; destructor TConfigManager.Destroy; begin if FAutoSave then Save; FConfigItems.Free; inherited; end; function TConfigManager.GetFullKey(const Section, Key: string): string; begin Result := Section + '.' + Key; end; function TConfigManager.GetConfigValue(const Key: string; const Default: TValue): TValue; var Item: TConfigItem; begin if FConfigItems.TryGetValue(Key, Item) then Result := Item.Value else Result := Default; end; procedure TConfigManager.LoadFromINI; var Sections: TStringList; Section, Key, ValueStr: string; Items: TStringList; I, J: Integer; INIFile: TIniFile; begin if not FileExists(FConfigFile) then Exit; INIFile := TIniFile.Create(FConfigFile); try Sections := TStringList.Create; try INIFile.ReadSections(Sections); for I := 0 to Sections.Count - 1 do begin Section := Sections[I]; Items := TStringList.Create; try INIFile.ReadSection(Section, Items); for J := 0 to Items.Count - 1 do begin Key := Items[J]; ValueStr := INIFile.ReadString(Section, Key, ''); FConfigItems.AddOrSetValue(GetFullKey(Section, Key), TConfigItem.Create(Key, Section, TValue.From(ValueStr))); end; finally Items.Free; end; end; finally Sections.Free; end; finally INIFile.Free; end; end; procedure TConfigManager.SaveToINI; var Item: TConfigItem; FullKey: string; INIFile: TIniFile; begin if FIsReadOnly then Exit; INIFile := TIniFile.Create(FConfigFile); try for FullKey in FConfigItems.Keys do begin Item := FConfigItems[FullKey]; case Item.ValueType of cvString: INIFile.WriteString(Item.Section, Item.Key, Item.Value.AsString); cvInteger: INIFile.WriteInteger(Item.Section, Item.Key, Item.Value.AsInteger); cvBoolean: INIFile.WriteBool(Item.Section, Item.Key, Item.Value.AsBoolean); cvFloat: INIFile.WriteFloat(Item.Section, Item.Key, Item.Value.AsExtended); end; end; finally INIFile.Free; end; end; procedure TConfigManager.LoadFromJSON; var JSONStr: string; JSONObj: TJSONObject; Section, Key: string; SectionObj: TJSONObject; Pair: TJSONPair; InnerPair: TJSONPair; Val: TValue; begin if not FileExists(FJSONFile) then Exit; JSONStr := TFile.ReadAllText(FJSONFile, TEncoding.UTF8); JSONObj := TJSONObject.ParseJSONValue(JSONStr) as TJSONObject; if JSONObj = nil then Exit; try for Pair in JSONObj do begin Section := Pair.JsonString.Value; if Pair.JsonValue is TJSONObject then begin SectionObj := Pair.JsonValue as TJSONObject; for InnerPair in SectionObj do begin Key := InnerPair.JsonString.Value; Val := TValue.From(InnerPair.JsonValue.Value); FConfigItems.AddOrSetValue(GetFullKey(Section, Key), TConfigItem.Create(Key, Section, Val)); end; end; end; finally JSONObj.Free; end; end; procedure TConfigManager.SaveToJSON; var JSONObj: TJSONObject; SectionObj: TJSONObject; Item: TConfigItem; FullKey: string; Sections: TDictionary; begin if FIsReadOnly then Exit; JSONObj := TJSONObject.Create; Sections := TDictionary.Create; try for FullKey in FConfigItems.Keys do begin Item := FConfigItems[FullKey]; if not Sections.TryGetValue(Item.Section, SectionObj) then begin SectionObj := TJSONObject.Create; Sections.Add(Item.Section, SectionObj); JSONObj.AddPair(Item.Section, SectionObj); end; case Item.ValueType of cvString: SectionObj.AddPair(Item.Key, Item.Value.AsString); cvInteger: SectionObj.AddPair(Item.Key, TJSONNumber.Create(Item.Value.AsInteger)); cvBoolean: SectionObj.AddPair(Item.Key, TJSONBool.Create(Item.Value.AsBoolean)); cvFloat: SectionObj.AddPair(Item.Key, TJSONNumber.Create(Item.Value.AsExtended)); cvArray, cvObject: SectionObj.AddPair(Item.Key, Item.Value.AsString); end; end; TFile.WriteAllText(FJSONFile, JSONObj.Format(2), TEncoding.UTF8); finally JSONObj.Free; Sections.Free; end; end; procedure TConfigManager.Load; begin FConfigItems.Clear; if FileExists(FConfigFile) then LoadFromINI else if FileExists(FJSONFile) then LoadFromJSON; end; procedure TConfigManager.Save; begin if FIsReadOnly then Exit; if EndsText('.ini', LowerCase(FConfigFile)) then SaveToINI else SaveToJSON; end; function TConfigManager.GetString(const Section, Key, Default: string): string; var FullKey: string; Val: TValue; begin FullKey := GetFullKey(Section, Key); Val := GetConfigValue(FullKey, TValue.From(Default)); Result := Val.AsString; end; function TConfigManager.GetInteger(const Section, Key: string; Default: Integer): Integer; var FullKey: string; Val: TValue; begin FullKey := GetFullKey(Section, Key); Val := GetConfigValue(FullKey, TValue.From(Default)); Result := Val.AsInteger; end; function TConfigManager.GetBoolean(const Section, Key: string; Default: Boolean): Boolean; var FullKey: string; Val: TValue; begin FullKey := GetFullKey(Section, Key); Val := GetConfigValue(FullKey, TValue.From(Default)); Result := Val.AsBoolean; end; function TConfigManager.GetFloat(const Section, Key: string; Default: Double): Double; var FullKey: string; Val: TValue; begin FullKey := GetFullKey(Section, Key); Val := GetConfigValue(FullKey, TValue.From(Default)); Result := Val.AsExtended; end; function TConfigManager.GetArray(const Section, Key: string; Default: TArray): TArray; var FullKey: string; Val: TValue; JSONStr: string; JSONArray: TJSONArray; I: Integer; begin FullKey := GetFullKey(Section, Key); Val := GetConfigValue(FullKey, TValue.From('[]')); JSONStr := Val.AsString; JSONArray := TJSONArray.ParseJSONValue(JSONStr) as TJSONArray; if JSONArray = nil then begin Result := Default; Exit; end; try SetLength(Result, JSONArray.Count); for I := 0 to JSONArray.Count - 1 do Result[I] := JSONArray.Items[I].Value; finally JSONArray.Free; end; end; function TConfigManager.GetObject(const Section, Key: string; Default: TJSONObject): TJSONObject; var FullKey: string; Val: TValue; JSONStr: string; begin FullKey := GetFullKey(Section, Key); Val := GetConfigValue(FullKey, TValue.From('{}')); JSONStr := Val.AsString; Result := TJSONObject.ParseJSONValue(JSONStr) as TJSONObject; if Result = nil then Result := Default; end; procedure TConfigManager.SetString(const Section, Key, Value: string); var FullKey: string; Item: TConfigItem; begin FullKey := GetFullKey(Section, Key); Item := TConfigItem.Create(Key, Section, TValue.From(Value)); FConfigItems.AddOrSetValue(FullKey, Item); if FAutoSave then Save; end; procedure TConfigManager.SetInteger(const Section, Key: string; Value: Integer); var FullKey: string; Item: TConfigItem; begin FullKey := GetFullKey(Section, Key); Item := TConfigItem.Create(Key, Section, TValue.From(Value)); FConfigItems.AddOrSetValue(FullKey, Item); if FAutoSave then Save; end; procedure TConfigManager.SetBoolean(const Section, Key: string; Value: Boolean); var FullKey: string; Item: TConfigItem; begin FullKey := GetFullKey(Section, Key); Item := TConfigItem.Create(Key, Section, TValue.From(Value)); FConfigItems.AddOrSetValue(FullKey, Item); if FAutoSave then Save; end; procedure TConfigManager.SetFloat(const Section, Key: string; Value: Double); var FullKey: string; Item: TConfigItem; begin FullKey := GetFullKey(Section, Key); Item := TConfigItem.Create(Key, Section, TValue.From(Value)); FConfigItems.AddOrSetValue(FullKey, Item); if FAutoSave then Save; end; procedure TConfigManager.SetArray(const Section, Key: string; Value: TArray); var FullKey: string; Item: TConfigItem; JSONArray: TJSONArray; I: Integer; begin FullKey := GetFullKey(Section, Key); JSONArray := TJSONArray.Create; try for I := Low(Value) to High(Value) do JSONArray.Add(Value[I]); Item := TConfigItem.Create(Key, Section, TValue.From(JSONArray.ToJSON)); finally JSONArray.Free; end; FConfigItems.AddOrSetValue(FullKey, Item); if FAutoSave then Save; end; procedure TConfigManager.SetObject(const Section, Key: string; Value: TJSONObject); var FullKey: string; Item: TConfigItem; JSONStr: string; begin FullKey := GetFullKey(Section, Key); if Value = nil then JSONStr := '{}' else JSONStr := Value.ToJSON; Item := TConfigItem.Create(Key, Section, TValue.From(JSONStr)); FConfigItems.AddOrSetValue(FullKey, Item); if FAutoSave then Save; end; procedure TConfigManager.AddConfigItem(const Item: TConfigItem); var FullKey: string; begin FullKey := GetFullKey(Item.Section, Item.Key); FConfigItems.AddOrSetValue(FullKey, Item); if FAutoSave then Save; end; function TConfigManager.HasKey(const Section, Key: string): Boolean; begin Result := FConfigItems.ContainsKey(GetFullKey(Section, Key)); end; procedure TConfigManager.RemoveKey(const Section, Key: string); begin FConfigItems.Remove(GetFullKey(Section, Key)); if FAutoSave then Save; end; procedure TConfigManager.Clear; begin FConfigItems.Clear; if FAutoSave then Save; end; function TConfigManager.GetAllKeys(const Section: string): TArray; var FullKey: string; Item: TConfigItem; List: TList; begin List := TList.Create; try for FullKey in FConfigItems.Keys do begin Item := FConfigItems[FullKey]; if SameText(Item.Section, Section) then List.Add(Item.Key); end; Result := List.ToArray; finally List.Free; end; end; { TAppConfig } constructor TAppConfig.Create; var ConfigPath: string; begin inherited Create; ConfigPath := ExtractFilePath(ParamStr(0)) + 'config.ini'; FConfigManager := TConfigManager.Create(ConfigPath); // 自动初始化默认配置 if not FConfigManager.HasKey('Network', 'ServerURL') then FConfigManager.SetString('Network', 'ServerURL', 'http://192.168.0.150:8080'); if not FConfigManager.HasKey('Network', 'ServerPort') then FConfigManager.SetInteger('Network', 'ServerPort', 8080); if not FConfigManager.HasKey('Network', 'Timeout') then FConfigManager.SetInteger('Network', 'Timeout', 30000); if not FConfigManager.HasKey('Network', 'RetryCount') then FConfigManager.SetInteger('Network', 'RetryCount', 3); if not FConfigManager.HasKey('Device', 'PLCHost') then FConfigManager.SetString('Device', 'PLCHost', '192.168.1.100'); if not FConfigManager.HasKey('Device', 'PLCPort') then FConfigManager.SetInteger('Device', 'PLCPort', 502); if not FConfigManager.HasKey('Device', 'SerialPort') then FConfigManager.SetString('Device', 'SerialPort', 'COM1'); if not FConfigManager.HasKey('Device', 'BaudRate') then FConfigManager.SetInteger('Device', 'BaudRate', 9600); if not FConfigManager.HasKey('Business', 'BatchSize') then FConfigManager.SetInteger('Business', 'BatchSize', 100); if not FConfigManager.HasKey('Business', 'AutoSave') then FConfigManager.SetBoolean('Business', 'AutoSave', True); if not FConfigManager.HasKey('Business', 'LogLevel') then FConfigManager.SetInteger('Business', 'LogLevel', 2); if not FConfigManager.HasKey('FileServer', 'FileServerPath') then FConfigManager.SetString('FileServer', 'FileServerPath', '\\192.168.0.250\DataSava'); if not FConfigManager.HasKey('FileServer', 'BackupServerPath') then FConfigManager.SetString('FileServer', 'BackupServerPath', '\\192.168.0.250\Backup'); if not FConfigManager.HasKey('Sync', 'SyncInterval') then FConfigManager.SetInteger('Sync', 'SyncInterval', 60000); if not FConfigManager.HasKey('Sync', 'MaxRetryCount') then FConfigManager.SetInteger('Sync', 'MaxRetryCount', 5); if not FConfigManager.HasKey('Log', 'LogFileDir') then FConfigManager.SetString('Log', 'LogFileDir', ExtractFilePath(ParamStr(0)) + 'log'); if not FConfigManager.HasKey('Log', 'LogFileMaxSize') then FConfigManager.SetInteger('Log', 'LogFileMaxSize', 10485760); if not FConfigManager.HasKey('Network', 'CORSOrigin') then FConfigManager.SetString('Network', 'CORSOrigin', '*'); if not FConfigManager.HasKey('Network', 'APIPath') then FConfigManager.SetString('Network', 'APIPath', '/prod-api'); if not FConfigManager.HasKey('Network', 'AdminAPIPath') then FConfigManager.SetString('Network', 'AdminAPIPath', '/admin-api'); end; destructor TAppConfig.Destroy; begin FConfigManager.Free; inherited; end; function TAppConfig.GetServerURL: string; begin Result := FConfigManager.GetString('Network', 'ServerURL', 'http://192.168.0.150:8080'); end; function TAppConfig.GetServerPort: Integer; begin Result := FConfigManager.GetInteger('Network', 'ServerPort', 8080); end; function TAppConfig.GetTimeout: Integer; begin Result := FConfigManager.GetInteger('Network', 'Timeout', 30000); end; function TAppConfig.GetRetryCount: Integer; begin Result := FConfigManager.GetInteger('Network', 'RetryCount', 3); end; function TAppConfig.GetPLCHost: string; begin Result := FConfigManager.GetString('Device', 'PLCHost', '192.168.1.100'); end; function TAppConfig.GetPLCPort: Integer; begin Result := FConfigManager.GetInteger('Device', 'PLCPort', 502); end; function TAppConfig.GetSerialPort: string; begin Result := FConfigManager.GetString('Device', 'SerialPort', 'COM1'); end; function TAppConfig.GetBaudRate: Integer; begin Result := FConfigManager.GetInteger('Device', 'BaudRate', 9600); end; function TAppConfig.GetBatchSize: Integer; begin Result := FConfigManager.GetInteger('Business', 'BatchSize', 100); end; function TAppConfig.GetAutoSave: Boolean; begin Result := FConfigManager.GetBoolean('Business', 'AutoSave', True); end; function TAppConfig.GetLogLevel: Integer; begin Result := FConfigManager.GetInteger('Business', 'LogLevel', 2); end; function TAppConfig.GetFileServerPath: string; begin Result := FConfigManager.GetString('FileServer', 'FileServerPath', '\\192.168.0.250\DataSava'); end; function TAppConfig.GetBackupServerPath: string; begin Result := FConfigManager.GetString('FileServer', 'BackupServerPath', '\\192.168.0.250\Backup'); end; function TAppConfig.GetSyncInterval: Integer; begin Result := FConfigManager.GetInteger('Sync', 'SyncInterval', 60000); end; function TAppConfig.GetMaxRetryCount: Integer; begin Result := FConfigManager.GetInteger('Sync', 'MaxRetryCount', 5); end; function TAppConfig.GetLogFileDir: string; begin Result := FConfigManager.GetString('Log', 'LogFileDir', ExtractFilePath(ParamStr(0)) + 'log'); end; function TAppConfig.GetLogFileMaxSize: Integer; begin Result := FConfigManager.GetInteger('Log', 'LogFileMaxSize', 10485760); end; function TAppConfig.GetCORSOrigin: string; begin Result := FConfigManager.GetString('Network', 'CORSOrigin', '*'); end; function TAppConfig.GetAPIPath: string; begin Result := FConfigManager.GetString('Network', 'APIPath', '/prod-api'); end; function TAppConfig.GetAdminAPIPath: string; begin Result := FConfigManager.GetString('Network', 'AdminAPIPath', '/admin-api'); end; procedure TAppConfig.Save; begin FConfigManager.Save; end; end.