From fa48419bd82fa9df17a48ed9894eb84f5586f87f Mon Sep 17 00:00:00 2001 From: SugarChes Date: Tue, 28 Jul 2026 14:19:28 +0800 Subject: [PATCH] feat: expose legacy login runtime for WPF host --- .../Lskj.Main/Hosting/IExternalMainShell.cs | 21 + .../Hosting/LegacyApplicationHost.cs | 439 ++++++++++++++++++ 插件库/Lskj.Main/Lskj.Main.csproj | 2 + 插件库/Lskj.Main/Model/Manager.cs | 105 ++++- 4 files changed, 562 insertions(+), 5 deletions(-) create mode 100644 插件库/Lskj.Main/Hosting/IExternalMainShell.cs create mode 100644 插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs diff --git a/插件库/Lskj.Main/Hosting/IExternalMainShell.cs b/插件库/Lskj.Main/Hosting/IExternalMainShell.cs new file mode 100644 index 0000000..16864f7 --- /dev/null +++ b/插件库/Lskj.Main/Hosting/IExternalMainShell.cs @@ -0,0 +1,21 @@ +using System.Windows.Forms; + +namespace Lskj.Main.Hosting +{ + /// + /// 旧启动流程与可选新主界面之间的中立扩展点。 + /// 此接口只使用 .NET Framework 4.0/WinForms 基础类型,不引用 WPF 或 DevExpress 25.2。 + /// + public interface IExternalMainShell + { + bool IsOpen { get; } + + DialogResult ShowLoginDialog(object loginRuntime); + + DialogResult ShowDialog(); + + void RequestRelogin(); + + void RequestExit(string message); + } +} diff --git a/插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs b/插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs new file mode 100644 index 0000000..3bc8d05 --- /dev/null +++ b/插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs @@ -0,0 +1,439 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using DevExpress.LookAndFeel; +using Lskj.Business; +using Lskj.Business.Impl; +using Lskj.Control.Model; +using Lskj.Core; +using Lskj.Data; +using Lskj.Main.Model; +using Lskj.Model; +using Lskj.Util; + +namespace Lskj.Main.Hosting +{ + /// + /// 为独立的新启动程序公开原 Ls_ERP 初始化和登录流程。 + /// + public static class LegacyApplicationHost + { + public static void Run(string[] args, IExternalMainShell externalMainShell) + { + if (externalMainShell == null) + throw new ArgumentNullException("externalMainShell"); + if (Manager.ExternalMainShell != null) + throw new InvalidOperationException("旧程序已经由另一个外部主界面托管。"); + + Manager.ExternalMainShell = externalMainShell; + try + { + Program.RunFromExternalHost(args); + } + finally + { + Manager.ExternalMainShell = null; + } + } + } + + /// + /// 向外部 WPF 登录页提供旧账套、用户、认证和会话状态写入能力。 + /// 该类型不创建 WinForms 控件,也不引用 WPF。 + /// + public sealed class LegacyLoginRuntime + { + private DataTable _ledgerTable; + + public LegacyLoginRuntime() + { + ERPInfo.Instance.LoginResult = false; + } + + public string SelectedLedgerName { get; private set; } + + public string LoginName + { + get { return DBConfig.Instance.LoginName ?? string.Empty; } + } + + public bool RememberPasswordEnabled + { + get { return !SystemInfo.Instance.DisableRememberPassword; } + } + + public DataTable LoadLedgers() + { + _ledgerTable = MainImpl.GetLedgerList() ?? new DataTable("Ledgers"); + SelectedLedgerName = ResolveSelectedLedgerName(_ledgerTable); + return _ledgerTable; + } + + public DataTable LoadUsers() + { + DataTable users = SystemInfo.Instance.DisplayUserCode + ? MainImpl.GetEmployeeListNew() + : MainImpl.GetEmployeeList(); + return users ?? new DataTable("Users"); + } + + public DataTable SelectLedger(string ledgerName) + { + if (string.IsNullOrWhiteSpace(ledgerName)) + throw new InvalidOperationException("请选择账套。"); + if (_ledgerTable == null) + LoadLedgers(); + + DataRow ledger = _ledgerTable.Rows.Cast().FirstOrDefault( + row => string.Equals( + Value(row, "ShowName"), + ledgerName, + StringComparison.OrdinalIgnoreCase)); + if (ledger == null) + throw new InvalidOperationException("未找到所选账套,请重新选择。"); + + string serverName = Value(ledger, "IP"); + string database = Value(ledger, "DBName"); + string dataBook = Value(ledger, "ShowName"); + if (string.IsNullOrWhiteSpace(serverName) || + string.IsNullOrWhiteSpace(database)) + { + throw new InvalidOperationException(ResourceKeys.UnSetServerAddress); + } + + string oldServerName = DBConfig.Instance.ServerName; + string oldDatabase = DBConfig.Instance.DataBase; + string oldDataBook = DBConfig.Instance.DataBook; + string oldSelectedLedgerName = SelectedLedgerName; + string oldAccountBook = ERPInfo.Instance.AccountBook; + try + { + DBConfig.Instance.ServerName = serverName; + DBConfig.Instance.DataBase = database; + DBConfig.Instance.DataBook = dataBook; + + if (!DBConfig.Instance.CreateConnection(DBConfig.Instance.Connection)) + throw new InvalidOperationException(ResourceKeys.ServerAddressFault); + if (!DBConfig.Instance.ServerType.Equals("达梦数据库") && + DelphiHelper.Delphi_Init(new StringBuilder( + DBConfig.Instance.GetDelphiConnection( + DBConfig.Instance.dephiConnection))) == 0) + { + throw new InvalidOperationException( + ResourceKeys.UnConnectServer + "[By Delphi]"); + } + + SystemInfo.RefreshSystemParam(); + DataTable users = LoadUsers(); + + SelectedLedgerName = dataBook; + ERPInfo.Instance.AccountBook = dataBook; + return users; + } + catch (Exception switchException) + { + DBConfig.Instance.ServerName = oldServerName; + DBConfig.Instance.DataBase = oldDatabase; + DBConfig.Instance.DataBook = oldDataBook; + SelectedLedgerName = oldSelectedLedgerName; + ERPInfo.Instance.AccountBook = oldAccountBook; + try + { + DBConfig.Instance.CreateConnection(DBConfig.Instance.Connection); + if (!DBConfig.Instance.ServerType.Equals("达梦数据库")) + { + DelphiHelper.Delphi_Init(new StringBuilder( + DBConfig.Instance.GetDelphiConnection( + DBConfig.Instance.dephiConnection))); + } + SystemInfo.RefreshSystemParam(); + } + catch (Exception restoreException) + { + LogHelper.Instance.WriteError(restoreException); + } + + LogHelper.Instance.WriteError(switchException); + throw; + } + } + + public string LoadRememberedPassword(string loginName) + { + if (!RememberPasswordEnabled || string.IsNullOrWhiteSpace(loginName)) + return null; + + try + { + string encrypted = IniHelper.Read( + DBConfig.Instance.ServerName + loginName); + if (string.IsNullOrWhiteSpace(encrypted)) + return null; + + string[] parts = AESUtil.Decrypt(encrypted).Split('^'); + bool remember; + if (parts.Length < 2 || + !bool.TryParse(parts[0], out remember) || + !remember) + { + return null; + } + + return string.Join("^", parts.Skip(1).ToArray()); + } + catch + { + return null; + } + } + + public string Login( + string loginName, + string password, + bool rememberPassword) + { + ERPInfo.Instance.LoginResult = false; + if (string.IsNullOrWhiteSpace(loginName)) + return ResourceKeys.UserNameNotNull; + if (password == null) + password = string.Empty; + + try + { + DataTable users = MainImpl.GetEmployeeList(); + string displayColumn = ResolveDisplayColumn(users); + List matches = users.Rows.Cast() + .Where(row => + string.Equals( + Value(row, displayColumn), + loginName, + StringComparison.OrdinalIgnoreCase) || + string.Equals( + Value(row, "UserCode"), + loginName, + StringComparison.OrdinalIgnoreCase)) + .ToList(); + if (matches.Count == 0) + return ResourceKeys.NameOrPasswordError; + if (matches.Count > 1) + return ResourceKeys.DuplicatedNameError; + + DataRow user = matches[0]; + string userId = Value(user, "UserId"); + string userName = Value(user, "UserName"); + string loginAccount = Value(user, "UserCode"); + if (string.IsNullOrWhiteSpace(userId)) + return ResourceKeys.UserNotFound; + + ERPInfo.Instance.LoginResult = false; + if (SystemInfo.Instance.MacEnabled) + { + int macResult = MainImpl.CheckMacAddress( + ERPInfo.Instance.MacAddress, + userId); + MainImpl.LoginAfterRegister( + ERPInfo.Instance.MacAddress, + userId); + if (macResult == 0) + return ResourceKeys.LoginFault + ResourceKeys.UnKownMacAddress; + if (macResult == -1) + return ResourceKeys.LoginFault + ResourceKeys.UnKownMacAddressTable; + } + + string keyError = VerifyEncryptionKey(); + if (!string.IsNullOrEmpty(keyError)) + return keyError; + + if (!MainImpl.CheckingMacAddress(userId)) + return ResourceKeys.MacAddressError + + ",当前地址:" + ERPInfo.Instance.MacAddress; + + int loginResult = MainImpl.Login(userId, password, false); + if (loginResult != 0) + return LoginError(loginResult); + + CompleteLogin( + userId, + userName, + loginAccount, + loginName, + password, + rememberPassword); + ERPInfo.Instance.LoginResult = true; + LogUtil.WriteDebug("", "登录软件", "进入操作", "系统登录"); + return string.Empty; + } + catch (Exception exception) + { + ERPInfo.Instance.LoginResult = false; + LogUtil.WriteError(ResourceKeys.UserLogin, exception); + return ResourceKeys.LoginFault; + } + } + + private static string VerifyEncryptionKey() + { + if (!SystemInfo.Instance.IsEncrypt) + return string.Empty; + + int[] keyHandles = ERPInfo.Instance.keyHandles = new int[8]; + int[] keyNumbers = ERPInfo.Instance.keyNumber = new int[8]; + SmartX1Api.SmartX1Find(DBConfig.Instance.ServerName, keyHandles, keyNumbers); + + int keyHandle = keyHandles[0]; + int pin1 = Convert.ToInt32("0x987F6BCD", 16); + int pin2 = Convert.ToInt32("0xE193C5B2", 16); + int pin3 = Convert.ToInt32("0xD507CC28", 16); + int pin4 = Convert.ToInt32("0x4B125AF6", 16); + return SmartX1Api.SmartX1Open(keyHandle, pin1, pin2, pin3, pin4) == 0 + ? string.Empty + : "U盾数据获取失败,请重试或联系管理员"; + } + + private void CompleteLogin( + string userId, + string userName, + string loginAccount, + string enteredLoginName, + string password, + bool rememberPassword) + { + if (!MainImpl.HasExistsColumn("p_employeetab", "MacAdress")) + BaseImpl.ExecSqlValue("alter table p_employeetab add MacAdress varchar(100)"); + BaseImpl.ExecSqlValue(string.Format( + "update p_employeetab set MacAdress = '{0}' where employeeid='{1}'", + (ERPInfo.Instance.MacAddress ?? string.Empty).Replace("'", "''"), + userId.Replace("'", "''"))); + + if (!MainImpl.HasExistsColumn("p_employeetab", "ClientIp")) + BaseImpl.ExecSqlValue("alter table p_employeetab add ClientIp varchar(100)"); + BaseImpl.ExecSqlValue(string.Format( + "update p_employeetab set ClientIp = '{0}' where employeeid='{1}'", + (ERPInfo.Instance.LoginIPV4 ?? string.Empty).Replace("'", "''"), + userId.Replace("'", "''"))); + + if (SystemInfo.Instance.AutoSystemDataFormat) + { + FormatSystemDatetime formatter = new FormatSystemDatetime(); + formatter.SetDateTimeFormat(); + } + + string skinName = MainImpl.GetUserSkin(userId); + skinName = string.IsNullOrEmpty(skinName) + ? ERPInfo.Instance.SkinName + : skinName; + UserLookAndFeel.Default.SetSkinStyle(skinName); + + IniHelper.Write( + DBConfig.Instance.ServerName + enteredLoginName, + AESUtil.Encrypt(rememberPassword + "^" + password)); + + ERPInfo.Instance.SeriesId = SystemInfo.Instance.seriesid; + ERPInfo.Instance.UserLinkPhone = MainImpl.GetUserPhone(userId); + ERPInfo.Instance.UserId = userId; + ERPInfo.Instance.UserName = userName; + ERPInfo.Instance.Password = SHAHelper.EncryptPassword(password); + ERPInfo.Instance.SkinName = skinName; + string accountBook = string.IsNullOrWhiteSpace(SelectedLedgerName) + ? DBConfig.Instance.DataBook + : SelectedLedgerName; + ERPInfo.Instance.AccountBook = accountBook; + ERPInfo.Instance.LoginAccount = loginAccount; + ERPInfo.Instance.InPassWord = password; + ERPInfo.Instance.PrimitiveBrowser = SystemInfo.Instance.PrimitiveBrowser; + + DBConfig.Instance.LoginName = enteredLoginName; + DBConfig.Instance.NoticeUserID = userId; + DBConfig.Instance.NoticeUserName = userName; + DBConfig.Instance.DataBook = accountBook; + DBConfig.Instance.WriteConfig(); + + if (userName.Equals("管理员") && + !string.IsNullOrWhiteSpace(loginAccount)) + { + ERPInfo.Instance.Password = SqlHelper.ExecuteString( + "Password", + "select Password from P_EmployeeTab where LoginAccount='" + + loginAccount.Replace("'", "''") + "'"); + } + + ERPInfo.Instance.LanguageName = "中文"; + LanguageTranslation.GetLanguageComparisonTable(); + TrySetDelphiParameters(); + PubUtil.ClearFilesInDirectory(PubUtil.ImageDownloadPath); + } + + private static void TrySetDelphiParameters() + { + try + { + DelphiHelper.Delphi_SetParams( + Convert.ToInt32(ERPInfo.Instance.UserId), + string.IsNullOrWhiteSpace(ERPInfo.Instance.SubSysId) + ? 0 + : Convert.ToInt32(ERPInfo.Instance.SubSysId), + new StringBuilder(ERPInfo.Instance.UserName), + new StringBuilder(ERPInfo.Instance.WindowName), + new StringBuilder(ERPInfo.Instance.SkinName)); + } + catch (Exception exception) + { + LogHelper.Instance.WriteError(exception); + } + } + + private static string LoginError(int loginResult) + { + switch (loginResult) + { + case 1: + return ResourceKeys.NameOrPasswordError; + case 2: + return ResourceKeys.AlreadyLogin; + case -2: + return ResourceKeys.MultiFailedLogin; + default: + return ResourceKeys.LoginFault; + } + } + + private static string ResolveSelectedLedgerName(DataTable ledgers) + { + DataRow selected = ledgers.Rows.Cast().FirstOrDefault( + row => string.Equals( + Value(row, "DBName"), + DBConfig.Instance.DataBase, + StringComparison.OrdinalIgnoreCase)); + if (selected == null && !string.IsNullOrWhiteSpace(DBConfig.Instance.DataBook)) + { + selected = ledgers.Rows.Cast().FirstOrDefault( + row => string.Equals( + Value(row, "ShowName"), + DBConfig.Instance.DataBook, + StringComparison.OrdinalIgnoreCase)); + } + if (selected == null) + selected = ledgers.Rows.Cast().FirstOrDefault(); + + return selected == null ? string.Empty : Value(selected, "ShowName"); + } + + private static string ResolveDisplayColumn(DataTable users) + { + string configured = SystemInfo.Instance.LoginUsername; + return !string.IsNullOrWhiteSpace(configured) && + users.Columns.Contains(configured) + ? configured + : "UserName"; + } + + private static string Value(DataRow row, string columnName) + { + return row != null && row.Table.Columns.Contains(columnName) + ? row[columnName] + string.Empty + : string.Empty; + } + } +} diff --git a/插件库/Lskj.Main/Lskj.Main.csproj b/插件库/Lskj.Main/Lskj.Main.csproj index 1bc8efc..355288c 100644 --- a/插件库/Lskj.Main/Lskj.Main.csproj +++ b/插件库/Lskj.Main/Lskj.Main.csproj @@ -276,6 +276,8 @@ + + diff --git a/插件库/Lskj.Main/Model/Manager.cs b/插件库/Lskj.Main/Model/Manager.cs index de4de7b..2faece4 100644 --- a/插件库/Lskj.Main/Model/Manager.cs +++ b/插件库/Lskj.Main/Model/Manager.cs @@ -15,6 +15,7 @@ using Lskj.Control; using Lskj.Control.Model; using Lskj.Core; using Lskj.Data; +using Lskj.Main.Hosting; using Lskj.Model; using Lskj.Util; using Lskj.Web.Core.Util; @@ -55,6 +56,10 @@ namespace Lskj.Main.Model public static int isAwaitTime = 10; public static int RefreshTime = 0; /// + /// 由外部启动程序提供的可选主界面。默认为 null,旧 Ls_ERP.exe 仍打开 FrmMain。 + /// + public static IExternalMainShell ExternalMainShell { get; internal set; } + /// /// 默认下载地址 /// private static string filePath = string.Empty; @@ -180,7 +185,13 @@ namespace Lskj.Main.Model bool isOpenBs = false; DialogResult result; - if (ERPInfo.Instance.SubMenuCount > 1 && !skipMenu) + if (ExternalMainShell != null) + { + // WPF 主框架已经提供顶部子系统导航,不再打开旧 FrmSubSystem。 + // 优先保留当前仍有权限的子系统,否则选择第一个可用子系统。 + isStart = SelectExternalMainShellSubSystem(); + } + else if (ERPInfo.Instance.SubMenuCount > 1 && !skipMenu) { // 进入子系统界面 result = RunSubSystemForm(); @@ -190,8 +201,19 @@ namespace Lskj.Main.Model if (isStart) { // 直接进入主界面 - RunMainForm(); - if (ERPInfo.Instance.SubMenuCount > 1) + result = RunMainForm(); + if (result == DialogResult.Retry) + { + StartForm(); + return; + } + if (ExternalMainShell != null) + { + // 外部主界面关闭即结束本次 WPF 入口,不返回旧子系统选择页。 + ExitSystem(); + return; + } + else if (ERPInfo.Instance.SubMenuCount > 1) { ReStartMain(); } @@ -653,6 +675,9 @@ namespace Lskj.Main.Model /// DialogResult. private static DialogResult RunLoginForm() { + if (ExternalMainShell != null) + return ExternalMainShell.ShowLoginDialog(new LegacyLoginRuntime()); + FrmLogin frmLogin = new FrmLogin(); return frmLogin.ShowDialog(); } @@ -674,6 +699,50 @@ namespace Lskj.Main.Model BsUrl = _frmSubSystem.BsUrl; return dialogResult; } + + /// + /// 为外部 WPF 主框架选择登录后的初始子系统。 + /// 旧 WinForms 入口仍由 FrmSubSystem 完成人工选择。 + /// + private static bool SelectExternalMainShellSubSystem() + { + string where = string.IsNullOrEmpty(ERPInfo.Instance.SeriesId) + ? string.Empty + : string.Format("and SeriesId={0}", ERPInfo.Instance.SeriesId); + DataTable subSystems = MainImpl.GetSubSystems(where); + if (subSystems == null || subSystems.Rows.Count == 0) + { + MessageUtil.Show(ResourceKeys.SubSystemUnEnabled); + return false; + } + + DataRow selectedRow = subSystems.AsEnumerable().FirstOrDefault(row => + IsEnabledSubSystem(row) && + string.Equals( + row["SubSysId"] + string.Empty, + ERPInfo.Instance.SubSysId, + StringComparison.OrdinalIgnoreCase)); + if (selectedRow == null) + selectedRow = subSystems.AsEnumerable().FirstOrDefault(IsEnabledSubSystem); + if (selectedRow == null) + { + MessageUtil.Show(ResourceKeys.SubSystemUnEnabled); + return false; + } + + ERPInfo.Instance.SubMenuCount = subSystems.AsEnumerable().Count(IsEnabledSubSystem); + ERPInfo.Instance.SubSysId = selectedRow["SubSysId"] + string.Empty; + ERPInfo.Instance.SubSysName = selectedRow["SubSysName"] + string.Empty; + return true; + } + + private static bool IsEnabledSubSystem(DataRow row) + { + return row != null && + row.Table.Columns.Contains("UseEd") && + row["UseEd"] != DBNull.Value && + Convert.ToBoolean(row["UseEd"]); + } /// /// 说明:打开主程序 /// 创建人:龚宇超 @@ -686,6 +755,10 @@ namespace Lskj.Main.Model /// DialogResult. private static DialogResult RunMainForm() { + IExternalMainShell externalMainShell = ExternalMainShell; + if (externalMainShell != null) + return externalMainShell.ShowDialog(); + _frmMain = new FrmMain(); return _frmMain.ShowDialog(); } @@ -909,6 +982,11 @@ namespace Lskj.Main.Model } })); } + else if (ExternalMainShell != null && ExternalMainShell.IsOpen) + { + ExternalMainShell.RequestExit($"当前用户在另一电脑登录\r\nClientIp:{clientip}\r\nMacAdress:{loginMacAdress}\r\n当前系统即将关闭"); + isClose = true; + } else if (_frmMain != null && _frmMain.Visible == true) { _frmMain.Invoke(new Action(() => @@ -953,7 +1031,7 @@ namespace Lskj.Main.Model } //记录登出日志 LogUtil.WriteDebug("", "退出软件", "软件退出", "系统登录"); - Process[] p = Process.GetProcessesByName("Ls_ERP"); + Process[] p = GetApplicationProcesses(); foreach (Process item in p) { try @@ -1107,6 +1185,12 @@ namespace Lskj.Main.Model /// public static void ReLogin() { + IExternalMainShell externalMainShell = ExternalMainShell; + if (externalMainShell != null && externalMainShell.IsOpen) + { + externalMainShell.RequestRelogin(); + return; + } if (_frmMain != null) { if (ERPInfo.Instance.WatermarkForm != null) @@ -1121,5 +1205,16 @@ namespace Lskj.Main.Model StartForm(); } + /// + /// 返回当前启动路线需要退出的主进程。 + /// 旧入口保持原有 Ls_ERP 进程名语义,外部宿主只退出当前 WPF 进程。 + /// + internal static Process[] GetApplicationProcesses() + { + return ExternalMainShell == null + ? Process.GetProcessesByName("Ls_ERP") + : new[] { Process.GetCurrentProcess() }; + } + } -} \ No newline at end of file +}