# 2026-05-24 批量审查修复 — 27项全部处理

**日期**: 2026-05-24
**动机**: 全面扫描项目，发现未实现/不完整/死代码问题 27 项，逐项沟通后全部处理
**变更摘要**:

### Bug 修复
- B-01: Agent API Key 验证完全失效 — `verify_api_key()` 参数传错，改为 `Depends()` 模式
- B-02: upgrade-agent 不检查/更新依赖 — 增加 Python 版本预检 + 自动 pip install（锁版本）+ 备份回滚
- B-03: POST /api/servers/push 死端点 — 删除端点 + ServerPush schema + ServerService.push_to_servers()

### 功能补全
- U-01: POST /api/servers/check 无前端入口 — servers.html 批量操作栏加「健康检查」按钮
- U-03: POST /api/sync/commands 无前端入口且与 scripts/exec 冗余 — 删除端点 + SyncCommands schema
- U-04/U-05: POST /api/sync/config + config/rollback — 删除端点（sysctl 可通过脚本执行实现）
- U-06: POST /api/sync/sftp 无前端入口 — 删除端点
- C-03: Schedule script 类型缺少必填校验 — Pydantic model_validator 校验 script_id/script_content + push 模式 source_path
- 定时任务支持一次性/循环模式 — Model 加 run_mode + fire_at，前端加切换 UI，执行后自动禁用

### 前端修复
- FE-01: alerts.html getTopType() 硬编码"见详情" — API 加 top_types 查询，前端显示实际类型
- FE-02: assets.html SSH 会话只显示 "Server #ID" — 加载服务器名称映射
- FE-03: assets.html editNode sort_order 硬编码 0 — 回填原始值
- FE-04: layout.js 搜索链接不定位到具体记录 — 加 ?highlight=id 参数，各页面自动定位

### 死代码清理
- S-01: FileDeploy schema 零引用 — 删除
- S-02: PaginatedResponse schema 零引用 — 删除
- D-02: ServerService.list_servers() 零调用者 — 删除
- D-03: ScriptService.test_credential_connection() 零调用者 — 删除
- Engine: sync_commands()、sync_config()、_rollback_config()、sftp_transfer() 随端点删除

### 安全/质量
- CFG-03: install.py 两处 silent pass — 改为 logger.warning

### 深度扫描第三轮 Bug 修复
- BUG-1 [HIGH]: servers.py 路由遮蔽 — `GET /logs` 声明在 `GET /{id}` 之后，导致 /logs 被 /{id} 拦截（3个前端页面同步日志查询失败）。修复：将 `/logs` 移到 `/{id}` 之前
- BUG-2 [CRITICAL]: terminal.html WebSSH 完全不可用 — 1) token URL 缺少 API 前缀 → 404；2) apiFetch() 返回 Response 未调 .json() → TypeError；3) 缺少 Content-Type header。三处全部修复
- BUG-3 [MEDIUM]: schedule_runner.py `_field_match()` 提前返回 — 逗号分隔 cron 表达式（如 `*/5,1`）遇到 `*/5` 不匹配时直接 return False，不再检查后续 `1`。修复：step 不匹配时 continue 而非 return

### 深度扫描第四轮 Bug 修复
- BUG-4 [CRITICAL]: scripts.html `startExecPoll()` 函数名错误 → `startExecPolling()`，长任务快速执行时 ReferenceError 崩溃
- BUG-5 [HIGH]: agent.py `{value!r!s:.50}` 格式串语法非法，agent 发非数值时 ValueError — 修复为 `{value!r:.50}`
- BUG-6 [HIGH]: alerts.html `d.daily.reduce()` 和 `d.top_servers[0]` 无 null 检查，API 返回空数据时 TypeError — 加 `||[]` 保护
- BUG-7 [MEDIUM]: scripts.html `doExec()` 缺少 `r.ok` 检查，服务端错误被静默吞掉 — 加错误处理
- BUG-8 [MEDIUM]: commands.html `s.id.substring(0,8)` 无 null 检查 — 改为 `(s.id||'').substring(0,8)`
- BUG-9 [MEDIUM]: `_server_to_dict()` 缺少 `last_checked_at` 和 `ssh_key_public` 字段 — 补充返回
- BUG-10 [MEDIUM]: 限速脚本 EXPIRE 每次请求重置窗口，精确间隔可绕过 — 改用 Lua 脚本原子 INCR+条件EXPIRE
- N-06 [HIGH]: install.php 的 ssh_key 列 VARCHAR(500) 存不下加密密钥 — 迁移加 MODIFY COLUMN → TEXT
- N-07 [HIGH]: servers 表新列缺迁移 (platform_id/node_id/protocols/extra_attrs/connectivity/last_checked_at/ssh_key_configured) — 迁移补全
- 清理: agent.py 冗余内联 import、servers.py/scripts.py 未使用 List 导入

**涉及文件**: web/app/scripts.html, web/app/alerts.html, web/app/commands.html, server/api/agent.py, server/api/servers.py, server/api/scripts.py, server/infrastructure/redis/script_callback_rate.py, server/infrastructure/database/migrations.py

### 深度扫描第五轮 Bug + 安全修复
- BUG-11 [HIGH]: self_monitor.py Telegram 告警无冷却，Redis/MySQL 宕机时每30秒刷屏 — 加5分钟冷却 + 恢复时清除冷却
- BUG-12 [HIGH]: schedule_runner.py 一次性调度执行超60秒会重复触发 — 执行前先设 last_run_at + disable
- BUG-13 [MEDIUM]: websocket.py 僵尸连接只从字典移除，TCP 未关 — disconnect() 中主动 close()
- BUG-14 [MEDIUM]: websocket.py `_last_alert_time` 字典无限增长 — 加1小时过期清理
- BUG-15 [HIGH]: asyncssh_pool.py 全忙时 pool 超过 MAX_CONNECTIONS 无限增长 — _evict_one 返回 bool + 全忙等待/拒绝
- SEC-1 [HIGH]: auth_jwt.py + main.py `INSTALL_MODE` 是导入时常量，安装后 JWT 认证仍禁用 — 改为动态函数 `_is_install_mode()`/`is_install_mode()`
- SEC-2 [MEDIUM]: install.py `/lock` 端点无保护，可在管理员创建前调用阻止安装 — 加管理员存在性验证

**涉及文件**: server/background/self_monitor.py, server/background/schedule_runner.py, server/api/websocket.py, server/infrastructure/ssh/asyncssh_pool.py, server/api/auth_jwt.py, server/main.py, server/api/install.py

**是否需迁移**: 是 — push_schedules 表新增 run_mode + fire_at 列，servers 表补全新列 + ssh_key 列类型修复（自动迁移）

**验证方式**:
- 一次性调度：创建后到 fire_at 时间自动触发并禁用
- 循环调度：cron 表达式匹配时触发，不自动禁用
- 健康检查：选服务器 → 点击按钮 → 显示结果
- 全局搜索：搜索结果点击跳转到对应页面并展开记录
- WebSSH：点击服务器终端 → 正常连接
- 路由遮蔽：服务器页面同步日志正常加载
- Cron 表达式：`*/5,1` 在分钟=1 时正确匹配
