From d60bb6116c698269d405fce3040bdc75128c6820 Mon Sep 17 00:00:00 2001
From: chenluhua1980 <Chenluhua@qq.com>
Date: 星期二, 18 十一月 2025 10:57:21 +0800
Subject: [PATCH] 1.用户管理相关;

---
 SourceCode/Bond/Servo/LoginDlg2.cpp         |   98 +++++++++
 SourceCode/Bond/Servo/Servo.vcxproj         |    6 
 .gitignore                                  |    1 
 SourceCode/Bond/Servo/resource.h            |    0 
 SourceCode/Bond/Servo/CUserManager2Dlg.h    |   26 ++
 SourceCode/Bond/Servo/CUserManager2.cpp     |  100 ++++++++++
 SourceCode/Bond/Servo/Servo.cpp             |    8 
 SourceCode/Bond/Servo/stdafx.h              |    2 
 SourceCode/Bond/Servo/ServoDlg.cpp          |   55 +++--
 SourceCode/Bond/Servo/Servo.vcxproj.filters |    8 
 SourceCode/Bond/Servo/CUserManager2Dlg.cpp  |   53 +++++
 SourceCode/Bond/Servo/LoginDlg2.h           |   30 +++
 SourceCode/Bond/Servo/ToolUnits.cpp         |   40 ++++
 SourceCode/Bond/Servo/Servo.rc              |    0 
 SourceCode/Bond/USERXLibrary/UserXAPI.h     |  136 +++++++++++++
 SourceCode/Bond/Servo/CUserManager2.h       |   20 ++
 SourceCode/Bond/Servo/ToolUnits.h           |    2 
 17 files changed, 564 insertions(+), 21 deletions(-)

diff --git a/.gitignore b/.gitignore
index e97c776..b3c6aac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,3 +63,4 @@
 SourceCode/Bond/x64/Debug/HsmsPassive.cache
 SourceCode/Bond/x64/Debug/MasterState.dat
 SourceCode/Bond/x64/Debug/Recipe/EQ10_Unit0.recipelist
+SourceCode/Bond/UserX/
diff --git a/SourceCode/Bond/Servo/CUserManager2.cpp b/SourceCode/Bond/Servo/CUserManager2.cpp
new file mode 100644
index 0000000..e06165f
--- /dev/null
+++ b/SourceCode/Bond/Servo/CUserManager2.cpp
@@ -0,0 +1,100 @@
+#include "stdafx.h"
+#include "CUserManager2.h"
+#include "ToolUnits.h"
+#include <sstream>
+#include <cwchar>
+
+std::vector<std::wstring> SplitLines(const std::wstring& text) 
+{
+	std::wstringstream ss(text); std::vector<std::wstring> v; std::wstring line; while (std::getline(ss, line)) v.push_back(line); return v;
+}
+
+template<typename Fn>
+std::wstring ReadBufferVia(Fn fn)
+{
+	int need = fn(nullptr, 0); if (need <= 0) return L"";
+	std::wstring buf; buf.resize((size_t)need);
+	int rc = fn(buf.data(), need);
+	if (rc == 0) { if (!buf.empty() && buf.back() == L'\0') buf.pop_back(); return buf; }
+	return L"";
+}
+
+// 获取单例实例
+CUserManager2& CUserManager2::getInstance() {
+	static CUserManager2 instance;
+	return instance;
+}
+
+CUserManager2::CUserManager2()
+{
+
+}
+
+CUserManager2::~CUserManager2()
+{
+
+}
+
+void CUserManager2::init(const char* pszDir)
+{
+	std::wstring dir = CToolUnits::AnsiToWString(std::string(pszDir));
+	UX_Init(dir.c_str());
+
+	wchar_t buffer[1024];
+	UX_GetUsers(buffer, 1024);
+	bool hasAny = false;
+	for (auto& ln : SplitLines(buffer)) { if (!ln.empty()) { hasAny = true; break; } }
+	if (!hasAny) {
+		const wchar_t* roles = L"Admin:100\nEngineer:50\nOperator:10\n";
+		(void)UX_SetRoleDefinitions(roles);
+		(void)UX_AddUser(L"admin", L"Administrator", L"admin123", L"Admin");
+	}
+}
+
+bool CUserManager2::login(const char* pszAccount, const char* pszPwd)
+{
+	std::wstring strUser, strPwd;
+	strUser = CToolUnits::AnsiToWString(std::string(pszAccount));
+	strPwd = CToolUnits::AnsiToWString(std::string(pszPwd));
+	int rc = UX_Login(strUser.c_str(), strPwd.c_str());
+	return rc == UX_OK;
+}
+
+bool CUserManager2::isLoggedIn()
+{
+	return UX_IsLoggedIn();
+}
+
+std::string CUserManager2::getCurrentUserName()
+{
+	std::string strName;
+
+	int need = UX_GetCurrentUser(nullptr, 0);
+	std::wstring buf; buf.resize((size_t)need);
+	if (UX_GetCurrentUser(buf.data(), need) == UX_OK) { 
+		if (!buf.empty() && buf.back() == L'\0')
+			buf.pop_back(); 
+
+		strName = CToolUnits::WStringToAnsi(buf);
+	}
+
+	return strName;
+}
+
+bool CUserManager2::IsAdminCurrent() 
+{
+	if (UX_IsLoggedIn() != 1) return false;
+	int need = UX_GetCurrentUser(nullptr, 0); if (need <= 0) return false;
+	std::wstring user; user.resize((size_t)need);
+	if (UX_GetCurrentUser(user.data(), need) != 0) return false;
+	if (!user.empty() && user.back() == L'\0') user.pop_back();
+	int maxLvl = 0; auto rolesTxt = ReadBufferVia([](wchar_t* b, int n) { return UX_GetRoles(b, n); });
+	for (auto& ln : SplitLines(rolesTxt)) { size_t p = ln.find(L':'); if (p != std::wstring::npos) { int lvl = _wtoi(ln.substr(p + 1).c_str()); if (lvl > maxLvl) maxLvl = lvl; } }
+	int myLvl = 0; auto usersTxt = ReadBufferVia([](wchar_t* b, int n) { return UX_GetUsers(b, n); });
+	for (auto& ln : SplitLines(usersTxt)) {
+		if (ln.empty()) continue; size_t p1 = ln.find(L','), p2 = ln.find(L',', p1 == std::wstring::npos ? 0 : p1 + 1), p3 = ln.find(L',', p2 == std::wstring::npos ? 0 : p2 + 1);
+		std::wstring name = (p1 == std::wstring::npos ? ln : ln.substr(0, p1)); if (name == user) { if (p2 != std::wstring::npos) { std::wstring lvlS = ln.substr(p2 + 1, (p3 == std::wstring::npos ? ln.size() : p3) - (p2 + 1)); myLvl = _wtoi(lvlS.c_str()); } break; }
+	}
+
+	return (maxLvl > 0) && (myLvl >= maxLvl);
+}
diff --git a/SourceCode/Bond/Servo/CUserManager2.h b/SourceCode/Bond/Servo/CUserManager2.h
new file mode 100644
index 0000000..7430b00
--- /dev/null
+++ b/SourceCode/Bond/Servo/CUserManager2.h
@@ -0,0 +1,20 @@
+#pragma once
+class CUserManager2
+{
+public:
+	static CUserManager2& getInstance();
+	CUserManager2(const CUserManager2&) = delete;
+	CUserManager2& operator=(const CUserManager2&) = delete;
+
+public:
+	void init(const char* pszDir);
+	bool login(const char* pszAccount, const char* pszPwd);
+	bool isLoggedIn();
+	std::string getCurrentUserName();
+	bool IsAdminCurrent();
+
+private:
+	CUserManager2();
+	~CUserManager2();
+};
+
diff --git a/SourceCode/Bond/Servo/CUserManager2Dlg.cpp b/SourceCode/Bond/Servo/CUserManager2Dlg.cpp
new file mode 100644
index 0000000..7d47ecd
--- /dev/null
+++ b/SourceCode/Bond/Servo/CUserManager2Dlg.cpp
@@ -0,0 +1,53 @@
+锘�// CUserManager2Dlg.cpp: 瀹炵幇鏂囦欢
+//
+
+#include "stdafx.h"
+#include "Servo.h"
+#include "CUserManager2Dlg.h"
+#include "afxdialogex.h"
+
+
+// CUserManager2Dlg 瀵硅瘽妗�
+
+IMPLEMENT_DYNAMIC(CUserManager2Dlg, CDialogEx)
+
+CUserManager2Dlg::CUserManager2Dlg(CWnd* pParent /*=nullptr*/)
+	: CDialogEx(IDD_DIALOG_USER_MANAGER2, pParent)
+{
+
+}
+
+CUserManager2Dlg::~CUserManager2Dlg()
+{
+}
+
+void CUserManager2Dlg::DoDataExchange(CDataExchange* pDX)
+{
+	CDialogEx::DoDataExchange(pDX);
+}
+
+
+BEGIN_MESSAGE_MAP(CUserManager2Dlg, CDialogEx)
+	ON_WM_SIZE()
+END_MESSAGE_MAP()
+
+
+// CUserManager2Dlg 娑堟伅澶勭悊绋嬪簭
+
+
+BOOL CUserManager2Dlg::OnInitDialog()
+{
+	CDialogEx::OnInitDialog();
+
+	// TODO:  鍦ㄦ娣诲姞棰濆鐨勫垵濮嬪寲
+
+	return TRUE;  // return TRUE unless you set the focus to a control
+				  // 寮傚父: OCX 灞炴�ч〉搴旇繑鍥� FALSE
+}
+
+void CUserManager2Dlg::OnSize(UINT nType, int cx, int cy)
+{
+	CDialogEx::OnSize(nType, cx, cy);
+
+	// TODO: 鍦ㄦ澶勬坊鍔犳秷鎭鐞嗙▼搴忎唬鐮�
+}
diff --git a/SourceCode/Bond/Servo/CUserManager2Dlg.h b/SourceCode/Bond/Servo/CUserManager2Dlg.h
new file mode 100644
index 0000000..5085a9e
--- /dev/null
+++ b/SourceCode/Bond/Servo/CUserManager2Dlg.h
@@ -0,0 +1,26 @@
+锘�#pragma once
+
+
+// CUserManager2Dlg 瀵硅瘽妗�
+
+class CUserManager2Dlg : public CDialogEx
+{
+	DECLARE_DYNAMIC(CUserManager2Dlg)
+
+public:
+	CUserManager2Dlg(CWnd* pParent = nullptr);   // 鏍囧噯鏋勯�犲嚱鏁�
+	virtual ~CUserManager2Dlg();
+
+// 瀵硅瘽妗嗘暟鎹�
+#ifdef AFX_DESIGN_TIME
+	enum { IDD = IDD_DIALOG_USER_MANAGER2 };
+#endif
+
+protected:
+	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 鏀寔
+
+	DECLARE_MESSAGE_MAP()
+public:
+	virtual BOOL OnInitDialog();
+	afx_msg void OnSize(UINT nType, int cx, int cy);
+};
diff --git a/SourceCode/Bond/Servo/LoginDlg2.cpp b/SourceCode/Bond/Servo/LoginDlg2.cpp
new file mode 100644
index 0000000..ad613a9
--- /dev/null
+++ b/SourceCode/Bond/Servo/LoginDlg2.cpp
@@ -0,0 +1,98 @@
+锘�// LoginDlg.cpp: 瀹炵幇鏂囦欢
+//
+
+#include "stdafx.h"
+#include "Servo.h"
+#include "afxdialogex.h"
+#include "LoginDlg2.h"
+
+
+// CLoginDlg 瀵硅瘽妗�
+
+IMPLEMENT_DYNAMIC(CLoginDlg2, CDialogEx)
+
+CLoginDlg2::CLoginDlg2(CWnd* pParent /*=nullptr*/)
+	: CDialogEx(IDD_DIALOG_LOGIN, pParent)
+{
+}
+
+CLoginDlg2::~CLoginDlg2()
+{
+}
+
+void CLoginDlg2::DoDataExchange(CDataExchange* pDX)
+{
+    CDialogEx::DoDataExchange(pDX);
+}
+
+
+BEGIN_MESSAGE_MAP(CLoginDlg2, CDialogEx)
+	ON_BN_CLICKED(IDC_BUTTON_LOGIN, &CLoginDlg2::OnBnClickedLogin)
+    ON_STN_CLICKED(IDC_STATIC_CHANGE_PASSWORD, &CLoginDlg2::OnBnClickedChangePassword)
+END_MESSAGE_MAP()
+
+
+// CLoginDlg 娑堟伅澶勭悊绋嬪簭
+
+
+BOOL CLoginDlg2::OnInitDialog()
+{
+    CDialog::OnInitDialog();
+
+    // 璁剧疆绐楀彛鏍囬鍜屽垵濮嬪��
+    SetWindowText(_T("鐧诲綍"));
+
+
+    CStatic* pStaticImage = (CStatic*)GetDlgItem(IDC_STATIC_IMAGE);
+    ASSERT(pStaticImage);
+
+    CString strIconPath;
+    strIconPath.Format(_T("%s\\Res\\Operator_High_32.ico"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
+    HICON hIcon = (HICON)::LoadImage(
+        nullptr,
+        strIconPath,
+        IMAGE_ICON,
+        32, // 鍥炬爣瀹藉害
+        32, // 鍥炬爣楂樺害
+        LR_LOADFROMFILE);
+    if (hIcon) {
+        // 璁剧疆 CStatic 鎺т欢涓哄浘鏍囨牱寮�
+        pStaticImage->ModifyStyle(0xF, SS_ICON);
+        pStaticImage->SetIcon(hIcon);
+    }
+
+    // 娣诲姞SS_NOTIFY鏍峰紡
+    CStatic* pStatic = (CStatic*)GetDlgItem(IDC_STATIC_CHANGE_PASSWORD);
+    if (pStatic != nullptr) {
+        pStatic->ModifyStyle(0, SS_NOTIFY);
+    }
+
+    GetDlgItem(IDC_CHECK_REMEMBER_PASSWORD)->ShowWindow(SW_HIDE);
+
+    return TRUE;
+}
+
+void CLoginDlg2::OnBnClickedLogin()
+{
+    GetDlgItemText(IDC_EDIT_USERNAME, m_strUsername);
+    GetDlgItemText(IDC_EDIT_PASSWORD, m_strPassword);
+
+    if (m_strUsername.IsEmpty()) {
+        AfxMessageBox(_T("璇疯緭鍏ョ敤鎴峰悕"));
+        GetDlgItem(IDC_EDIT_USERNAME)->SetFocus();
+        return;
+    }
+    if (m_strPassword.IsEmpty()) {
+        AfxMessageBox(_T("璇疯緭鍏ュ瘑鐮�"));
+        GetDlgItem(IDC_EDIT_PASSWORD)->SetFocus();
+        return;
+}
+
+
+    EndDialog(IDOK);
+}
+
+void CLoginDlg2::OnBnClickedChangePassword()
+{
+
+}
\ No newline at end of file
diff --git a/SourceCode/Bond/Servo/LoginDlg2.h b/SourceCode/Bond/Servo/LoginDlg2.h
new file mode 100644
index 0000000..335037d
--- /dev/null
+++ b/SourceCode/Bond/Servo/LoginDlg2.h
@@ -0,0 +1,30 @@
+锘�#pragma once
+#include "afxdialogex.h"
+
+
+// CLoginDlg 瀵硅瘽妗�
+
+class CLoginDlg2 : public CDialogEx
+{
+	DECLARE_DYNAMIC(CLoginDlg2)
+
+public:
+	CLoginDlg2(CWnd* pParent = nullptr);   // 鏍囧噯鏋勯�犲嚱鏁�
+	virtual ~CLoginDlg2();
+
+// 瀵硅瘽妗嗘暟鎹�
+#ifdef AFX_DESIGN_TIME
+	enum { IDD = IDD_DIALOG_LOGIN };
+#endif
+
+public:
+	CString m_strUsername;
+	CString m_strPassword;
+
+protected:
+	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 鏀寔
+	virtual BOOL OnInitDialog();
+	afx_msg void OnBnClickedLogin();
+	afx_msg void OnBnClickedChangePassword();
+	DECLARE_MESSAGE_MAP()
+};
diff --git a/SourceCode/Bond/Servo/Servo.cpp b/SourceCode/Bond/Servo/Servo.cpp
index 60c2b8a..f4f67b4 100644
--- a/SourceCode/Bond/Servo/Servo.cpp
+++ b/SourceCode/Bond/Servo/Servo.cpp
@@ -17,6 +17,8 @@
 #include "MapPosWnd.h"
 #include "HmTab.h"
 #include "CControlJobManagerDlg.h"
+#include "ToolUnits.h"
+#include "CUserManager2.h"
 
 
 // 澹版槑鍏ㄥ眬鍙橀噺锛岀敤浜庣鐞� GDI+ 鍒濆鍖�
@@ -100,6 +102,11 @@
 	m_strAppDir = CString(sDrive) + CString(sDir);
 	m_strAppFile = CString(sFilename);
 	m_model.setWorkDir((LPTSTR)(LPCTSTR)m_strAppDir);
+
+
+	// 鐢ㄦ埛鏁版嵁搴撶鐞�
+	CString strDir = m_strAppDir + _T("\\DB");
+	CUserManager2::getInstance().init((LPTSTR)(LPCTSTR)strDir);
 
 
 	// 娉ㄥ唽鎺т欢
@@ -233,6 +240,7 @@
 	m_model.term();
 	HSMS_Term();
 	RX_Term();
+	UX_Shutdown();
 
 	// 娓呯悊 GDI+
 	TermGDIPlus();
diff --git a/SourceCode/Bond/Servo/Servo.rc b/SourceCode/Bond/Servo/Servo.rc
index 5188bbb..aadc145 100644
--- a/SourceCode/Bond/Servo/Servo.rc
+++ b/SourceCode/Bond/Servo/Servo.rc
Binary files differ
diff --git a/SourceCode/Bond/Servo/Servo.vcxproj b/SourceCode/Bond/Servo/Servo.vcxproj
index 2e09825..31c7ea3 100644
--- a/SourceCode/Bond/Servo/Servo.vcxproj
+++ b/SourceCode/Bond/Servo/Servo.vcxproj
@@ -255,6 +255,8 @@
     <ClInclude Include="CRobotTaskDlg.h" />
     <ClInclude Include="CSVData.h" />
     <ClInclude Include="CServoUtilsTool.h" />
+    <ClInclude Include="CUserManager2.h" />
+    <ClInclude Include="CUserManager2Dlg.h" />
     <ClInclude Include="CVariable.h" />
     <ClInclude Include="DeviceRecipeParamDlg.h" />
     <ClInclude Include="GlassJson.h" />
@@ -280,6 +282,7 @@
     <ClInclude Include="InputDialog.h" />
     <ClInclude Include="JobSlotGrid.h" />
     <ClInclude Include="LoginDlg.h" />
+    <ClInclude Include="LoginDlg2.h" />
     <ClInclude Include="MsgDlg.h" />
     <ClInclude Include="PageRecipe.h" />
     <ClInclude Include="CDoubleGlass.h" />
@@ -468,6 +471,8 @@
     <ClCompile Include="CRobotTaskDlg.cpp" />
     <ClCompile Include="CSVData.cpp" />
     <ClCompile Include="CServoUtilsTool.cpp" />
+    <ClCompile Include="CUserManager2.cpp" />
+    <ClCompile Include="CUserManager2Dlg.cpp" />
     <ClCompile Include="CVariable.cpp" />
     <ClCompile Include="DeviceRecipeParamDlg.cpp" />
     <ClCompile Include="GlassJson.cpp" />
@@ -491,6 +496,7 @@
     <ClCompile Include="InputDialog.cpp" />
     <ClCompile Include="JobSlotGrid.cpp" />
     <ClCompile Include="LoginDlg.cpp" />
+    <ClCompile Include="LoginDlg2.cpp" />
     <ClCompile Include="MsgDlg.cpp" />
     <ClCompile Include="PageRecipe.cpp" />
     <ClCompile Include="CDoubleGlass.cpp" />
diff --git a/SourceCode/Bond/Servo/Servo.vcxproj.filters b/SourceCode/Bond/Servo/Servo.vcxproj.filters
index 8c439f7..a0fd9b9 100644
--- a/SourceCode/Bond/Servo/Servo.vcxproj.filters
+++ b/SourceCode/Bond/Servo/Servo.vcxproj.filters
@@ -227,6 +227,10 @@
     <ClCompile Include="..\DAQBridge\proto\ProtocolCodec.cpp">
       <Filter>DAQBridge</Filter>
     </ClCompile>
+    <ClCompile Include="ClientListDlg.cpp" />
+    <ClCompile Include="LoginDlg2.cpp" />
+    <ClCompile Include="CUserManager2.cpp" />
+    <ClCompile Include="CUserManager2Dlg.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="AlarmManager.h" />
@@ -497,6 +501,10 @@
     <ClInclude Include="..\DAQBridge\DAQConfig.h">
       <Filter>DAQBridge</Filter>
     </ClInclude>
+    <ClInclude Include="ClientListDlg.h" />
+    <ClInclude Include="LoginDlg2.h" />
+    <ClInclude Include="CUserManager2.h" />
+    <ClInclude Include="CUserManager2Dlg.h" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="Servo.rc" />
diff --git a/SourceCode/Bond/Servo/ServoDlg.cpp b/SourceCode/Bond/Servo/ServoDlg.cpp
index 8eaf56f..dd3206c 100644
--- a/SourceCode/Bond/Servo/ServoDlg.cpp
+++ b/SourceCode/Bond/Servo/ServoDlg.cpp
@@ -16,6 +16,7 @@
 #include "CRobotCmdContainerDlg.h"
 #include "CRobotCmdTestDlg.h"
 #include "LoginDlg.h"
+#include "LoginDlg2.h"
 #include "ChangePasswordDlg.h"
 #include "UserManagerDlg.h"
 #include "SystemLogManagerDlg.h"
@@ -30,6 +31,8 @@
 #include "InputDialog.h"
 #include "ClientListDlg.h"
 #include "CControlJobManagerDlg.h"
+#include "CUserManager2.h"
+#include "CUserManager2Dlg.h"
 
 
 #ifdef _DEBUG
@@ -44,7 +47,7 @@
 #define TIMER_ID_UPDATE_RUMTIME			2
 
 /* Test */
-#define TIMER_ID_TEST					3
+#define TIMER_ID_LOGIN					3
 
 
 // 鐢ㄤ簬搴旂敤绋嬪簭鈥滃叧浜庘�濊彍鍗曢」鐨� CAboutDlg 瀵硅瘽妗�
@@ -197,7 +200,7 @@
 			else if (RX_CODE_MASTER_STATE_CHANGED == code) {
 				SERVO::MASTERSTATE state = theApp.m_model.getMaster().getState();
 				if (state == SERVO::MASTERSTATE::READY) {
-					m_pTopToolbar->GetBtn(IDC_BUTTON_RUN)->EnableWindow(TRUE);
+					m_pTopToolbar ->GetBtn(IDC_BUTTON_RUN)->EnableWindow(TRUE);
 					m_pTopToolbar->GetBtn(IDC_BUTTON_RUN_BATCH)->EnableWindow(TRUE);
 					m_pTopToolbar->GetBtn(IDC_BUTTON_RUN_CT)->EnableWindow(TRUE);
 					m_pTopToolbar->GetBtn(IDC_BUTTON_STOP)->EnableWindow(FALSE);
@@ -368,7 +371,7 @@
 
 	// model init
 	theApp.m_model.init();
-	SetTimer(TIMER_ID_TEST, 1000, nullptr);
+	SetTimer(TIMER_ID_LOGIN, 1000, nullptr);
 
 	// 鑿滃崟
 	CMenu menu;
@@ -935,11 +938,23 @@
 		m_pMyStatusbar->setRunTimeText((LPTSTR)(LPCTSTR)strText);
 	}
 
-	else if(TIMER_ID_TEST == nIDEvent){
-		static __int64 tttt = 0;
-		tttt++;
-		theApp.m_model.m_hsmsPassive.setVariableValue("CJobSpace", tttt % 10);
-		theApp.m_model.m_hsmsPassive.setVariableValue("PJobSpace", tttt % 5);
+	else if(TIMER_ID_LOGIN == nIDEvent){
+		KillTimer(TIMER_ID_LOGIN);
+		if (!CUserManager2::getInstance().isLoggedIn()) {
+			CLoginDlg2 dlg;
+			if (dlg.DoModal() != IDOK) {
+				PostMessage(WM_CLOSE);
+			}
+			else {
+				bool bRet = CUserManager2::getInstance().login((LPTSTR)(LPCTSTR)dlg.m_strUsername,
+					(LPTSTR)(LPCTSTR)dlg.m_strPassword);
+				if (!bRet) {
+					AfxMessageBox("鐧诲綍澶辫触锛岃妫�鏌ョ敤鎴峰悕鎴栧瘑鐮佹槸鍚︽纭紒");
+					PostMessage(WM_CLOSE);
+				}
+				UpdateLoginStatus();
+			}
+		}
 	}
 
 
@@ -977,32 +992,24 @@
 void CServoDlg::UpdateLoginStatus()
 {
 	HMENU hMenu = m_pTopToolbar->GetOperatorMenu();
-	UserManager& userManager = UserManager::getInstance();
-	if (userManager.isLoggedIn())
-	{
-		::EnableMenuItem(hMenu, ID_OPEATOR_LOGIN, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
-		::EnableMenuItem(hMenu, ID_OPERATOR_CHANGE_PASSWORD, MF_BYCOMMAND | MF_ENABLED);
+	CUserManager2& userManager = CUserManager2::getInstance();
+	if (userManager.isLoggedIn()) {
 		::EnableMenuItem(hMenu, ID_OPERATOR_SYSTEM_LOG, MF_BYCOMMAND | MF_ENABLED);
 		::EnableMenuItem(hMenu, ID_OPEATOR_SWITCH, MF_BYCOMMAND | MF_ENABLED);
-		::EnableMenuItem(hMenu, ID_OPERATOR_LOGOUT, MF_BYCOMMAND | MF_ENABLED);
 
-		if (userManager.getCurrentUserRole() == UserRole::SuperAdmin) {
+		if (userManager.IsAdminCurrent()) {
 			::EnableMenuItem(hMenu, ID_OPEATOR_USER_MANAGER, MF_BYCOMMAND | MF_ENABLED);
 		}
 		else {
 			::EnableMenuItem(hMenu, ID_OPEATOR_USER_MANAGER, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
 		}
 
-		m_pTopToolbar->SetOperatorBtnText(userManager.getCurrentUser().c_str());
+		m_pTopToolbar->SetOperatorBtnText(userManager.getCurrentUserName().c_str());
 	}
 	else {
-		::EnableMenuItem(hMenu, ID_OPEATOR_LOGIN, MF_BYCOMMAND | MF_ENABLED);
-		::EnableMenuItem(hMenu, ID_OPERATOR_CHANGE_PASSWORD, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
 		::EnableMenuItem(hMenu, ID_OPEATOR_USER_MANAGER, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
 		::EnableMenuItem(hMenu, ID_OPERATOR_SYSTEM_LOG, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
 		::EnableMenuItem(hMenu, ID_OPEATOR_SWITCH, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
-		::EnableMenuItem(hMenu, ID_OPERATOR_LOGOUT, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
-
 		m_pTopToolbar->SetOperatorBtnText(_T("鏈櫥褰�"));
 	}
 }
@@ -1090,6 +1097,13 @@
 	}
 	else if (id == IDC_BUTTON_OPERATOR) {
 		int menuId = (int)wParam;
+		if (menuId == 0) {
+			CUserManager2Dlg dlg;
+			dlg.DoModal();
+		}
+
+
+		/*
 		SystemLogManager& logManager = SystemLogManager::getInstance();
 		UserManager& userManager = UserManager::getInstance();
 		if (menuId == 0) {
@@ -1138,6 +1152,7 @@
 		}
 
 		UpdateLoginStatus();
+		*/
 	}
 
 	return 0;
diff --git a/SourceCode/Bond/Servo/ToolUnits.cpp b/SourceCode/Bond/Servo/ToolUnits.cpp
index 6d5df0d..974121c 100644
--- a/SourceCode/Bond/Servo/ToolUnits.cpp
+++ b/SourceCode/Bond/Servo/ToolUnits.cpp
@@ -574,4 +574,44 @@
 	std::ostringstream oss;
 	oss << std::put_time(&tm, "%Y%m%d%H%M%S"); // 例:2025-09-15 08:23:07
 	return oss.str();
+}
+
+std::wstring CToolUnits::AnsiToWString(const std::string& str)
+{
+	if (str.empty()) return std::wstring();
+
+	int len = ::MultiByteToWideChar(CP_ACP, 0,
+		str.c_str(), -1,
+		nullptr, 0);
+	if (len <= 0) return std::wstring();
+
+	std::wstring ws;
+	ws.resize(len - 1);
+
+	::MultiByteToWideChar(CP_ACP, 0,
+		str.c_str(), -1,
+		&ws[0], len);
+
+	return ws;
+}
+
+std::string CToolUnits::WStringToAnsi(const std::wstring& wstr)
+{
+	if (wstr.empty()) return std::string();
+
+	int len = ::WideCharToMultiByte(CP_ACP, 0,
+		wstr.c_str(), -1,
+		nullptr, 0,
+		nullptr, nullptr);
+	if (len <= 0) return std::string();
+
+	std::string str;
+	str.resize(len - 1);
+
+	::WideCharToMultiByte(CP_ACP, 0,
+		wstr.c_str(), -1,
+		&str[0], len,
+		nullptr, nullptr);
+
+	return str;
 }
\ No newline at end of file
diff --git a/SourceCode/Bond/Servo/ToolUnits.h b/SourceCode/Bond/Servo/ToolUnits.h
index f88788d..7314ea8 100644
--- a/SourceCode/Bond/Servo/ToolUnits.h
+++ b/SourceCode/Bond/Servo/ToolUnits.h
@@ -60,5 +60,7 @@
 		const char* fmt = "%Y-%m-%d %H:%M:%S");
 	static std::string TimePointToLocalStringMs(const std::optional<TP>& tp);
 	static std::string NowStrSec();
+	static std::wstring AnsiToWString(const std::string& str);
+	static std::string WStringToAnsi(const std::wstring& wstr);
 };
 
diff --git a/SourceCode/Bond/Servo/resource.h b/SourceCode/Bond/Servo/resource.h
index 0a875a6..4cf1ef2 100644
--- a/SourceCode/Bond/Servo/resource.h
+++ b/SourceCode/Bond/Servo/resource.h
Binary files differ
diff --git a/SourceCode/Bond/Servo/stdafx.h b/SourceCode/Bond/Servo/stdafx.h
index 8e4739a..36ba555 100644
--- a/SourceCode/Bond/Servo/stdafx.h
+++ b/SourceCode/Bond/Servo/stdafx.h
@@ -75,7 +75,7 @@
 
 #include "..\RxWindows1.0\include\RxWindowsLib.h"
 #include "..\HSMSSDK\Include\HSMSSDK.h"
-
+#include "..\UserXLibrary\UserXAPI.h"
 
 
 #ifdef _UNICODE
diff --git a/SourceCode/Bond/USERXLibrary/UserXAPI.h b/SourceCode/Bond/USERXLibrary/UserXAPI.h
new file mode 100644
index 0000000..94e141a
--- /dev/null
+++ b/SourceCode/Bond/USERXLibrary/UserXAPI.h
@@ -0,0 +1,136 @@
+// UserX 用户管理库(C 接口)/ UserX user management library (C API)
+// 覆盖功能 / Features: users, roles, actions, auth, logs(固定 SQLite3 持久化 / SQLite3 persistence)
+
+#pragma once
+
+#if defined(_WIN32)
+#  ifdef USERXLIBRARY_EXPORTS
+#    define UX_API extern "C" __declspec(dllexport)
+#  else
+#    define UX_API extern "C" __declspec(dllimport)
+#  endif
+#else
+#  define UX_API extern "C"
+#endif
+
+#include <wchar.h>
+
+
+#ifdef _COMPILE_AS_LIB
+#warning "compiling as lib!"
+#else
+#ifdef _DEBUG
+#ifndef _WIN64
+#pragma comment(lib, "../USERXLibrary/lib/Win32/Debug/UserXLibrary.lib")
+#else 
+#pragma comment(lib, "../USERXLibrary/lib/x64/Debug/UserXLibrary.lib")
+#endif
+#else 
+#ifndef _WIN64
+#pragma comment(lib, "../USERXLibrary/lib/Win32/Release/UserXLibrary.lib")
+#else 
+#pragma comment(lib, "../USERXLibrary/lib/x64/Release/UserXLibrary.lib")
+#endif
+#endif
+#endif // !BUILD_AS_LIB
+
+
+// 额外错误码 / Extra error code
+#ifndef UX_ERR_BAD_PASSWORD
+#define UX_ERR_BAD_PASSWORD        -10    // 密码错误 / bad password
+#endif
+
+// 错误码宏定义 / Error code macros
+#define UX_OK                        0    // 成功 / success
+#define UX_ERR_INVALID_ARGS         -1    // 参数错误 / invalid arguments
+#define UX_ERR_NOT_FOUND            -2    // 未找到 / not found
+#define UX_ERR_EXISTS               -3    // 已存在 / already exists
+#define UX_ERR_PERMISSION           -4    // 权限不足 / permission denied
+#define UX_ERR_NOT_LOGGED_IN        -5    // 未登录 / not logged in
+#define UX_ERR_BUFFER_TOO_SMALL     -6    // 缓冲区不足 / buffer too small
+#define UX_ERR_DB                   -7    // I/O 或数据库错误 / I/O or database error
+#define UX_ERR_NOT_DEFINED          -8    // 未定义(如动作不存在)/ not defined
+#define UX_ERR_DB_NOT_EMPTY         -9    // 数据库非空(已初始化)/ database not empty
+
+// 返回码 / Return codes(详见上方宏)
+// UX_OK, UX_ERR_INVALID_ARGS, UX_ERR_NOT_FOUND, UX_ERR_EXISTS,
+// UX_ERR_PERMISSION, UX_ERR_NOT_LOGGED_IN, UX_ERR_BUFFER_TOO_SMALL,
+// UX_ERR_DB, UX_ERR_NOT_DEFINED, UX_ERR_DB_NOT_EMPTY
+
+// 初始化:指定数据目录,内部将打开/创建 SQLite 数据库文件 UserX.db
+// Init: provide data directory; opens/creates SQLite DB file UserX.db
+UX_API int UX_Init(const wchar_t* storage_dir);
+
+// 可选:覆盖数据库路径(仅使用第一个参数作为 .db 文件路径,其他忽略)
+// Optional: override DB path (use first arg as .db path; others ignored)
+UX_API int UX_SetStorage(const wchar_t* users_db_path,
+                         const wchar_t* /*unused_logs*/,
+                         const wchar_t* /*unused_roles*/,
+                         const wchar_t* /*unused_actions*/);
+
+// 配置角色(name:level 按行)/ Configure roles from lines "name:level"
+// 仅允许在“空数据库”(roles/users/actions/logs 四表均无数据)时调用;否则返回 -9。
+// Only allowed when DB is empty (roles/users/actions/logs all zero rows); otherwise returns -9.
+// e.g. L"Admin:100\nEngineer:50\nOperator:10\n"
+UX_API int UX_SetRoleDefinitions(const wchar_t* roles_text);
+
+// 获取角色列表(name:level 按行);buffer 为空或不足时返回所需大小
+// Get roles list as lines; returns required size if buffer is null/too small
+UX_API int UX_GetRoles(wchar_t* buffer, int buffer_chars);
+
+// 用户管理 / User management
+UX_API int UX_AddUser(const wchar_t* username,
+                      const wchar_t* display_name,
+                      const wchar_t* password,
+                      const wchar_t* role_name);
+
+UX_API int UX_DeleteUser(const wchar_t* username);
+
+// 更新任意字段(传 nullptr 表示保持不变)/ Update subset; nullptr keeps field
+UX_API int UX_UpdateUser(const wchar_t* username,
+                         const wchar_t* new_display_name,
+                         const wchar_t* new_password,
+                         const wchar_t* new_role_name,
+                         int enabled /* -1 keep, 0 disable, 1 enable */);
+
+UX_API int UX_EnableUser(const wchar_t* username, int enabled /*0/1*/);
+UX_API int UX_ResetPassword(const wchar_t* username, const wchar_t* new_password);
+UX_API int UX_RenameUser(const wchar_t* username, const wchar_t* new_display_name);
+
+// 管理员权限:删除所有用户 / Admin-only
+UX_API int UX_DeleteAllUsers();
+
+// 获取用户列表(每行:username,display,level,enabled)/ Query users
+UX_API int UX_GetUsers(wchar_t* buffer, int buffer_chars);
+
+// 认证 / Authentication
+UX_API int UX_Login(const wchar_t* username, const wchar_t* password);
+UX_API void UX_Logout();
+UX_API int UX_IsLoggedIn();
+UX_API void UX_Shutdown(); // 释放资源 / shutdown and free resources
+UX_API int UX_GetCurrentUser(wchar_t* buffer, int buffer_chars);
+
+// 动作与权限 / Actions & permissions
+// 定义/覆盖动作(名称、描述、最低角色)/ Define/overwrite action
+UX_API int UX_DefineAction(const wchar_t* action_name,
+                           const wchar_t* description,
+                           const wchar_t* min_role_name);
+
+// 可执行返回1,不可执行返回0;负数为错误 / 1 allowed, 0 denied
+UX_API int UX_CanExecute(const wchar_t* action_name);
+
+// 记录动作到日志(时间、用户、动作、描述)/ Record action to logs
+UX_API int UX_RecordAction(const wchar_t* action_name);
+
+// 获取动作列表(每行:name,desc,minlevel)/ Get actions list
+UX_API int UX_GetActions(wchar_t* buffer, int buffer_chars);
+
+// 记录尝试(允许/拒绝)/ record an attempt with allowed flag (1 allowed, 0 denied)
+UX_API int UX_RecordAttempt(const wchar_t* action_name, int allowed /*1/0*/);
+
+// 查询最近 N 条日志(每行:ISO8601时间,用户名,动作,描述)/ Query logs
+UX_API int UX_QueryLogs(int last_n, wchar_t* buffer, int buffer_chars);
+
+// 根据错误码返回文字描述(中文/English)
+// Return a human-readable message for an error code
+UX_API const wchar_t* UX_ErrorMessage(int code);

--
Gitblit v1.9.3