diff --git a/app/common/lottery/lottery_manager.py b/app/common/lottery/lottery_manager.py index f328a311..edab1527 100644 --- a/app/common/lottery/lottery_manager.py +++ b/app/common/lottery/lottery_manager.py @@ -450,8 +450,10 @@ def get_random_items(self, count): group_name = "" student_name = "" + student_id = 0 if self.current_group_index == 1: - raw_group = system_random.choice(candidates).get("name", "") + selected_candidate = system_random.choice(candidates) + raw_group = selected_candidate.get("name", "") include_group = show_random in (0, 1, 2, 5, 6, 7, 8, 9) include_name = show_random in (0, 1, 2, 3, 4, 7, 8, 9, 10, 11) @@ -464,13 +466,23 @@ def get_random_items(self, count): if group_members: selected_member = system_random.choice(group_members) student_name = (selected_member or {}).get("name", "") + try: + student_id = int((selected_member or {}).get("id", 0) or 0) + except Exception: + student_id = 0 if not student_name: student_name = raw_group else: - student_name = system_random.choice(candidates).get("name", "") + selected_candidate = system_random.choice(candidates) + student_name = selected_candidate.get("name", "") + try: + student_id = int(selected_candidate.get("id", 0) or 0) + except Exception: + student_id = 0 prize_copy["ipc_group_name"] = str(group_name or "") prize_copy["ipc_student_name"] = str(student_name or "") + prize_copy["student_id"] = student_id if group_name or student_name: prize_copy["name"] = self._format_prize_student_text( prize_name, group_name, student_name, show_random @@ -1125,9 +1137,13 @@ def draw_random(widget): for p in prizes or []: if not isinstance(p, dict): continue + try: + sid = int(p.get("student_id", 0) or 0) + except Exception: + sid = 0 ipc_selected_students.append( { - "student_id": 0, + "student_id": sid, "student_name": str(p.get("ipc_student_name", "") or ""), "display_text": str( p.get("ipc_display_text", p.get("name", "")) or "" @@ -1139,7 +1155,10 @@ def draw_random(widget): ), } ) - selected_prizes = [(p["id"], p["name"], p.get("exist", True)) for p in prizes] + selected_prizes = [ + (p.get("student_id", 0) or 0, p["name"], p.get("exist", True)) + for p in prizes + ] display_result_animated( widget, diff --git a/app/core/single_instance.py b/app/core/single_instance.py index e216b7ac..1fa7cd83 100644 --- a/app/core/single_instance.py +++ b/app/core/single_instance.py @@ -20,13 +20,62 @@ def check_single_instance() -> Tuple[Optional[QSharedMemory], bool]: _activate_existing_instance() return shared_memory, False else: - logger.exception("无法附加到共享内存") - return shared_memory, False + logger.warning("无法附加到共享内存,尝试检测服务器状态") + if _check_server_alive(): + logger.info("检测到活跃的服务器,已有实例正在运行") + _activate_existing_instance() + return shared_memory, False + else: + logger.warning("服务器无响应,清理残留资源后重新启动") + _cleanup_stale_resources() + if shared_memory.create(1): + logger.info("单实例检查通过(清理后重新创建)") + return shared_memory, True + else: + logger.exception("清理后仍无法创建共享内存") + return shared_memory, False logger.info("单实例检查通过,可以安全启动程序") return shared_memory, True +def _check_server_alive() -> bool: + """检查本地服务器是否有响应 + + Returns: + bool: 服务器是否有响应 + """ + local_socket = QLocalSocket() + local_socket.connectToServer(SHARED_MEMORY_KEY) + connected = local_socket.waitForConnected(500) + if connected: + local_socket.disconnectFromServer() + return True + return False + + +def _cleanup_stale_resources() -> None: + """清理残留的单实例资源 + """ + QLocalServer.removeServer(SHARED_MEMORY_KEY) + logger.debug("已清理残留的socket资源") + + _cleanup_stale_shared_memory() + + +def _cleanup_stale_shared_memory() -> None: + """清理残留的共享内存段(POSIX系统需要) + + 在POSIX系统上,QSharedMemory在进程崩溃后不会自动清理。 + 需要先attach再detach来触发清理。 + """ + stale_memory = QSharedMemory(SHARED_MEMORY_KEY) + if stale_memory.attach(): + logger.debug("检测到残留的共享内存段,正在清理") + stale_memory.detach() + logger.debug("已清理残留的共享内存段") + + def _activate_existing_instance() -> bool: """激活已有实例 @@ -74,8 +123,14 @@ def setup_local_server( """ server = QLocalServer() if not server.listen(SHARED_MEMORY_KEY): - logger.exception(f"无法启动本地服务器: {server.errorString()}") - return None + error_string = server.errorString() + logger.warning(f"本地服务器启动失败,尝试清理残留socket: {error_string}") + + QLocalServer.removeServer(SHARED_MEMORY_KEY) + + if not server.listen(SHARED_MEMORY_KEY): + logger.exception(f"无法启动本地服务器: {server.errorString()}") + return None server.newConnection.connect( lambda: _handle_new_connection(server, main_window, float_window, url_handler) diff --git a/requirements-linux.txt b/requirements-linux.txt deleted file mode 100644 index 5a6b12da..00000000 --- a/requirements-linux.txt +++ /dev/null @@ -1,55 +0,0 @@ -# Linux 依赖配置 - 支持 Python 3.8.10 - -# === GUI框架 === -PySide6==6.7.1 -PySide6-Qt6==6.7.3 -PySide6-Fluent-Widgets==1.9.1 -PySide6-Frameless-Window==0.7.4 -darkdetect==0.8.0 - -# === 核心库 === -asyncio~=3.4.3 -loguru==0.7.3 -colorama==0.4.6 -packaging==25.0 - -# === 数据处理 === -numpy~=1.24.4 -pandas~=2.0.3 -pillow~=10.4.0 -openpyxl==3.1.5 -xlrd>=2.0.1 - -# === 网络与通信 === -requests==2.32.4 -edge-tts==7.0.2 - -# === 音频处理 === -pyttsx3==2.98 -sounddevice==0.5.2 -soundfile==0.13.1 - -# === 系统工具 === -psutil~=7.0.0 -keyboard==0.13.5 - -# === 加密与安全 === -pyotp==2.9.0 -pycryptodome==3.23.0 - -# === 二维码处理 === -pyqrcode~=1.2.1 -pypng~=0.20220715.0 -colorthief==0.2.1 - -# === Linux特定依赖 === -# Linux音频控制替代pycaw -pulsectl==24.8.0; platform_system == "Linux" - -# === 其他依赖 === -sip~=6.8.6 -jinja2~=3.1.6 -pyyaml>=6.0.1 - -# === 通知工具 === -plyer>=2.1.0 diff --git a/requirements-windows.txt b/requirements-windows.txt deleted file mode 100644 index 5bd1e2d3..00000000 --- a/requirements-windows.txt +++ /dev/null @@ -1,59 +0,0 @@ -# Windows 依赖配置 - 支持 Python 3.8.10 - -# === GUI框架 === -PySide6==6.7.1 -PySide6-Qt6==6.7.3 -PySide6-Fluent-Widgets==1.9.1 -PySide6-Frameless-Window==0.7.4 -darkdetect==0.8.0 - -# === 核心库 === -asyncio~=3.4.3 -loguru==0.7.3 -colorama==0.4.6 -packaging==25.0 - -# === 数据处理 === -numpy~=1.24.4 -pandas~=2.0.3 -pillow~=10.4.0 -openpyxl==3.1.5 -xlrd>=2.0.1 - -# === 网络与通信 === -requests==2.32.4 -edge-tts==7.0.2 - -# === 音频处理 === -pyttsx3==2.98 -sounddevice==0.5.2 -soundfile==0.13.1 - -# === 系统工具 === -psutil~=7.0.0 -keyboard==0.13.5 - -# === 加密与安全 === -pyotp==2.9.0 -pycryptodome==3.23.0 - -# === 二维码处理 === -pyqrcode~=1.2.1 -pypng~=0.20220715.0 -colorthief==0.2.1 - -# === Windows特定依赖 === -pywin32==310; platform_system == "Windows" -win32_setctime==1.2.0; platform_system == "Windows" -winshell==0.6; platform_system == "Windows" -comtypes==1.4.10 -wmi==1.5.1 -pycaw==20240210; platform_system == "Windows" - -# === 其他依赖 === -sip~=6.8.6 -jinja2~=3.1.6 -pyyaml>=6.0.1 - -# === 通知工具 === -plyer>=2.1.0