增加登陆日志留存模块
Showing
1 changed file
with
100 additions
and
2 deletions
| ... | @@ -27,6 +27,8 @@ from google import genai | ... | @@ -27,6 +27,8 @@ from google import genai |
| 27 | from google.genai import types | 27 | from google.genai import types |
| 28 | import hashlib | 28 | import hashlib |
| 29 | import pymysql | 29 | import pymysql |
| 30 | import socket | ||
| 31 | import requests | ||
| 30 | from datetime import datetime | 32 | from datetime import datetime |
| 31 | 33 | ||
| 32 | 34 | ||
| ... | @@ -117,10 +119,12 @@ class LoginDialog(QDialog): | ... | @@ -117,10 +119,12 @@ class LoginDialog(QDialog): |
| 117 | # Username field | 119 | # Username field |
| 118 | username_label = QLabel("用户名") | 120 | username_label = QLabel("用户名") |
| 119 | username_label.setObjectName("field_label") | 121 | username_label.setObjectName("field_label") |
| 122 | |||
| 120 | main_layout.addWidget(username_label) | 123 | main_layout.addWidget(username_label) |
| 121 | 124 | ||
| 122 | self.username_entry = QLineEdit() | 125 | self.username_entry = QLineEdit() |
| 123 | self.username_entry.setText(self.last_user) | 126 | self.username_entry.setText(self.last_user) |
| 127 | self.username_entry.setFixedHeight(40) | ||
| 124 | main_layout.addWidget(self.username_entry) | 128 | main_layout.addWidget(self.username_entry) |
| 125 | 129 | ||
| 126 | main_layout.addSpacing(10) | 130 | main_layout.addSpacing(10) |
| ... | @@ -132,6 +136,7 @@ class LoginDialog(QDialog): | ... | @@ -132,6 +136,7 @@ class LoginDialog(QDialog): |
| 132 | 136 | ||
| 133 | self.password_entry = QLineEdit() | 137 | self.password_entry = QLineEdit() |
| 134 | self.password_entry.setEchoMode(QLineEdit.Password) | 138 | self.password_entry.setEchoMode(QLineEdit.Password) |
| 139 | self.password_entry.setFixedHeight(40) | ||
| 135 | 140 | ||
| 136 | # Handle saved password placeholder | 141 | # Handle saved password placeholder |
| 137 | if self.saved_password_hash: | 142 | if self.saved_password_hash: |
| ... | @@ -159,6 +164,7 @@ class LoginDialog(QDialog): | ... | @@ -159,6 +164,7 @@ class LoginDialog(QDialog): |
| 159 | # Login button | 164 | # Login button |
| 160 | self.login_button = QPushButton("登录") | 165 | self.login_button = QPushButton("登录") |
| 161 | self.login_button.setObjectName("login_button") | 166 | self.login_button.setObjectName("login_button") |
| 167 | self.login_button.setFixedHeight(40) | ||
| 162 | self.login_button.clicked.connect(self.on_login) | 168 | self.login_button.clicked.connect(self.on_login) |
| 163 | main_layout.addWidget(self.login_button) | 169 | main_layout.addWidget(self.login_button) |
| 164 | 170 | ||
| ... | @@ -287,9 +293,13 @@ class LoginDialog(QDialog): | ... | @@ -287,9 +293,13 @@ class LoginDialog(QDialog): |
| 287 | self.success = True | 293 | self.success = True |
| 288 | self.authenticated_user = username | 294 | self.authenticated_user = username |
| 289 | self.current_password_hash = password_hash | 295 | self.current_password_hash = password_hash |
| 296 | |||
| 297 | # 记录用户登录日志 | ||
| 298 | self.log_user_login(username) | ||
| 299 | |||
| 290 | self.accept() # Close dialog with success | 300 | self.accept() # Close dialog with success |
| 291 | else: | 301 | else: |
| 292 | self.show_error("用户名或密码错误") | 302 | self.show_error("用户名或密码错误,联系[柴进]重置密码") |
| 293 | self.password_entry.clear() | 303 | self.password_entry.clear() |
| 294 | self.password_changed = False | 304 | self.password_changed = False |
| 295 | self.login_button.setEnabled(True) | 305 | self.login_button.setEnabled(True) |
| ... | @@ -303,8 +313,96 @@ class LoginDialog(QDialog): | ... | @@ -303,8 +313,96 @@ class LoginDialog(QDialog): |
| 303 | self.show_error(f"认证失败: {str(e)}") | 313 | self.show_error(f"认证失败: {str(e)}") |
| 304 | self.login_button.setEnabled(True) | 314 | self.login_button.setEnabled(True) |
| 305 | 315 | ||
| 316 | def get_local_ip(self): | ||
| 317 | """获取局域网IP地址""" | ||
| 318 | try: | ||
| 319 | # 尝试获取真实的局域网IP,而不是127.0.0.1 | ||
| 320 | hostname = socket.gethostname() | ||
| 321 | local_ip = socket.gethostbyname(hostname) | ||
| 322 | |||
| 323 | # 如果获取到的是127.0.0.1,尝试连接外部地址获取本地IP | ||
| 324 | if local_ip.startswith('127.'): | ||
| 325 | # 创建一个UDP socket连接到公共DNS服务器 | ||
| 326 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
| 327 | try: | ||
| 328 | s.connect(('8.8.8.8', 80)) | ||
| 329 | local_ip = s.getsockname()[0] | ||
| 330 | except: | ||
| 331 | pass | ||
| 332 | finally: | ||
| 333 | s.close() | ||
| 334 | |||
| 335 | return local_ip | ||
| 336 | except: | ||
| 337 | return "127.0.0.1" | ||
| 338 | |||
| 339 | def get_public_ip(self): | ||
| 340 | """获取公网IP地址,失败返回None""" | ||
| 341 | try: | ||
| 342 | # 使用多个API作为备选 | ||
| 343 | apis = [ | ||
| 344 | 'https://api.ipify.org', | ||
| 345 | 'https://ifconfig.me', | ||
| 346 | 'https://ipinfo.io/ip' | ||
| 347 | ] | ||
| 348 | |||
| 349 | for api in apis: | ||
| 350 | try: | ||
| 351 | response = requests.get(api, timeout=3) | ||
| 352 | if response.status_code == 200: | ||
| 353 | public_ip = response.text.strip() | ||
| 354 | # 简单验证IP格式 | ||
| 355 | if len(public_ip.split('.')) == 4 or ':' in public_ip: | ||
| 356 | return public_ip | ||
| 357 | except: | ||
| 358 | continue | ||
| 359 | return None | ||
| 360 | except: | ||
| 361 | return None | ||
| 362 | |||
| 363 | def get_device_name(self): | ||
| 364 | """获取设备名称""" | ||
| 365 | try: | ||
| 366 | return socket.gethostname() | ||
| 367 | except: | ||
| 368 | return "Unknown" | ||
| 369 | |||
| 370 | def log_user_login(self, username): | ||
| 371 | """静默记录用户登录日志,支持双IP""" | ||
| 372 | try: | ||
| 373 | local_ip = self.get_local_ip() | ||
| 374 | public_ip = self.get_public_ip() | ||
| 375 | device_name = self.get_device_name() | ||
| 376 | |||
| 377 | conn = pymysql.connect( | ||
| 378 | host=self.db_config['host'], | ||
| 379 | port=self.db_config.get('port', 3306), | ||
| 380 | user=self.db_config['user'], | ||
| 381 | password=self.db_config['password'], | ||
| 382 | database=self.db_config['database'], | ||
| 383 | connect_timeout=5 | ||
| 384 | ) | ||
| 385 | |||
| 386 | try: | ||
| 387 | with conn.cursor() as cursor: | ||
| 388 | sql = """INSERT INTO nano_banana_user_log | ||
| 389 | (user_name, local_ip, public_ip, device_name, login_time) | ||
| 390 | VALUES (%s, %s, %s, %s, %s)""" | ||
| 391 | cursor.execute(sql, (username, local_ip, public_ip, device_name, datetime.now())) | ||
| 392 | conn.commit() | ||
| 393 | finally: | ||
| 394 | conn.close() | ||
| 395 | |||
| 396 | except Exception: | ||
| 397 | # 静默处理,不影响登录流程 | ||
| 398 | pass | ||
| 399 | |||
| 306 | def show_error(self, message): | 400 | def show_error(self, message): |
| 307 | """Display error message""" | 401 | """显示错误弹窗和标签""" |
| 402 | # 显示弹窗错误提示 | ||
| 403 | QMessageBox.critical(self, "登录错误", message) | ||
| 404 | |||
| 405 | # 同时保留标签显示 | ||
| 308 | self.error_label.setText(message) | 406 | self.error_label.setText(message) |
| 309 | self.error_label.setStyleSheet("QLabel { color: #ff3b30; }") | 407 | self.error_label.setStyleSheet("QLabel { color: #ff3b30; }") |
| 310 | 408 | ... | ... |
-
Please register or sign in to post a comment