diff --git a/插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs b/插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs index 402f90f..82a9db0 100644 --- a/插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs +++ b/插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs @@ -43,7 +43,7 @@ namespace Lskj.Main.Hosting /// 向外部 WPF 登录页提供旧账套、用户、认证和会话状态写入能力。 /// 该类型不创建 WinForms 控件,也不引用 WPF。 /// - public sealed class LegacyLoginRuntime + public sealed partial class LegacyLoginRuntime { private DataTable _ledgerTable; diff --git a/插件库/Lskj.Main/Hosting/LegacyLoginRuntime.ConnectionSettings.cs b/插件库/Lskj.Main/Hosting/LegacyLoginRuntime.ConnectionSettings.cs new file mode 100644 index 0000000..8856719 --- /dev/null +++ b/插件库/Lskj.Main/Hosting/LegacyLoginRuntime.ConnectionSettings.cs @@ -0,0 +1,595 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Net; +using System.Text; +using Microsoft.Win32; +using Lskj.Business; +using Lskj.Business.Impl; +using Lskj.Control.Model; +using Lskj.Core; +using Lskj.Main.Model; +using Lskj.Model; +using Lskj.Util; + +namespace Lskj.Main.Hosting +{ + public sealed partial class LegacyLoginRuntime + { + private const string RegistryFilePath = @"AA_LS_Erp V2.0\File"; + private const string SystemResourcesIniName = "SystemResources.ini"; + private const string SystemResourcesSection = "SystemResources"; + private const string DirectHistoryPrefix = "DownloadAESStr_"; + private const string DirectLastHostKey = "LocalLastIP"; + private const string DirectLastPortKey = "LocalLastPort"; + private const string DirectLastNameKey = "LocalLastPortName"; + private const string ConnectionModeKey = "FrmConfigFlag"; + private const int DirectRequestTimeoutMilliseconds = 5000; + + public DataSet LoadConnectionSettings() + { + var result = new DataSet("LoginConnectionSettings"); + DataTable settings = CreateSettingsTable(); + DataTable savedConnections = CreateSavedConnectionsTable(); + + string directHost = IniHelper.Read( + SystemResourcesIniName, + DirectLastHostKey); + string directPort = IniHelper.Read( + SystemResourcesIniName, + DirectLastPortKey); + string directName = IniHelper.Read( + SystemResourcesIniName, + DirectLastNameKey); + bool directMode = string.Equals( + IniHelper.Read(SystemResourcesIniName, ConnectionModeKey), + "1", + StringComparison.Ordinal); + + settings.Rows.Add( + directMode ? "Direct" : "Database", + ReadRegistryValue("ServerName"), + ReadRegistryValue("datastr"), + directHost, + directPort, + directName); + + Dictionary entries = IniHelper.GetSectionKeys( + SystemResourcesIniName, + SystemResourcesSection); + foreach (KeyValuePair entry in entries) + { + if (!entry.Key.StartsWith( + DirectHistoryPrefix, + StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + string endpoint = entry.Key.Substring( + DirectHistoryPrefix.Length); + string host; + int port; + if (!TrySplitEndpoint(endpoint, out host, out port)) + continue; + + string displayName = ReadHistoryDisplayName(entry.Value); + if (string.IsNullOrWhiteSpace(displayName)) + continue; + + savedConnections.Rows.Add( + entry.Key, + displayName, + host, + port); + } + + result.Tables.Add(settings); + result.Tables.Add(savedConnections); + return result; + } + + public string ApplyDatabaseConnection( + string serverName, + string databaseName) + { + try + { + ConnectionCandidate candidate = CreateDatabaseCandidate( + serverName, + databaseName); + return ApplyConnection( + candidate, + delegate { CommitDatabaseConnection(); }); + } + catch (Exception exception) + { + LogSanitized(exception); + return ToSafeConnectionError(exception); + } + } + + public string ApplyDirectConnection( + string host, + int port, + string accountBookName) + { + try + { + Uri resourceUri = BuildDirectResourceUri(host, port); + string encryptedPayload = DownloadDirectPayload(resourceUri); + string decryptedPayload = AESUtil.Decrypt(encryptedPayload); + string[] parts = ParseDirectPayload(decryptedPayload); + ConnectionCandidate candidate = CreateDirectCandidate( + parts, + host, + port, + accountBookName, + encryptedPayload); + return ApplyConnection( + candidate, + delegate { CommitDirectConnection(candidate); }); + } + catch (Exception exception) + { + LogSanitized(exception); + return ToSafeConnectionError(exception); + } + } + + private string ApplyConnection( + ConnectionCandidate candidate, + Action commit) + { + LegacyConnectionStateSnapshot snapshot = + LegacyConnectionStateSnapshot.Capture(this); + try + { + ApplyCandidate(candidate); + ValidateDatabase(candidate); + ValidateDelphi(candidate); + SystemInfo.RefreshSystemParam(); + commit(); + + _ledgerTable = null; + SelectedLedgerName = candidate.AccountBookName; + ERPInfo.Instance.AccountBook = candidate.AccountBookName; + return string.Empty; + } + catch (Exception exception) + { + snapshot.Restore(this); + LogSanitized(exception); + return ToSafeConnectionError(exception); + } + } + + private static ConnectionCandidate CreateDatabaseCandidate( + string serverName, + string databaseName) + { + serverName = (serverName ?? string.Empty).Trim(); + databaseName = (databaseName ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(serverName)) + throw new ArgumentException("请输入数据库服务器地址。"); + if (string.IsNullOrWhiteSpace(databaseName)) + throw new ArgumentException("请输入数据库名称。"); + + return new ConnectionCandidate + { + ServerName = serverName, + DatabaseName = databaseName, + AccountBookName = DBConfig.Instance.DataBook ?? string.Empty, + ServerType = DBConfig.Instance.ServerType ?? "SqlServer", + ConnectionTemplate = string.Empty, + DelphiConnectionTemplate = string.Empty + }; + } + + private static ConnectionCandidate CreateDirectCandidate( + string[] parts, + string host, + int port, + string accountBookName, + string encryptedPayload) + { + accountBookName = (accountBookName ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(accountBookName)) + throw new ArgumentException("请输入直连账套名称。"); + + string connectionTemplate = + "Server={0};Database={1};Persist Security Info=True;" + + "User ID=" + parts[2] + ";Password=" + parts[3] + ";" + + "Connection Timeout=5;MultipleActiveResultSets=true"; + string delphiTemplate = + "Provider=SQLOLEDB.1;Server={0};Database={1};" + + "Persist Security Info=True;User ID=" + parts[2] + + ";Password=" + parts[3] + ";Connection Timeout=5"; + + return new ConnectionCandidate + { + ServerName = parts[0], + DatabaseName = parts[1], + AccountBookName = accountBookName, + ServerType = DBConfig.Instance.ServerType ?? "SqlServer", + ConnectionTemplate = AESUtil.Encrypt(connectionTemplate), + DelphiConnectionTemplate = AESUtil.Encrypt(delphiTemplate), + DirectHost = (host ?? string.Empty).Trim().TrimEnd('/'), + DirectPort = port, + DirectEncryptedPayload = encryptedPayload + }; + } + + private static void ApplyCandidate(ConnectionCandidate candidate) + { + DBConfig.Instance.ServerName = candidate.ServerName; + DBConfig.Instance.DataBase = candidate.DatabaseName; + DBConfig.Instance.DataBook = candidate.AccountBookName; + DBConfig.Instance.ServerType = candidate.ServerType; + DBConfig.Instance.Connection = candidate.ConnectionTemplate; + DBConfig.Instance.dephiConnection = + candidate.DelphiConnectionTemplate; + } + + private static void ValidateDatabase(ConnectionCandidate candidate) + { + if (!DBConfig.Instance.CreateConnection( + candidate.ConnectionTemplate)) + { + throw new LegacyConnectionSettingsException( + "无法连接数据库,请检查服务器和数据库配置。"); + } + } + + private static void ValidateDelphi(ConnectionCandidate candidate) + { + if (string.Equals( + candidate.ServerType, + "达梦数据库", + StringComparison.Ordinal)) + { + return; + } + + string connection = DBConfig.Instance.GetDelphiConnection( + candidate.DelphiConnectionTemplate); + if (DelphiHelper.Delphi_Init(new StringBuilder(connection)) == 0) + { + throw new LegacyConnectionSettingsException( + "数据库已连接,但 Delphi 组件初始化失败。"); + } + } + + private static void CommitDatabaseConnection() + { + DBConfig.Instance.WriteConfig(); + IniHelper.Write( + SystemResourcesIniName, + ConnectionModeKey, + "0"); + } + + private static void CommitDirectConnection( + ConnectionCandidate candidate) + { + string endpoint = candidate.DirectHost + ":" + + candidate.DirectPort; + string historyValue = candidate.AccountBookName + "^" + + candidate.DirectEncryptedPayload; + + IniHelper.Write( + SystemResourcesIniName, + DirectHistoryPrefix + endpoint, + historyValue); + IniHelper.Write( + SystemResourcesIniName, + DirectLastHostKey, + candidate.DirectHost); + IniHelper.Write( + SystemResourcesIniName, + DirectLastPortKey, + candidate.DirectPort.ToString()); + IniHelper.Write( + SystemResourcesIniName, + DirectLastNameKey, + candidate.AccountBookName); + IniHelper.Write( + SystemResourcesIniName, + ConnectionModeKey, + "1"); + + DBConfig.Instance.WriteConfig( + DirectLastHostKey, + candidate.DirectHost); + DBConfig.Instance.WriteConfig( + DirectLastPortKey, + candidate.DirectPort.ToString()); + DBConfig.Instance.WriteConfig( + DirectLastNameKey, + candidate.AccountBookName); + } + + private static Uri BuildDirectResourceUri(string host, int port) + { + host = (host ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(host)) + throw new ArgumentException("请输入直连服务器地址。"); + if (port < 1 || port > 65535) + throw new ArgumentOutOfRangeException( + "port", + "直连端口必须介于 1 和 65535 之间。"); + + if (!host.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && + !host.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + { + host = "http://" + host; + } + + Uri baseUri; + if (!Uri.TryCreate(host, UriKind.Absolute, out baseUri) || + (baseUri.Scheme != Uri.UriSchemeHttp && + baseUri.Scheme != Uri.UriSchemeHttps)) + { + throw new ArgumentException("直连服务器地址格式无效。"); + } + + var builder = new UriBuilder(baseUri) + { + Port = port, + Path = "/SystemResources.txt", + Query = string.Empty, + Fragment = string.Empty + }; + return builder.Uri; + } + + private static string DownloadDirectPayload(Uri resourceUri) + { + var request = (HttpWebRequest)WebRequest.Create(resourceUri); + request.Method = "GET"; + request.Timeout = DirectRequestTimeoutMilliseconds; + request.ReadWriteTimeout = DirectRequestTimeoutMilliseconds; + + using (var response = (HttpWebResponse)request.GetResponse()) + using (Stream stream = response.GetResponseStream()) + { + if (stream == null) + { + throw new InvalidDataException( + "直连服务器未返回连接配置。"); + } + + using (var reader = new StreamReader( + stream, + Encoding.UTF8, + true)) + { + string payload = reader.ReadToEnd().Trim(); + if (string.IsNullOrWhiteSpace(payload)) + { + throw new InvalidDataException( + "直连服务器返回的连接配置为空。"); + } + + return payload; + } + } + } + + private static string[] ParseDirectPayload(string decryptedPayload) + { + string[] parts = (decryptedPayload ?? string.Empty).Split('^'); + if (parts.Length != 5) + { + throw new InvalidDataException( + "直连服务器返回的连接配置格式无效。"); + } + + for (int index = 0; index < parts.Length; index++) + { + parts[index] = (parts[index] ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(parts[index])) + { + throw new InvalidDataException( + "直连服务器返回的连接配置不完整。"); + } + } + + return parts; + } + + private static string ToSafeConnectionError(Exception exception) + { + if (exception is ArgumentException || + exception is InvalidDataException || + exception is LegacyConnectionSettingsException) + { + return exception.Message; + } + if (exception is WebException) + { + return "无法读取直连配置,请检查地址、端口和网络连接。"; + } + + return "连接设置失败,请检查配置和网络连接。"; + } + + private static void LogSanitized(Exception exception) + { + string exceptionType = exception == null + ? "Unknown" + : exception.GetType().FullName; + LogHelper.Instance.WriteLog( + "WPF 登录连接设置失败。异常类型:" + exceptionType); + } + + private static DataTable CreateSettingsTable() + { + var table = new DataTable("Settings"); + table.Columns.Add("Mode", typeof(string)); + table.Columns.Add("ServerName", typeof(string)); + table.Columns.Add("DatabaseName", typeof(string)); + table.Columns.Add("DirectHost", typeof(string)); + table.Columns.Add("DirectPort", typeof(string)); + table.Columns.Add("DirectAccountBookName", typeof(string)); + return table; + } + + private static DataTable CreateSavedConnectionsTable() + { + var table = new DataTable("SavedDirectConnections"); + table.Columns.Add("Key", typeof(string)); + table.Columns.Add("DisplayName", typeof(string)); + table.Columns.Add("Host", typeof(string)); + table.Columns.Add("Port", typeof(int)); + return table; + } + + private static string ReadRegistryValue(string valueName) + { + try + { + using (RegistryKey key = Registry.CurrentUser.OpenSubKey( + RegistryFilePath, + false)) + { + object value = key == null + ? null + : key.GetValue(valueName, string.Empty); + return value == null ? string.Empty : value.ToString(); + } + } + catch + { + return string.Empty; + } + } + + private static bool TrySplitEndpoint( + string endpoint, + out string host, + out int port) + { + host = string.Empty; + port = 0; + if (string.IsNullOrWhiteSpace(endpoint)) + return false; + + int separatorIndex = endpoint.LastIndexOf(':'); + if (separatorIndex <= 0 || + separatorIndex >= endpoint.Length - 1) + { + return false; + } + + host = endpoint.Substring(0, separatorIndex).Trim(); + return !string.IsNullOrWhiteSpace(host) && + int.TryParse(endpoint.Substring(separatorIndex + 1), out port) && + port >= 1 && + port <= 65535; + } + + private static string ReadHistoryDisplayName(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return string.Empty; + + int separatorIndex = value.IndexOf('^'); + return separatorIndex < 0 + ? string.Empty + : value.Substring(0, separatorIndex).Trim(); + } + + private sealed class ConnectionCandidate + { + public string ServerName; + public string DatabaseName; + public string AccountBookName; + public string ServerType; + public string ConnectionTemplate; + public string DelphiConnectionTemplate; + public string DirectHost; + public int DirectPort; + public string DirectEncryptedPayload; + } + + private sealed class LegacyConnectionStateSnapshot + { + private string _serverName; + private string _databaseName; + private string _dataBook; + private string _loginName; + private string _serverType; + private string _connectionTemplate; + private string _delphiConnectionTemplate; + private string _selectedLedgerName; + private string _accountBook; + + public static LegacyConnectionStateSnapshot Capture( + LegacyLoginRuntime runtime) + { + return new LegacyConnectionStateSnapshot + { + _serverName = DBConfig.Instance.ServerName, + _databaseName = DBConfig.Instance.DataBase, + _dataBook = DBConfig.Instance.DataBook, + _loginName = DBConfig.Instance.LoginName, + _serverType = DBConfig.Instance.ServerType, + _connectionTemplate = DBConfig.Instance.Connection, + _delphiConnectionTemplate = + DBConfig.Instance.dephiConnection, + _selectedLedgerName = runtime.SelectedLedgerName, + _accountBook = ERPInfo.Instance.AccountBook + }; + } + + public void Restore(LegacyLoginRuntime runtime) + { + DBConfig.Instance.ServerName = _serverName; + DBConfig.Instance.DataBase = _databaseName; + DBConfig.Instance.DataBook = _dataBook; + DBConfig.Instance.LoginName = _loginName; + DBConfig.Instance.ServerType = _serverType; + DBConfig.Instance.Connection = _connectionTemplate; + DBConfig.Instance.dephiConnection = + _delphiConnectionTemplate; + runtime.SelectedLedgerName = _selectedLedgerName; + runtime._ledgerTable = null; + ERPInfo.Instance.AccountBook = _accountBook; + + try + { + if (!string.IsNullOrWhiteSpace(_serverName) && + !string.IsNullOrWhiteSpace(_databaseName)) + { + DBConfig.Instance.CreateConnection( + _connectionTemplate); + if (!string.Equals( + _serverType, + "达梦数据库", + StringComparison.Ordinal)) + { + DelphiHelper.Delphi_Init(new StringBuilder( + DBConfig.Instance.GetDelphiConnection( + _delphiConnectionTemplate))); + } + SystemInfo.RefreshSystemParam(); + } + } + catch (Exception restoreException) + { + LogSanitized(restoreException); + } + } + } + + private sealed class LegacyConnectionSettingsException : Exception + { + public LegacyConnectionSettingsException(string message) + : base(message) + { + } + } + } +} diff --git a/插件库/Lskj.Main/Lskj.Main.csproj b/插件库/Lskj.Main/Lskj.Main.csproj index 355288c..896046f 100644 --- a/插件库/Lskj.Main/Lskj.Main.csproj +++ b/插件库/Lskj.Main/Lskj.Main.csproj @@ -278,6 +278,7 @@ +