using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; using System.IO; namespace Txgy.EWS.Client.Common { public static class BusinessConfigManager { private static readonly object SyncRoot = new object(); private static BusinessConfig _current; public static string ConfigPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config", "client-settings.json"); public static BusinessConfig Current { get { lock (SyncRoot) { return _current ?? (_current = Read()); } } } public static BusinessConfig Read() { if (!File.Exists(ConfigPath)) { throw new FileNotFoundException("Business config file was not found.", ConfigPath); } var json = File.ReadAllText(ConfigPath); var config = JsonConvert.DeserializeObject(json); NormalizeRuntimeSettings(config); Validate(config); return config; } public static BusinessConfig Reload() { lock (SyncRoot) { _current = Read(); FreeSqlTencent.Reset(); FreeSqlLocalSqLite.Reset(); return _current; } } public static BusinessConfig Save(BusinessConfig config) { lock (SyncRoot) { NormalizeRuntimeSettings(config); Validate(config); SaveConfig(config); _current = Read(); FreeSqlTencent.Reset(); FreeSqlLocalSqLite.Reset(); return _current; } } public static string GetConnectionString(string name) { var connections = Current.Database.Connections; if (connections == null || !connections.TryGetValue(name, out var value) || string.IsNullOrWhiteSpace(value)) { throw new InvalidOperationException($"Business config is missing database connection: {name}"); } return value; } public static string GetRequiredPath(string value, string name) { if (string.IsNullOrWhiteSpace(value)) { throw new InvalidOperationException($"Business config is missing path: {name}"); } return value; } public static string GetAppSetting(string key) { if (!GetLegacyAppSettings().TryGetValue(key, out var token)) { return null; } if (token == null || token.Type == JTokenType.Null) { return null; } return token.Type == JTokenType.String ? token.Value() : token.ToString(); } public static void UpdateAppSetting(string key, string value) { lock (SyncRoot) { var root = ReadRoot(); var appSettings = GetLegacyAppSettings(root); if (!appSettings.TryGetValue(key, out var token) || token == null) { throw new InvalidOperationException($"Business config does not support writing setting: {key}"); } token.Replace(ConvertValue(token, value)); SaveRoot(root); _current = root.ToObject(); Validate(_current); FreeSqlTencent.Reset(); FreeSqlLocalSqLite.Reset(); } } public static void UpdateRuntimeSettings(int loadDataTimeLenMins, int dataCacheTimeLenMins) { UpdateDataLookbackHours(loadDataTimeLenMins / 60.0); } public static void UpdateDataLookbackHours(double dataLookbackHours) { lock (SyncRoot) { if (dataLookbackHours <= 0) { throw new InvalidOperationException("runtime.dataLookbackHours must be greater than 0."); } var root = ReadRoot(); root["runtime"]["dataLookbackHours"] = dataLookbackHours; root["runtime"]["dataCacheTimeLenMins"] = (int)Math.Round(dataLookbackHours * 60.0); SaveRoot(root); _current = root.ToObject(); NormalizeRuntimeSettings(_current); Validate(_current); } } private static JObject ReadRoot() { if (!File.Exists(ConfigPath)) { throw new FileNotFoundException("Business config file was not found.", ConfigPath); } return JObject.Parse(File.ReadAllText(ConfigPath)); } private static void SaveConfig(BusinessConfig config) { var serializer = JsonSerializer.Create(new JsonSerializerSettings { Formatting = Formatting.Indented, StringEscapeHandling = StringEscapeHandling.EscapeNonAscii, ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy { ProcessDictionaryKeys = false } } }); string json; using (var writer = new StringWriter()) { serializer.Serialize(writer, config); json = writer.ToString(); } WriteJson(json); } private static void SaveRoot(JObject root) { var serializer = JsonSerializer.Create(new JsonSerializerSettings { Formatting = Formatting.Indented, StringEscapeHandling = StringEscapeHandling.EscapeNonAscii }); string json; using (var writer = new StringWriter()) { serializer.Serialize(writer, root); json = writer.ToString(); } WriteJson(json); } private static void WriteJson(string json) { var directory = Path.GetDirectoryName(ConfigPath); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } var tempPath = ConfigPath + ".tmp"; File.WriteAllText(tempPath, json, System.Text.Encoding.UTF8); if (File.Exists(ConfigPath)) { var backupPath = ConfigPath + ".bak"; if (File.Exists(backupPath)) { File.Delete(backupPath); } File.Replace(tempPath, ConfigPath, backupPath, true); if (File.Exists(backupPath)) { File.Delete(backupPath); } return; } File.Move(tempPath, ConfigPath); } private static Dictionary GetLegacyAppSettings() { return GetLegacyAppSettings(ReadRoot()); } private static Dictionary GetLegacyAppSettings(JObject root) { return new Dictionary { { "api_domain", root["endpoints"]?["apiDomain"] }, { "AlarmSetting", root["paths"]?["alarmSetting"] }, { "AlarmLevelConfig", root["paths"]?["alarmLevelConfig"] }, { "ReportEventLevelSetting", root["paths"]?["reportEventLevelSetting"] }, { "WorkAreaFilePath", root["paths"]?["workAreaFilePath"] }, { "StationsCsvFilePath", root["paths"]?["stationsCsvFilePath"] }, { "CadDwgFilePath", root["paths"]?["cadDwgFilePath"] }, { "DwgJsonSetting", root["paths"]?["dwgJsonSetting"] }, { "WavesMseedFilePath", root["paths"]?["wavesMseedFilePath"] }, { "WavesTxtFilePath", root["paths"]?["wavesTxtFilePath"] }, { "LocalSqLiteDb", root["paths"]?["localSqLiteDb"] }, { "DwgSettings", root["paths"]?["dwgSettings"] }, { "CadSettingsFileName", root["paths"]?["cadSettingsFileName"] }, { "IsDesign", root["runtime"]?["isDesign"] }, { "IsRealtime", root["runtime"]?["isRealtime"] }, { "RefreshInterval", root["runtime"]?["refreshInterval"] }, { "DataLookbackHours", root["runtime"]?["dataLookbackHours"] }, { "DataCacheTimeLenMins", root["runtime"]?["dataCacheTimeLenMins"] }, { "RealtimeResultTable", root["database"]?["tables"]?["realtimeResultTable"] }, { "RealtimeWaveDataTable", root["database"]?["tables"]?["realtimeWaveDataTable"] }, { "RealtimeFocalmechanismTable", root["database"]?["tables"]?["realtimeFocalmechanismTable"] }, { "PostResultTable", root["database"]?["tables"]?["postResultTable"] }, { "PostWaveDataTable", root["database"]?["tables"]?["postWaveDataTable"] }, { "PostFocalmechanismTable", root["database"]?["tables"]?["postFocalmechanismTable"] }, { "CommpanyName", root["branding"]?["commpanyName"] }, { "SystemNameCn", root["branding"]?["systemNameCn"] }, { "SystemNameEn", root["branding"]?["systemNameEn"] }, { "WorkAreaName", root["branding"]?["workAreaName"] }, { "SystemShortName", root["branding"]?["systemShortName"] }, { "日报起始时间", root["report"]?["dailyReportStartTime"] }, { "日报平面图横向偏移", root["report"]?["dailyReportPlanOffsetX"] }, { "日报平面图纵向偏移", root["report"]?["dailyReportPlanOffsetY"] }, { "BaseX", root["coordinates"]?["baseX"] }, { "BaseY", root["coordinates"]?["baseY"] }, { "BaseZ", root["coordinates"]?["baseZ"] } }; } private static JToken ConvertValue(JToken currentValue, string value) { switch (currentValue.Type) { case JTokenType.Boolean: return bool.Parse(value); case JTokenType.Integer: return int.Parse(value); case JTokenType.Float: return double.Parse(value); default: return value; } } private static void Validate(BusinessConfig config) { if (config == null) { throw new InvalidOperationException("Business config file format is invalid."); } Require(config.Endpoints?.ApiDomain, "endpoints.apiDomain"); Require(config.Paths?.AlarmSetting, "paths.alarmSetting"); Require(config.Paths?.AlarmLevelConfig, "paths.alarmLevelConfig"); Require(config.Paths?.ReportEventLevelSetting, "paths.reportEventLevelSetting"); Require(config.Paths?.WorkAreaFilePath, "paths.workAreaFilePath"); Require(config.Paths?.StationsCsvFilePath, "paths.stationsCsvFilePath"); Require(config.Paths?.CadDwgFilePath, "paths.cadDwgFilePath"); Require(config.Paths?.DwgJsonSetting, "paths.dwgJsonSetting"); Require(config.Paths?.WavesMseedFilePath, "paths.wavesMseedFilePath"); Require(config.Paths?.WavesTxtFilePath, "paths.wavesTxtFilePath"); Require(config.Paths?.LocalSqLiteDb, "paths.localSqLiteDb"); Require(config.Branding?.CommpanyName, "branding.commpanyName"); Require(config.Branding?.SystemNameCn, "branding.systemNameCn"); Require(config.Branding?.SystemNameEn, "branding.systemNameEn"); Require(config.Branding?.WorkAreaName, "branding.workAreaName"); Require(config.Branding?.SystemShortName, "branding.systemShortName"); Require(config.Report?.DailyReportStartTime, "report.dailyReportStartTime"); Require(config.Database?.Tables?.RealtimeResultTable, "database.tables.realtimeResultTable"); Require(config.Database?.Tables?.RealtimeWaveDataTable, "database.tables.realtimeWaveDataTable"); Require(config.Database?.Tables?.RealtimeFocalmechanismTable, "database.tables.realtimeFocalmechanismTable"); Require(config.Database?.Tables?.PostResultTable, "database.tables.postResultTable"); Require(config.Database?.Tables?.PostWaveDataTable, "database.tables.postWaveDataTable"); Require(config.Database?.Tables?.PostFocalmechanismTable, "database.tables.postFocalmechanismTable"); GetConnectionStringFromConfig(config, "TencetnMySQL"); DateTime.Parse(config.Report.DailyReportStartTime); } private static void NormalizeRuntimeSettings(BusinessConfig config) { if (config?.Runtime == null) { return; } var runtime = config.Runtime; if (runtime.DataLookbackHours <= 0) { throw new InvalidOperationException("runtime.dataLookbackHours must be greater than 0."); } runtime.DataCacheTimeLenMins = (int)Math.Round(runtime.DataLookbackHours * 60.0); } private static void Require(string value, string name) { if (string.IsNullOrWhiteSpace(value)) { throw new InvalidOperationException($"Business config is missing setting: {name}"); } } private static void GetConnectionStringFromConfig(BusinessConfig config, string name) { if (config.Database?.Connections == null || !config.Database.Connections.TryGetValue(name, out var value) || string.IsNullOrWhiteSpace(value)) { throw new InvalidOperationException($"Business config is missing database connection: {name}"); } } } }