Files
Nexus/docs/changelog/2026-06-02-system-bug-fix-7-high.md
T

102 lines
4.3 KiB
Markdown
Raw Normal View History

# 2026-06-02 全系统 Bug 扫描与修复
## 日期
2026-06-02
## 变更摘要
对全系统进行静态代码扫描,发现并修复 **7 个 HIGH 级 Bug**
---
## HIGH-1: `download_file` SSH 连接在流传输前被过早释放
**文件**: `server/api/sync_v2.py`
**问题**: `finally` 块中 `ssh_pool.release()``StreamingResponse` 生成器尚未开始执行时就关闭连接,导致文件下载中断。
**修复**: 将 `release()` 移入 `file_generator()``finally` 子句,在流传输完成或客户端断开后再释放;提前退出(404/400/413)在各自分支 `await release()`
---
## HIGH-2: WebSocket JWT `tv` 默认值 `-1` 与 HTTP 中间件 `0` 不一致
**文件**: `server/api/websocket.py`
**问题**: `_verify_ws_token` 对无 `tv` 字段的旧 token 使用默认值 `-1`,而数据库 `token_version``NULL/0`,导致 `-1 != 0` 判断失败,合法用户 WebSocket 连接被拒。
**修复**: 对齐 HTTP 中间件逻辑,改为 `payload.get("tv") or 0`DB 侧 `admin.token_version or 0`
---
## HIGH-3: `retry_runner` 达到 `max_retries` 后任务永久僵尸
**文件**: `server/background/retry_runner.py`
**问题**: 重试次数达到 `max_retries` 时,任务状态仍维持 `pending``retry_count < max_retries` 条件不再触发,形成永不被处理的僵尸记录。
**修复**: 成功/失败分支均检查 `retry_count >= max_retries`,达到上限时将 `status` 置为 `failed`,记录原因日志。
---
## HIGH-4: `sync_v2.py` 和 `sync_engine_v2.py` 多处 `stderr` 为 `None` 引发 `TypeError`
**文件**: `server/api/sync_v2.py`, `server/application/services/sync_engine_v2.py`
**问题**: 多处直接执行 `result["stderr"][:N]`,当 SSH 执行结果中 `stderr``None` 时(如连接超时)抛 `TypeError: 'NoneType' object is not subscriptable`,导致 API 返回 500。
**修复**: 所有 `result["stderr"][:N]` 统一改为 `(result.get("stderr") or "")[:N]`,共修复 5 处。
---
## HIGH-5: `auth_service._verify_password` bcrypt 抛 `ValueError` 导致 500
**文件**: `server/application/services/auth_service.py`
**问题**: DB 中若存在格式异常的 `password_hash``bcrypt.checkpw` 会抛 `ValueError`,导致登录接口 500 而非 401。
**修复**: 用 `try/except` 包裹 `bcrypt.checkpw`,异常时返回 `False`(密码不匹配)。
---
## HIGH-6: `install.py /lock` 硬编码查询 `"admin"` 用户名
**文件**: `server/api/install.py`
**问题**: `/lock` 端点通过 `get_by_username("admin")` 验证管理员是否创建,若用户在步骤 4 使用了非 `admin` 的用户名(如 `administrator`),会错误地拒绝锁定(状态 400),且 DB 不可达时会静默放行锁定操作。
**修复**: 改为 `SELECT COUNT(*) FROM admins WHERE is_active = 1` 验证活跃管理员存在;DB 不可达时改为拒绝锁定(503)而非放行。
---
## HIGH-7: `heartbeat_flush.py` 单条失败污染整批事务
**文件**: `server/background/heartbeat_flush.py`
**问题**: 所有服务器在同一 `AsyncSession` 中批量更新,单条 `UPDATE` 失败时仅打日志,但会话处于需要 rollback 的状态,最终 `commit()` 使整批失败,日志显示 "N 台更新" 实际全部失败。同时修复 `datetime` 时区感知问题(naive datetime 写入 MySQL)。
**修复**: 每台服务器使用独立的 `AsyncSessionLocal()` 上下文,单条失败不影响其他;`fromisoformat` 后统一补 `tzinfo=timezone.utc`
---
## 涉及文件
| 文件 | 修复项 |
|------|-------|
| `server/api/sync_v2.py` | HIGH-1(下载连接)、HIGH-4stderr None, 3 处) |
| `server/api/websocket.py` | HIGH-2WebSocket tv |
| `server/api/install.py` | HIGH-6/lock 用户名) |
| `server/application/services/auth_service.py` | HIGH-5bcrypt ValueError |
| `server/application/services/sync_engine_v2.py` | HIGH-4stderr None, 2 处) |
| `server/background/retry_runner.py` | HIGH-3(僵尸任务) |
| `server/background/heartbeat_flush.py` | HIGH-7(事务污染、时区) |
## 需要迁移/重启
- 后端重启生效(`supervisorctl restart nexus`
- 无数据库 migration 变更
## 验证方式
1. `python -c "import server.main"` — 导入无报错 ✓
2. 登录测试 WebSocket 实时推送
3. 下载文件功能测试
4. 查看 `push_retry_jobs` 表确认无 `status=pending` 的僵尸记录