feat: 新增设备画面截图、列表复选框/排序/批量停止,修复端口绑定失败
This commit is contained in:
+51
-20
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user