feat: 新增设备画面截图、列表复选框/排序/批量停止,修复端口绑定失败

This commit is contained in:
2026-08-07 15:08:36 +08:00
parent bce48fc137
commit 0fe2a2fc5f
7 changed files with 355 additions and 55 deletions
+51 -20
View File
@@ -15,7 +15,7 @@
import os
import time
from flask import Flask, jsonify, request, redirect, url_for, render_template
from flask import Flask, jsonify, request, redirect, url_for, render_template, Response
from flask_login import (LoginManager, login_user, logout_user,
login_required, current_user)
@@ -24,7 +24,7 @@ from core.task_manager import TaskManager
from core.logger import get_logger, _LOG_DIR, _MODULE_FILES
from core.models import db, init_db, User, DeviceGroup, TaskJob
from core.apk_manager import ApkManager
from core.adb_helper import identify_device
from core.adb_helper import screenshot
from tasks import list_task_types, get_task_class
_log = get_logger("web")
@@ -341,17 +341,24 @@ def api_release():
return jsonify({"ok": True, "released": released})
@app.route("/api/device/identify", methods=["POST"])
@app.route("/api/device/screenshot")
@login_required
def api_device_identify():
"""让设备响铃以供识别(直连 serial,不经过 STF)。"""
serial = (request.json or {}).get("serial", "")
def api_device_screenshot():
"""获取设备当前画面截图(PNG)。
用 adb exec-out screencap -p,只读操作,不抢占 u2 的 atx-agent 通道,
任务运行中调用安全。直接返回 image/png,前端用 <img> 加载。
?serial=xxx 设备 serial
?t=123 时间戳,避免浏览器缓存(前端自动加)
"""
serial = request.args.get("serial", "")
if not serial:
return jsonify({"ok": False, "error": "缺少 serial"}), 400
ok, msg = identify_device(serial)
ok, data = screenshot(serial)
if ok:
return jsonify({"ok": True, "msg": msg})
return jsonify({"ok": False, "error": msg}), 500
return Response(data, mimetype="image/png",
headers={"Cache-Control": "no-store"})
return jsonify({"ok": False, "error": data}), 500
# ================== API:应用管理(APK 上传/安装)==================
@@ -407,20 +414,44 @@ def api_apks_install_status():
return jsonify({"ok": True, "status": status})
def _run_server(host, port):
"""启动 Flask,自动处理端口冲突和权限问题。
常见失败原因:
WinError 10013 — 端口在 Windows 动态端口范围内被出站连接占用,
或非管理员绑定 0.0.0.0。前者换端口,后者降级 127.0.0.1。
WinError 10048 — 端口已被其他进程监听。换端口重试。
策略:原端口失败 → 试 127.0.0.1:原端口 → 试 127.0.0.1:原端口+1..+5
"""
import socket
# 先探测原端口是否可用(避免 flask 报错打日志难看)
candidates = [(host, port)]
if host == "0.0.0.0":
candidates.append(("127.0.0.1", port))
for i in range(1, 6):
candidates.append(("127.0.0.1", port + i))
for h, p in candidates:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((h, p))
# 探测成功,启动 flask
if (h, p) != (host, port):
_log.warning(f"原端口 {host}:{port} 不可用,改用 {h}:{p}")
_log.info(f"启动服务: http://localhost:{p}/")
app.run(host=h, port=p, debug=False, threaded=True)
return
except OSError as e:
_log.warning(f"绑定 {h}:{p} 失败: {e}")
# 全部失败
raise OSError(f"无法绑定任何候选端口({host}:{port} 及 127.0.0.1:{port}~{port+5})")
if __name__ == "__main__":
from config import WEB_HOST, WEB_PORT
_log.info(f"管理后台: http://localhost:{WEB_PORT}/ (admin/admin123)")
try:
try:
app.run(host=WEB_HOST, port=WEB_PORT, debug=False, threaded=True)
except OSError as e:
# 0.0.0.0 在 Windows 非管理员下会触发 WinError 10013
# 降级到 127.0.0.1 保证本机至少能访问,局域网访问需用管理员启动
if WEB_HOST == "0.0.0.0":
_log.warning(f"绑定 {WEB_HOST} 失败({e}),降级到 127.0.0.1(仅本机可访问)")
_log.warning("如需局域网访问,请用管理员身份运行 start_web.bat")
app.run(host="127.0.0.1", port=WEB_PORT, debug=False, threaded=True)
else:
raise
_run_server(WEB_HOST, WEB_PORT)
finally:
mgr.shutdown()