From e42aa86fb06c0ea6e94cbefda37ea4f67edf1278 Mon Sep 17 00:00:00 2001 From: Supercmd Date: Sat, 4 Apr 2026 17:05:03 +0800 Subject: [PATCH 1/7] =?UTF-8?q?fix(=E6=8A=BD=E5=A5=96=E7=AE=A1=E7=90=86):?= =?UTF-8?q?=20=E4=BF=AE=E5=A4=8D=E6=8A=BD=E5=A5=96=E7=BB=93=E6=9E=9C?= =?UTF-8?q?=E4=B8=AD=E5=AD=A6=E7=94=9FID=E6=9C=AA=E6=AD=A3=E7=A1=AE?= =?UTF-8?q?=E4=BC=A0=E9=80=92=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 确保抽奖结果中的学生ID能够正确从候选数据中提取并传递到奖品信息中 同时修正抽奖结果显示时学生ID的获取逻辑 --- app/common/lottery/lottery_manager.py | 27 ++++++++++-- requirements-linux.txt | 55 ------------------------- requirements-windows.txt | 59 --------------------------- 3 files changed, 23 insertions(+), 118 deletions(-) delete mode 100644 requirements-linux.txt delete mode 100644 requirements-windows.txt 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/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 From 3251e05a168c488a22cc2ba4442baf921c508180 Mon Sep 17 00:00:00 2001 From: Supercmd Date: Sat, 4 Apr 2026 17:39:44 +0800 Subject: [PATCH 2/7] =?UTF-8?q?fix(=E5=8D=95=E5=AE=9E=E4=BE=8B):=20?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=85=B1=E4=BA=AB=E5=86=85=E5=AD=98=E5=92=8C?= =?UTF-8?q?=E6=9C=AC=E5=9C=B0=E6=9C=8D=E5=8A=A1=E5=99=A8=E7=9A=84=E5=81=A5?= =?UTF-8?q?=E5=A3=AE=E6=80=A7=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当无法附加到共享内存时,增加服务器状态检测和资源清理逻辑 修复本地服务器启动失败时未清理残留socket的问题 --- app/core/single_instance.py | 48 +++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/app/core/single_instance.py b/app/core/single_instance.py index e216b7ac..4b43d69e 100644 --- a/app/core/single_instance.py +++ b/app/core/single_instance.py @@ -20,13 +20,47 @@ 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资源") + + def _activate_existing_instance() -> bool: """激活已有实例 @@ -74,8 +108,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) From db5c6cf7661a303f2639a087a54935b150fac26a Mon Sep 17 00:00:00 2001 From: Supercmd Date: Sat, 4 Apr 2026 18:06:08 +0800 Subject: [PATCH 3/7] =?UTF-8?q?fix(=E5=8D=95=E5=AE=9E=E4=BE=8B):=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0POSIX=E7=B3=BB=E7=BB=9F=E4=B8=8B=E5=85=B1?= =?UTF-8?q?=E4=BA=AB=E5=86=85=E5=AD=98=E6=AE=8B=E7=95=99=E6=B8=85=E7=90=86?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在POSIX系统上,QSharedMemory在进程崩溃后不会自动清理残留的共享内存段 添加_cleanup_stale_shared_memory函数来检测并清理这些残留资源 --- app/core/single_instance.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/core/single_instance.py b/app/core/single_instance.py index 4b43d69e..1fa7cd83 100644 --- a/app/core/single_instance.py +++ b/app/core/single_instance.py @@ -60,6 +60,21 @@ 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: """激活已有实例 From fdcf8a55d8600e780489418f838580e8f324f5f2 Mon Sep 17 00:00:00 2001 From: Supercmd Date: Sat, 4 Apr 2026 18:23:27 +0800 Subject: [PATCH 4/7] =?UTF-8?q?style:=20=E7=A7=BB=E9=99=A4=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E5=AD=97=E7=AC=A6=E4=B8=B2=E4=B8=AD=E7=9A=84=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E7=A9=BA=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/single_instance.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/core/single_instance.py b/app/core/single_instance.py index 1fa7cd83..69185c93 100644 --- a/app/core/single_instance.py +++ b/app/core/single_instance.py @@ -55,8 +55,7 @@ def _check_server_alive() -> bool: def _cleanup_stale_resources() -> None: - """清理残留的单实例资源 - """ + """清理残留的单实例资源""" QLocalServer.removeServer(SHARED_MEMORY_KEY) logger.debug("已清理残留的socket资源") From 4b5342b7516883882e0f549bd221e6331e41d272 Mon Sep 17 00:00:00 2001 From: Supercmd Date: Sat, 4 Apr 2026 18:38:56 +0800 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E8=BF=9E=E6=8E=A5=E6=A3=80=E6=9F=A5=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E5=92=8C=E5=A5=96=E5=93=81ID=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复服务器无响应检查的描述不准确问题,改为更明确的"服务器不可连接" 改进奖品ID处理逻辑,增加异常捕获和多种ID字段尝试 --- app/common/lottery/lottery_manager.py | 20 +++++++++++++++----- app/core/single_instance.py | 6 +++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/common/lottery/lottery_manager.py b/app/common/lottery/lottery_manager.py index edab1527..ea3c72c5 100644 --- a/app/common/lottery/lottery_manager.py +++ b/app/common/lottery/lottery_manager.py @@ -467,7 +467,9 @@ def get_random_items(self, count): 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) + student_id = int( + (selected_member or {}).get("id", 0) or 0 + ) except Exception: student_id = 0 if not student_name: @@ -1155,10 +1157,18 @@ def draw_random(widget): ), } ) - selected_prizes = [ - (p.get("student_id", 0) or 0, p["name"], p.get("exist", True)) - for p in prizes - ] + selected_prizes = [] + for p in prizes: + try: + display_num = int(p.get("student_id", 0) or 0) + except Exception: + display_num = 0 + if not display_num: + try: + display_num = int(p.get("id", 0) or 0) + except Exception: + display_num = 0 + selected_prizes.append((display_num, p["name"], p.get("exist", True))) display_result_animated( widget, diff --git a/app/core/single_instance.py b/app/core/single_instance.py index 69185c93..ebc0bfc4 100644 --- a/app/core/single_instance.py +++ b/app/core/single_instance.py @@ -26,7 +26,7 @@ def check_single_instance() -> Tuple[Optional[QSharedMemory], bool]: _activate_existing_instance() return shared_memory, False else: - logger.warning("服务器无响应,清理残留资源后重新启动") + logger.warning("服务器不可连接,清理残留资源后重新启动") _cleanup_stale_resources() if shared_memory.create(1): logger.info("单实例检查通过(清理后重新创建)") @@ -40,10 +40,10 @@ def check_single_instance() -> Tuple[Optional[QSharedMemory], bool]: def _check_server_alive() -> bool: - """检查本地服务器是否有响应 + """检查本地服务器是否在监听(可连接) Returns: - bool: 服务器是否有响应 + bool: 服务器是否可连接 """ local_socket = QLocalSocket() local_socket.connectToServer(SHARED_MEMORY_KEY) From abbac567773b1831e416e155288d7354c5bb100c Mon Sep 17 00:00:00 2001 From: Supercmd Date: Sat, 4 Apr 2026 18:46:35 +0800 Subject: [PATCH 6/7] =?UTF-8?q?refactor(music=5Fplayer):=20=E9=80=9A?= =?UTF-8?q?=E8=BF=87ruff=E7=A7=BB=E9=99=A4=E6=9C=AA=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E7=9A=84time=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/music/music_player.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/common/music/music_player.py b/app/common/music/music_player.py index c3b57e8b..723de2df 100644 --- a/app/common/music/music_player.py +++ b/app/common/music/music_player.py @@ -3,7 +3,6 @@ # ================================================== import threading -import time from typing import Optional import numpy as np from loguru import logger @@ -287,7 +286,10 @@ def _play_music_worker(self, music_path: str, loop: bool) -> None: if self._fade_out_remaining_time >= chunk_duration: # 整个块都在渐出范围内 fade_out_ratio = 1.0 - ( - (self._fade_out_duration - self._fade_out_remaining_time) + ( + self._fade_out_duration + - self._fade_out_remaining_time + ) / self._fade_out_duration ) chunk *= self._volume * fade_out_ratio @@ -299,10 +301,15 @@ def _play_music_worker(self, music_path: str, loop: bool) -> None: ) if fade_out_samples > 0: fade_out_ratio = 1.0 - ( - (self._fade_out_duration - self._fade_out_remaining_time) + ( + self._fade_out_duration + - self._fade_out_remaining_time + ) / self._fade_out_duration ) - chunk[:fade_out_samples] *= self._volume * fade_out_ratio + chunk[:fade_out_samples] *= ( + self._volume * fade_out_ratio + ) chunk[fade_out_samples:] = 0 else: chunk[:] = 0 From 93b1c1dcae2d1e800fbe2a7d9be95723f57ad91b Mon Sep 17 00:00:00 2001 From: Supercmd Date: Sat, 4 Apr 2026 18:58:38 +0800 Subject: [PATCH 7/7] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0CHANGELOG?= =?UTF-8?q?=E5=92=8CAGENTS=E6=96=87=E6=A1=A3=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 统一文档中的换行格式,移除多余的空格 --- CHANGELOG/v2.3.2/CHANGELOG.md | 12 ++++++------ CHANGELOG/v2.3.5/CHANGELOG.md | 2 +- app/view/AGENTS.md | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG/v2.3.2/CHANGELOG.md b/CHANGELOG/v2.3.2/CHANGELOG.md index 15b1f76a..c3e18a77 100644 --- a/CHANGELOG/v2.3.2/CHANGELOG.md +++ b/CHANGELOG/v2.3.2/CHANGELOG.md @@ -16,17 +16,17 @@ v2.3 - Shiroko (砂狼白子) release 3 **为何选择新架构?** -- ✅ **跨平台兼容性**更强 -- ✅ **界面流畅度**更高 -- ✅ **长期可维护性**更稳 +- ✅ **跨平台兼容性**更强 +- ✅ **界面流畅度**更高 +- ✅ **长期可维护性**更稳 新架构将助力我们更好地实现「简洁、公平、多功能」的核心目标,并在校园点名、抽奖、游戏、决策等场景中保持高效与易用。 **开发计划** -- 持续在 GitHub 更新进度与设计方案 -- 测试版本功能稳定后开放下载 -- 欢迎第一时间体验与验证 +- 持续在 GitHub 更新进度与设计方案 +- 测试版本功能稳定后开放下载 +- 欢迎第一时间体验与验证 感谢一直以来的支持与信任,让我们共同迎接这次全面焕新! diff --git a/CHANGELOG/v2.3.5/CHANGELOG.md b/CHANGELOG/v2.3.5/CHANGELOG.md index 80a8f2ba..1cea492b 100644 --- a/CHANGELOG/v2.3.5/CHANGELOG.md +++ b/CHANGELOG/v2.3.5/CHANGELOG.md @@ -16,7 +16,7 @@ v2.3 - Shiroko (砂狼白子) release 4 ## 🐛 修复问题 -- 修复 **主题页面** 无法显示的问题 +- 修复 **主题页面** 无法显示的问题 - 修复 **设置窗口** 重启后无法打开的问题 - 修复 **闪抽** 在随机抽取模式下,无法正常工作的问题 diff --git a/app/view/AGENTS.md b/app/view/AGENTS.md index 937cafc0..600388f0 100644 --- a/app/view/AGENTS.md +++ b/app/view/AGENTS.md @@ -72,11 +72,11 @@ class SomeSettingPage(QWidget): super().__init__(parent) self._init_ui() self._load_settings() - + def _init_ui(self): # Setup layout and widgets pass - + def _load_settings(self): # Load from settings_access pass