Skip to content

fix: 修复抽奖动画学号显示错误与单实例检测失败问题#213

Closed
trustedinster wants to merge 3 commits intoSECTL:masterfrom
trustedinster:master
Closed

fix: 修复抽奖动画学号显示错误与单实例检测失败问题#213
trustedinster wants to merge 3 commits intoSECTL:masterfrom
trustedinster:master

Conversation

@trustedinster
Copy link
Copy Markdown
Contributor

修复内容

本 PR 包含两个独立的 bug 修复:

1. 修复抽奖动画期间学号显示错误 (Fixes #211)

问题描述
在播放抽奖动画时,截图抓拍显示的学号超出了班级实际人数范围(如显示70号,但班里一共56人)。

根本原因
lottery_manager.py 中的 draw_random 函数在创建动画帧数据时,错误地使用了奖品ID而非学生ID。当奖池中有70个奖品但班级只有56名学生时,动画显示的"学号"就会超出实际学生范围。

修复方案

  • 修改 get_random_items 函数,在选取学生时同时获取并存储学生ID
  • 修改 draw_random 函数,使用学生ID替代奖品ID

影响范围

  • 仅影响抽奖功能的动画播放期间
  • 不影响最终抽取结果
  • 仅当启用了"关联学生名单"功能时才会出现此问题

2. 修复单实例检测与本地服务器启动失败 (initiative_1)

问题描述
应用程序在异常终止后重启时出现两个相关问题:

  1. 无法附加到共享内存
  2. 本地服务器启动失败(Address in use)

根本原因
当进程异常退出时,QSharedMemoryQLocalServer 的资源可能处于不一致状态,导致新进程无法正常启动。

在POSIX系统(Linux/macOS)上,QSharedMemory 使用System V或POSIX共享内存API,这些系统级资源在进程崩溃后不会自动清理,导致重启时 shared_memory.create(1) 失败。

修复方案

  • 新增 _check_server_alive 函数检测本地服务器是否有响应
  • 新增 _cleanup_stale_resources 函数清理残留的socket资源
  • 新增 _cleanup_stale_shared_memory 函数清理残留的共享内存段(POSIX系统需要)
  • 修改 check_single_instance 函数,当无法附加共享内存时检测服务器状态
  • 修改 setup_local_server 函数,当 socket 地址被占用时清理残留资源后重试

影响范围

  • 影响所有平台(Windows、Linux、macOS)
  • 仅在进程异常退出后重启时出现
  • 使用 Qt 跨平台 API,确保兼容性

修改文件

  • app/common/lottery/lottery_manager.py
  • app/core/single_instance.py

测试建议

Issue #211 测试:

  1. 导入一个56人的学生名单(学号1-56)
  2. 创建一个包含70个奖品的奖池
  3. 在抽奖设置中关联该学生名单
  4. 启用动画播放,设置手动停止
  5. 执行抽取,观察动画期间的学号显示
  6. 验证学号始终在1-56范围内

initiative_1 测试:

  1. 启动应用程序
  2. 异常终止进程(如 kill -9 或崩溃)
  3. 再次启动应用程序
  4. 验证程序能正常启动或激活已有实例

验证

所有修改已通过 ruff 代码检查。

确保抽奖结果中的学生ID能够正确从候选数据中提取并传递到奖品信息中
同时修正抽奖结果显示时学生ID的获取逻辑
当无法附加到共享内存时,增加服务器状态检测和资源清理逻辑
修复本地服务器启动失败时未清理残留socket的问题
在POSIX系统上,QSharedMemory在进程崩溃后不会自动清理残留的共享内存段
添加_cleanup_stale_shared_memory函数来检测并清理这些残留资源
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 主要修复抽奖动画阶段的“学号显示错误”(Fixes #211),并增强单实例检测在异常退出后重启时的自恢复能力,避免共享内存/本地 socket 残留导致无法启动。

Changes:

  • 抽奖动画帧数据中增加并传递 student_id,在动画展示/通知 IPC 中使用学生 ID 替代奖品 ID。
  • 单实例检测增加“本地服务器存活探测 + 残留资源清理 + 失败后重建共享内存”的恢复流程,并在本地服务器 listen 失败时清理 socket 后重试。
  • 删除 requirements-windows.txtrequirements-linux.txt(平台 requirements 文件)。

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
app/common/lottery/lottery_manager.py 动画随机帧携带并使用学生 ID,修复关联学生名单时动画学号越界问题
app/core/single_instance.py 增加存活检测与残留清理逻辑,改善异常退出后的重启体验
requirements-windows.txt 删除 Windows 平台 requirements 文件
requirements-linux.txt 删除 Linux 平台 requirements 文件

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1158 to +1161
selected_prizes = [
(p.get("student_id", 0) or 0, p["name"], p.get("exist", True))
for p in prizes
]
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

draw_random() now builds selected_prizes using p.get("student_id", 0) for the tuple's num. When get_random_items() returns prizes without a student_id (e.g., student-assignment disabled, or candidates is empty and it returns selected_prizes directly), this will force the displayed number to 0/00 for all animation frames and changes prior behavior (previously used p["id"]). Consider falling back to the prize id when student_id is missing/0 (or using None to suppress the number), so non-assignment / empty-candidate cases continue to display correctly while still using real student ids when available.

Suggested change
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))
)

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +54
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
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_check_server_alive()的注释写的是“服务器是否有响应”,但当前实现只检查是否能在超时内建立连接(并未验证对端能处理/回应任何数据)。建议要么把注释/日志措辞改为“是否可连接/是否在监听”,要么实现一个简单的 ping(写入并等待对端读/回)来匹配“有响应”的语义。

Copilot uses AI. Check for mistakes.
Comment on lines 20 to +27
_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
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本 PR 代码中还删除了 requirements-windows.txtrequirements-linux.txt 两个依赖文件,但 PR 描述里未提及这一变更。若这是有意切换到 pyproject.toml/uv.lock 作为唯一依赖来源,建议在描述中补充说明(或拆分为独立 PR),以便评审者确认对打包/CI/文档没有影响。

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

播放抽取动画时偶现号码错误

2 participants