refactor: 架构优化-后端(SQLite WAL/迁移机制/健康检查/uiautodev孤儿清理/残留提示)
- SQLite 开 WAL + busy_timeout + synchronous=NORMAL,避免多 worker 并发写锁 - 新增版本化 schema 迁移机制(SCHEMA_MIGRATIONS + app_meta 表记录版本) - 新增 /api/health 健康检查(无需登录,返回设备/任务/错误摘要) - uiautodev 用 PID 文件追踪,启动时清理上次异常退出残留的孤儿进程 - 启动时提示残留的 STF 占用(不自动释放,避免多实例误伤)
This commit is contained in:
@@ -89,6 +89,30 @@ def logout():
|
||||
return redirect(url_for("login"))
|
||||
|
||||
|
||||
# ================== API:健康检查(无需登录,供探活) ==================
|
||||
@app.route("/api/health")
|
||||
def api_health():
|
||||
"""轻量健康检查:返回进程/设备/任务摘要,不暴露敏感信息,供运维探活。"""
|
||||
try:
|
||||
devices, err = mgr.get_status()
|
||||
devs = devices or []
|
||||
return jsonify({
|
||||
"ok": True,
|
||||
"status": "up",
|
||||
"time": time.time(),
|
||||
"device_total": len(devs),
|
||||
"device_online": sum(1 for d in devs if d.get("present")),
|
||||
"device_running": sum(1 for d in devs
|
||||
if d.get("worker_status") in ("running", "connecting")),
|
||||
"device_error": sum(1 for d in devs
|
||||
if d.get("worker_status") in ("error", "failed")),
|
||||
"occupied": sum(1 for d in devs if d.get("stf_occupied")),
|
||||
"jobs": len(mgr.jobs),
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "status": "down", "error": str(e)}), 500
|
||||
|
||||
|
||||
# ================== API:状态(监控大屏用)==================
|
||||
@app.route("/api/status")
|
||||
@login_required
|
||||
@@ -550,6 +574,35 @@ def api_apks_install_status():
|
||||
|
||||
# ================== uiautodev 自动启动 ==================
|
||||
_uiauto_proc = None
|
||||
# PID 文件:web_server 异常退出(kill -9/崩溃)时 uiautodev 成孤儿残留,
|
||||
# 用 PID 文件在下次启动时识别并清理,避免多次重启后堆积进程。
|
||||
_UIAUTO_PID_FILE = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "data", "uiauto.pid")
|
||||
|
||||
|
||||
def _kill_stale_uiauto():
|
||||
"""清理上次 web_server 残留的 uiautodev 进程(读取 PID 文件)。"""
|
||||
if not os.path.exists(_UIAUTO_PID_FILE):
|
||||
return
|
||||
try:
|
||||
with open(_UIAUTO_PID_FILE) as f:
|
||||
pid = int(f.read().strip())
|
||||
if pid > 0 and pid != os.getpid():
|
||||
try:
|
||||
os.kill(pid, 0) # 探测进程是否存活
|
||||
os.kill(pid, 15) # 终止残留
|
||||
_log.warning(f"已清理残留的 uiautodev 进程 (PID={pid})")
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
except PermissionError:
|
||||
_log.warning(f"无权限清理残留 uiautodev (PID={pid}),忽略")
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
os.remove(_UIAUTO_PID_FILE)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def _ensure_uiauto_running():
|
||||
@@ -559,6 +612,7 @@ def _ensure_uiauto_running():
|
||||
启动失败只记日志,不影响主服务。
|
||||
"""
|
||||
global _uiauto_proc
|
||||
_kill_stale_uiauto()
|
||||
if uiauto_helper.is_running():
|
||||
_log.info("uiauto2 服务已在运行 (端口 20242)")
|
||||
return
|
||||
@@ -572,6 +626,11 @@ def _ensure_uiauto_running():
|
||||
**kwargs,
|
||||
)
|
||||
_log.info(f"uiautodev 服务已启动 (PID={_uiauto_proc.pid})")
|
||||
try:
|
||||
with open(_UIAUTO_PID_FILE, "w") as f:
|
||||
f.write(str(_uiauto_proc.pid))
|
||||
except OSError:
|
||||
pass
|
||||
atexit.register(_stop_uiauto)
|
||||
except FileNotFoundError:
|
||||
_log.warning("uiautodev 未安装,元素抓取功能不可用。请运行: pip install uiautodev")
|
||||
@@ -582,6 +641,11 @@ def _ensure_uiauto_running():
|
||||
def _stop_uiauto():
|
||||
"""停止自动启动的 uiautodev 子进程。"""
|
||||
global _uiauto_proc
|
||||
try:
|
||||
if os.path.exists(_UIAUTO_PID_FILE):
|
||||
os.remove(_UIAUTO_PID_FILE)
|
||||
except OSError:
|
||||
pass
|
||||
if not _uiauto_proc:
|
||||
return
|
||||
try:
|
||||
@@ -634,6 +698,16 @@ if __name__ == "__main__":
|
||||
from config import WEB_HOST, WEB_PORT
|
||||
# 自动启动 uiautodev 服务(元素抓取功能依赖,端口 20242)
|
||||
_ensure_uiauto_running()
|
||||
# 启动时提示可能残留的 STF 占用(上次异常退出/强杀遗留)。
|
||||
# 不自动释放:本机与生产可能是多实例共用一个 STF 账户,误放会中断另一实例的任务。
|
||||
try:
|
||||
_mine = stf.list_my_devices()
|
||||
if _mine:
|
||||
_serials = ", ".join(d["serial"] for d in _mine)
|
||||
_log.warning(f"启动时发现 {len(_mine)} 台设备仍被本账户占用(可能异常退出残留):{_serials}。"
|
||||
f"确认无其他实例在跑时可用\"强制释放占用\"清理")
|
||||
except Exception:
|
||||
pass
|
||||
_log.info(f"管理后台: http://localhost:{WEB_PORT}/ (admin/admin123)")
|
||||
try:
|
||||
_run_server(WEB_HOST, WEB_PORT)
|
||||
|
||||
Reference in New Issue
Block a user