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
+6 -2
View File
@@ -220,17 +220,19 @@ class Device(db.Model):
"""设备池(本地设备清单,替代 STF 池作为调度数据源)。
serial 即 adb 序列号(IP:5555 或 USB 序列号);enabled=False 不参与调度。
model 为在线时自动采集的型号(如 Redmi 12C),供管理页/监控页区分设备。
"""
serial = db.Column(db.String(120), primary_key=True)
name = db.Column(db.String(80), default="") # 备注名(可选)
model = db.Column(db.String(120), default="") # 型号(自动采集)
enabled = db.Column(db.Boolean, default=True) # 是否参与调度
note = db.Column(db.Text, default="") # 备注
created_at = db.Column(db.String(20), default="") # 添加时间
def to_dict(self):
return {"serial": self.serial, "name": self.name or "",
"enabled": bool(self.enabled), "note": self.note or "",
"created_at": self.created_at or ""}
"model": self.model or "", "enabled": bool(self.enabled),
"note": self.note or "", "created_at": self.created_at or ""}
def __repr__(self):
return f"<Device {self.serial}>"
@@ -248,6 +250,8 @@ SCHEMA_MIGRATIONS = [
"enabled BOOLEAN DEFAULT 1,"
"note TEXT DEFAULT '',"
"created_at VARCHAR(20) DEFAULT '')"),
(3, "设备池:device 表新增 model 列(型号,在线时自动采集)",
"ALTER TABLE device ADD COLUMN model TEXT DEFAULT ''"),
]
+5 -3
View File
@@ -824,9 +824,11 @@ class TaskManager:
except Exception:
online = set()
try:
names = {d["serial"]: d["name"] for d in device_pool.list_devices()}
pool_rows = device_pool.list_devices()
names = {d["serial"]: d["name"] for d in pool_rows}
models = {d["serial"]: d["model"] for d in pool_rows}
except Exception:
names = {}
names, models = {}, {}
# 清理陈旧状态:serial 已不在设备池、且没有在跑 worker 的条目,
# 避免设备被删除后其失败记录仍残留在"异常汇总"里
@@ -845,7 +847,7 @@ class TaskManager:
r = running.get(serial, {})
result.append({
"serial": serial,
"model": w.get("model", ""),
"model": w.get("model") or models.get(serial, ""),
"device_name": names.get(serial, ""),
"present": is_online,
"ready": is_online, # 阶段 1:ready 概念并入在线状态