feat: 设备型号采集展示(devices 表 model 列+getprop 采集+管理页型号列/采集按钮+监控页兜底)+ fix: 监控页设备列错位(离线标签残留 td)、appver 端点误删恢复、appver 只查在线设备用轻量连接防挂起

This commit is contained in:
2026-08-19 13:53:27 +08:00
parent eda71c8e5f
commit 6f6093842c
8 changed files with 227 additions and 21 deletions
+80 -2
View File
@@ -11,8 +11,10 @@
后台线程(task_manager 等)调用时由本模块自行推 app context,调用方无需关心。
"""
import time
import threading
import subprocess
from config import USB_ADB_HOST, USB_ADB_PORT
from config import ADB_PATH, USB_ADB_HOST, USB_ADB_PORT
from core.adb_helper import _adb, _adb_remote
from core.logger import get_logger
from core.models import db, Device
@@ -23,9 +25,24 @@ _app = None
def init_app(app):
"""web_server 启动时调用:绑定 Flask app(供后台线程推 db context)。"""
"""web_server 启动时调用:绑定 Flask app(供后台线程推 db context),
并后台刷新一次设备型号(首次启动/设备更换后型号可能变化)。"""
global _app
_app = app
try:
t = threading.Thread(target=_refresh_models_bg, daemon=True)
t.start()
except Exception:
pass
def _refresh_models_bg():
"""后台批量采集在线设备型号(启动时/手动触发)。失败静默,不影响启动。"""
time.sleep(3) # 等服务起来再查
try:
refresh_all_models()
except Exception as e:
_log.warning(f"启动型号采集失败(可稍后手动采集): {e}")
def _ctx():
@@ -133,3 +150,64 @@ def set_enabled(serial, enabled):
d.enabled = enabled
db.session.commit()
return True
# ================== 型号采集 ==================
def refresh_model(serial, timeout=8):
"""采集单台设备型号(getprop ro.product.model)并写回 DB。
IP:5555 设备走本机 adb;USB 设备先查本机 adb,查不到再查 220 远程 adb server。
采集失败返回空串(静默,不影响主流程)。返回采集到的型号。
"""
if not serial:
return ""
model = ""
if ":" in serial:
try:
r = subprocess.run([ADB_PATH, "-s", serial, "shell",
"getprop", "ro.product.model"],
capture_output=True, timeout=timeout)
model = (r.stdout or b"").decode("utf-8", errors="replace").strip()
except Exception:
model = ""
else:
# USB:先本机,再远程
try:
r = subprocess.run([ADB_PATH, "-s", serial, "shell",
"getprop", "ro.product.model"],
capture_output=True, timeout=timeout)
model = (r.stdout or b"").decode("utf-8", errors="replace").strip()
except Exception:
model = ""
if not model:
model = _adb_remote(USB_ADB_HOST, USB_ADB_PORT, "-s", serial,
"shell", "getprop", "ro.product.model")
model = model.strip() or ""
if not model:
return ""
with _ctx():
d = Device.query.get(serial)
if d and d.model != model:
d.model = model
db.session.commit()
_log.info(f"设备池采集型号: {serial} -> {model}")
return model
def refresh_all_models():
"""批量采集池内在线设备的型号(并发 10,后台线程调用)。返回成功数。"""
serials = list_online()
if not serials:
return 0
from concurrent.futures import ThreadPoolExecutor, as_completed
ok = 0
with ThreadPoolExecutor(max_workers=min(10, len(serials))) as pool:
futures = {pool.submit(refresh_model, s): s for s in serials}
for fut in as_completed(futures):
try:
if fut.result():
ok += 1
except Exception:
pass
_log.info(f"设备池批量采集型号完成: {ok}/{len(serials)} 台")
return ok