fix: 回归脚本 Windows 可用 + 加"禁止对生产库跑"安全闸;迁移脚本去掉 GBK 控制台会崩的符号

- scripts/regression_test.py:
  * signal.alarm 加 hasattr 守卫(Windows 上直接 AttributeError 退出,
    backlog A4)→ 本机终于能跑"一条命令扫全接口 500"
  * 新增安全闸:.env 声明 prod、或目标库 app_meta.deployment_env=prod 时
    **拒绝运行**(exit 3)。本脚本会发真实写请求(设备池增删/剪贴板注入/
    亮灭屏),跑在生产配置上就是拿正式数据做实验
- scripts/migrate_sqlite_to_mysql.py: stdout 重设为 UTF-8 并去掉 ✔/✘ 符号。
  实测在 GBK 控制台里打印 ✔ 会抛 UnicodeEncodeError,而且崩在"写库标签"之前,
  看起来像迁移失败(数据其实已搬完)
- doc/backlog/TODO.md: A4 移入已完成;gitignore 条目更新(mcp_audit.log 已补,
  uiauto.pid 仍缺);登记 MySQL 迁移与回归安全闸

实测(对 192.168.2.27 的 auto_control_dev):数据迁移 12 张表逐表 SHA-256 一致;
应用直连 MySQL 全部接口 200,设备名/指纹唯一索引语义与 SQLite 一致(空值可重复、
非空重复被拦、大小写敏感 A08≠a08);备份导出→预览→应用往返正常。
This commit is contained in:
2026-09-13 10:48:36 +08:00
parent f5fc96c300
commit da77d8eb6b
3 changed files with 40 additions and 13 deletions
+5 -9
View File
@@ -32,14 +32,6 @@
- **涉及**:`web_server.py`(注册钩子)+ 需要回归所有写接口(前端、脚本、MCP 的 `platform_client` 都已带 token,风险点是第三方直接调 API)。
- **验收**:注册后所有写接口在缺 token 时返回 403,且前端/MCP 全链路正常。
### A4. 路由回归脚本在 Windows 上不可用
- **现象**:`python scripts/regression_test.py` 直接报 `AttributeError: module 'signal' has no attribute 'alarm'` 退出。
- **原因**:脚本用了 Unix 专有的 `signal.alarm`(第 29 行),docstring 里的示例路径也是 `.venv/bin/python`。
- **影响**:本机(Windows)开发时没有"一条命令扫全接口 500"的手段——A1/A2 这类 import 遗漏本可被它第一时间发现。
- **涉及**:`scripts/regression_test.py`(把 `signal.alarm` 换成跨平台超时,或 `hasattr` 守卫)。
- **验收**:Windows 与 Linux 都能跑;能扫出 A1。
### A5. 前端 `--card-line` 变量未定义
- **现象**:AI 控制台的会话/经验/动作模态框边框失效(`border:1px solid var(--card-line)` 解析为无效值)。
@@ -74,7 +66,7 @@
- `ANDROID_ADB_SERVER_ADDRESS/HOST/PORT` 会被 `.env` 注入 `os.environ`,**adb 客户端**据此把所有调用指向 220;`ANDROID_ADB_SERVER_HOST` 是非标准键(adb 不认)。
- 期望:在配置速查里写明该行为与取舍,或本地去掉这三行改走本机 adb。
- [ ] **`.gitignore` 漏项**:`data/mcp_audit.log` 与 `data/uiauto.pid` 未被忽略(`git status` 会显示为未跟踪);`data/apks/` 目录无占位文件。
- [ ] **`.gitignore` 漏项**:`data/mcp_audit.log` 已补(`data/*.log`,2026-09-13);**`data/uiauto.pid` 仍未忽略**(`git status` 会显示为未跟踪);`data/apks/` 目录无占位文件。
- [ ] 220 的 Tailscale / adb server(5037) 可达性巡检(曾出现连不通超时)。
@@ -129,3 +121,7 @@
- [x] 监控页任务卡去掉「编辑/删除」,只留执行/停用 + 显示**覆盖设备**。
- [x] 备份覆盖清单补 `agent_action` / `app_meta` + 导出/导入双向自检。
- [x] AI 控制台:Markdown 渲染、推理链可折叠、token 用量显示。
- [x] 路由回归脚本 Windows 可用(2026-09-13:`signal.alarm` 加 `hasattr` 守卫)+ 新增安全闸:
配置或目标库被标为 `prod` 时**拒绝运行**(脚本会发真实写请求,不能拿生产数据做实验)。
- [x] 数据库从 SQLite 迁到 MySQL:连接层多环境配置 + 防混库校验、schema 收敛到 ORM metadata、
备份/恢复改为「SQLite 归档 + 单事务整库替换」、迁移脚本(2026-09-13)。
+10 -2
View File
@@ -40,6 +40,14 @@ import uuid
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ROOT)
# Windows 控制台默认 GBK,输出里带 ✔ 之类的符号会直接抛 UnicodeEncodeError
# (本脚本是运维在终端里手跑的,崩在最后一步会让人以为迁移失败)
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
from sqlalchemy import inspect as sa_inspect, text as sa_text # noqa: E402
@@ -306,7 +314,7 @@ def main():
with app.app_context():
stats, ok = copy_and_verify(args.sqlite, tables, "verify")
print_stats(stats)
print("\n校验结果: " + ("全部一致 ✔" if ok else "存在不一致 ✘"))
print("\n校验结果: " + ("全部一致 OK" if ok else "存在不一致 FAIL"))
sys.exit(0 if ok else 1)
# 目标库环境标签检查(防止误把 dev 数据灌进生产库)
@@ -331,7 +339,7 @@ def main():
print_stats(stats)
if not ok:
_fail("搬运后校验不一致 —— 事务已回滚,目标库保持原样。请把上面的表格发给我排查")
print(f"\n搬运完成,用时 {time.time() - t0:.1f}s,逐表校验一致 ✔")
print(f"\n搬运完成,用时 {time.time() - t0:.1f}s,逐表校验一致 OK")
# 4) 写库标签与版本
src = sqlite3.connect(_readonly_uri(args.sqlite), uri=True)
+25 -2
View File
@@ -25,11 +25,34 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# 干扰回归/占用设备——实测 ocr关键字任务每 30 分钟触发导致回归超时)
os.environ["DISABLE_SCHEDULER"] = "1"
# 单接口超时保护:adb 相关接口可能慢(连接/查询设备),给足时间
signal.alarm(300)
# 单接口超时保护:adb 相关接口可能慢(连接/查询设备),给足时间。
# Windows 没有 signal.alarm(会 AttributeError 直接崩),那里就靠调用方自己掐时间。
if hasattr(signal, "alarm"):
signal.alarm(300)
from web_server import app # noqa: E402 (导入即完成 app 装配,与生产一致)
# ================== 安全闸:绝不对生产库跑写操作 ==================
# 本脚本会发真实写请求(设备池增删、剪贴板注入、亮灭屏…)。跑在连生产库的配置上
# 就是拿正式数据做实验——直接拒绝启动。
from core import db_config # noqa: E402
if db_config.DEPLOY_ENV == "prod":
print("=" * 70)
print("拒绝运行:当前 .env 声明 DEPLOY_ENV=prod,本脚本会发真实写请求。")
print("回归测试请在 dev 环境跑(或先把 .env 指向 dev 库/临时库)。")
print("=" * 70)
sys.exit(3)
with app.app_context():
_labeled = db_config.meta_get("deployment_env")
if _labeled == "prod":
print("=" * 70)
print("拒绝运行:目标库 %s 被登记为 prod 环境(app_meta.deployment_env)。"
% db_config.describe_target())
print("回归测试会写数据,不能对生产库执行。")
print("=" * 70)
sys.exit(3)
# 流式/二进制接口:无法用普通 GET 断言,跳过(已有单独验证路径)
_SKIP_PREFIXES = (