feat: add computer name-based silent login (无感登录)

Implement automatic authentication based on computer name to enable
passwordless login for registered computers with fallback to manual
authentication.

Changes:
- Add SessionManager.login_by_computer_name() for silent login attempt
- Add BIPUsersDAO.authenticate_by_computer_name() for computer name lookup
- Update BIPUsersDAO.create_user() to support optional computer_name parameter
- Update main_ui.py to try silent login before showing login dialog
- Display current computer name in login dialog for user reference

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-02-10 16:44:47 +08:00
parent 7264fdc71a
commit 6d81d5cb76
3 changed files with 101 additions and 14 deletions

View File

@@ -63,6 +63,28 @@ class SessionManager:
return True return True
return False return False
def login_by_computer_name(self) -> bool:
"""
Attempt silent login using computer name
Returns:
True if login successful, False otherwise
"""
import socket
from db.bip_users_dao import BIPUsersDAO
computer_name = socket.gethostname()
dao = BIPUsersDAO()
user_info = dao.authenticate_by_computer_name(computer_name)
if user_info:
self._current_user = {
'username': user_info['username'],
'user_type': user_info['user_type']
}
return True
return False
def logout(self): def logout(self):
"""Logout the current user and clear session""" """Logout the current user and clear session"""
self._current_user = None self._current_user = None

View File

@@ -49,6 +49,44 @@ class BIPUsersDAO(BaseDAO):
} }
return None return None
def authenticate_by_computer_name(self, computer_name: str) -> Optional[Dict[str, Any]]:
"""
Authenticate a user using computer name (silent login)
Args:
computer_name: The computer name to authenticate
Returns:
Dict with user info if authentication successful, None otherwise
Returns: {id, username, user_type}
"""
table_name = self._convert_sql('[dbo].[BIPUsers]')
placeholder = self._get_placeholder()
# Note: Column name is 'ComputerNmae' (typo in database schema)
if self._db_type == DatabaseType.MYSQL:
sql = f"""
SELECT ID, UserName, UserType
FROM {table_name}
WHERE ComputerNmae = {placeholder}
"""
else:
sql = f"""
SELECT [ID], [UserName], [UserType]
FROM {table_name}
WHERE [ComputerNmae] = {placeholder}
"""
with get_connection() as db:
results = db.execute_query(sql, (computer_name,))
if results:
return {
'id': results[0]['ID'],
'username': results[0]['UserName'],
'user_type': results[0]['UserType']
}
return None
def get_all_users(self) -> List[Dict[str, Any]]: def get_all_users(self) -> List[Dict[str, Any]]:
""" """
Get all users from the database Get all users from the database
@@ -84,14 +122,15 @@ class BIPUsersDAO(BaseDAO):
for row in results for row in results
] ]
def create_user(self, username: str, password: str, user_type: str) -> bool: def create_user(self, username: str, password: str, user_type: str, computer_name: str = '') -> bool:
""" """
Create a new user Create a new user with optional computer name for silent login
Args: Args:
username: The username (must be unique) username: The username (must be unique)
password: The password (plain text for internal tool) password: The password (plain text for internal tool)
user_type: User type ('Admin', 'User', or 'Guest') user_type: User type ('Admin', 'User', or 'Guest')
computer_name: Optional computer name for silent login (default: '')
Returns: Returns:
True if successful, False otherwise True if successful, False otherwise
@@ -101,19 +140,35 @@ class BIPUsersDAO(BaseDAO):
# 根据数据库类型选择列名格式 # 根据数据库类型选择列名格式
if self._db_type == DatabaseType.MYSQL: if self._db_type == DatabaseType.MYSQL:
if computer_name:
sql = f"""
INSERT INTO {table_name} (UserName, Password, UserType, ComputerNmae)
VALUES ({placeholder}, {placeholder}, {placeholder}, {placeholder})
"""
params = (username, password, user_type, computer_name)
else:
sql = f""" sql = f"""
INSERT INTO {table_name} (UserName, Password, UserType) INSERT INTO {table_name} (UserName, Password, UserType)
VALUES ({placeholder}, {placeholder}, {placeholder}) VALUES ({placeholder}, {placeholder}, {placeholder})
""" """
params = (username, password, user_type)
else:
if computer_name:
sql = f"""
INSERT INTO {table_name} ([UserName], [Password], [UserType], [ComputerNmae])
VALUES ({placeholder}, {placeholder}, {placeholder}, {placeholder})
"""
params = (username, password, user_type, computer_name)
else: else:
sql = f""" sql = f"""
INSERT INTO {table_name} ([UserName], [Password], [UserType]) INSERT INTO {table_name} ([UserName], [Password], [UserType])
VALUES ({placeholder}, {placeholder}, {placeholder}) VALUES ({placeholder}, {placeholder}, {placeholder})
""" """
params = (username, password, user_type)
try: try:
with get_connection() as db: with get_connection() as db:
db.execute_update(sql, (username, password, user_type)) db.execute_update(sql, params)
return True return True
except Exception as e: except Exception as e:
print(f"Error creating user: {e}") print(f"Error creating user: {e}")

View File

@@ -1,6 +1,7 @@
""" """
Login Dialog - Modal dialog for user authentication Login Dialog - Modal dialog for user authentication
""" """
import socket
import tkinter as tk import tkinter as tk
from tkinter import ttk, messagebox from tkinter import ttk, messagebox
from typing import Optional, Tuple from typing import Optional, Tuple
@@ -37,7 +38,7 @@ class LoginDialog:
"""Create the login dialog UI""" """Create the login dialog UI"""
self.dialog = tk.Toplevel(self.parent) self.dialog = tk.Toplevel(self.parent)
self.dialog.title("ERP 自动化工具 - 登录") self.dialog.title("ERP 自动化工具 - 登录")
self.dialog.geometry("400x250") self.dialog.geometry("400x280")
self.dialog.resizable(False, False) self.dialog.resizable(False, False)
# Center the dialog on parent # Center the dialog on parent
@@ -52,7 +53,7 @@ class LoginDialog:
parent_height = self.parent.winfo_height() parent_height = self.parent.winfo_height()
dialog_width = 400 dialog_width = 400
dialog_height = 250 dialog_height = 280
x = parent_x + (parent_width - dialog_width) // 2 x = parent_x + (parent_width - dialog_width) // 2
y = parent_y + (parent_height - dialog_height) // 2 y = parent_y + (parent_height - dialog_height) // 2
self.dialog.geometry(f"{dialog_width}x{dialog_height}+{x}+{y}") self.dialog.geometry(f"{dialog_width}x{dialog_height}+{x}+{y}")
@@ -78,7 +79,16 @@ class LoginDialog:
text="请登录", text="请登录",
font=('', 16, 'bold') font=('', 16, 'bold')
) )
title_label.pack(pady=(0, 20)) title_label.pack(pady=(0, 10))
# Computer name display
computer_name_label = ttk.Label(
main_frame,
text=f"当前计算机: {socket.gethostname()}",
font=('', 9),
foreground='gray'
)
computer_name_label.pack(pady=(0, 15))
# Username field # Username field
username_frame = ttk.Frame(main_frame) username_frame = ttk.Frame(main_frame)