diff --git a/Module/FlaskService.py b/Module/FlaskService.py index 9626208..53b9d2b 100644 --- a/Module/FlaskService.py +++ b/Module/FlaskService.py @@ -317,7 +317,7 @@ def deviceList(): @app.route('/passToken', methods=['POST']) def passToken(): data = request.get_json() - print(data) + print(json.dumps(data)) return ResultData(data="").toJson() # 获取设备应用列表 diff --git a/Module/Main.py b/Module/Main.py index e7f5faf..35694d7 100644 --- a/Module/Main.py +++ b/Module/Main.py @@ -1,10 +1,20 @@ - +import asyncio # ===== Main.py 顶部放置(所有 import 之前)===== import os import sys from pathlib import Path + +from asgiref.wsgi import WsgiToAsgi + +from Utils.AiUtils import AiUtils from Utils.LogManager import LogManager -import logging +from hypercorn.asyncio import serve +from hypercorn.config import Config +import sys +from pathlib import Path +from Module.DeviceInfo import DeviceInfo +from Module.FlaskSubprocessManager import FlaskSubprocessManager +from Utils.DevDiskImageDeployer import DevDiskImageDeployer if "IOSAI_PYTHON" not in os.environ: base_path = Path(sys.argv[0]).resolve() @@ -14,13 +24,6 @@ if "IOSAI_PYTHON" not in os.environ: os.environ["IOSAI_PYTHON"] = str(sidecar) # ============================================== -import sys -from pathlib import Path - -from Module.DeviceInfo import DeviceInfo -from Module.FlaskSubprocessManager import FlaskSubprocessManager -from Utils.DevDiskImageDeployer import DevDiskImageDeployer - # 确定 exe 或 py 文件所在目录 BASE = Path(getattr(sys, 'frozen', False) and sys.executable or __file__).resolve().parent LOG_DIR = BASE / "log" @@ -33,8 +36,32 @@ def _run_flask_role(): print("Flask Pid:", os.getpid()) port = int(os.getenv("FLASK_COMM_PORT", "34566")) # 固定端口的兜底仍是 34567 app = get_app() + flaskPort = port + 1 + AiUtils.flask_port_free(flaskPort) bootstrap_server_side_effects() - app.run(host="0.0.0.0", port=port + 1, debug=False, use_reloader=False, threaded=True) + + # 把 WSGI Flask app 包成 ASGI app + asgi_app = WsgiToAsgi(app) + + # Hypercorn 配置 + # 自动定位 resources 目录 + base_dir = os.path.dirname(os.path.abspath(__file__)) # 当前 py 的目录(Module/) + project_root = os.path.dirname(base_dir) # 回到项目根目录(iOSAi/) + resource_dir = os.path.join(project_root, "resources") # 拼到 resources + + config = Config() + config.bind = [f"0.0.0.0:{flaskPort}"] + config.alpn_protocols = ["h2"] # 开 HTTP/2 + config.certfile = os.path.join(resource_dir, "cert.pem") + config.keyfile = os.path.join(resource_dir, "key.pem") + + print(f"Starting Hypercorn on port {flaskPort} (HTTP/2 enabled)") + + # 开启 HTTP/2 + config.alpn_protocols = ["h2"] + + print(f"Starting Hypercorn on https://localhost:{flaskPort} (HTTP/2 enabled)") + asyncio.run(serve(asgi_app, config)) if "--role=flask" in sys.argv: _run_flask_role() @@ -51,7 +78,7 @@ if __name__ == "__main__": # 清空日志 LogManager.clearLogs() - # main(sys.argv) + main(sys.argv) # 添加iOS开发包到电脑上 deployer = DevDiskImageDeployer(verbose=True) diff --git a/Utils/AiUtils.py b/Utils/AiUtils.py index 8259bca..ed8b40c 100644 --- a/Utils/AiUtils.py +++ b/Utils/AiUtils.py @@ -2,7 +2,11 @@ import html import json import os import re +import signal +import socket import subprocess +import sys +import time import xml.etree.ElementTree as ET from pathlib import Path import cv2 @@ -62,6 +66,56 @@ class AiUtils(object): # print(e) # return -1, -1 + @classmethod + def flask_port_free(cls,port): + """无需 psutil 的版本,通过系统命令查 PID""" + + def can_bind(p): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.bind(("0.0.0.0", p)) + s.close() + return True + except OSError: + s.close() + return False + + if can_bind(port): + return + + print(f"[ensure_port_free] Port {port} is occupied. Searching PID...") + + pids = set() + + if sys.platform.startswith("darwin") or sys.platform.startswith("linux"): + cmd = f"lsof -t -i:{port}" + proc = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + for line in proc.stdout.splitlines(): + if line.strip().isdigit(): + pids.add(int(line.strip())) + + elif sys.platform.startswith("win"): + cmd = f"netstat -ano | findstr :{port}" + proc = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + for line in proc.stdout.splitlines(): + parts = line.split() + if len(parts) >= 5 and parts[-1].isdigit(): + pids.add(int(parts[-1])) + + else: + raise RuntimeError("Unsupported platform for ensure_port_free") + + for pid in pids: + try: + print(f"[ensure_port_free] Killing PID {pid}...") + os.kill(pid, signal.SIGKILL) + except Exception as e: + print(f"[ensure_port_free] Failed to kill PID {pid}: {e}") + + time.sleep(0.3) + if not can_bind(port): + raise RuntimeError(f"[ensure_port_free] Port {port} still occupied after kill.") + @classmethod def findImageInScreen(cls, target, udid): try: diff --git a/Utils/LogManager.py b/Utils/LogManager.py index c1432e0..9a81fe5 100644 --- a/Utils/LogManager.py +++ b/Utils/LogManager.py @@ -26,7 +26,7 @@ def _force_utf8_everywhere(): except Exception: pass -# _force_utf8_everywhere() +_force_utf8_everywhere() class LogManager: """ diff --git a/build.bat b/build.bat index ab5167e..01fb3f0 100644 --- a/build.bat +++ b/build.bat @@ -9,7 +9,7 @@ python -m nuitka Module\Main.py ^ --windows-console-mode=disable ^ --output-filename=IOSAI ^ --include-package=Module,Utils,Entity,script ^ - --include-module=flask,wda,psutil,portalocker,flask_cors,cv2,lxml.etree,requests,urllib3,certifi,idna,setuptools ^ + --include-module=flask,wda,psutil,portalocker,flask_cors,cv2,lxml.etree,requests,urllib3,certifi,idna,setuptools,asgiref,hypercorn,h2,hpack,wsproto,priority,anyio,sniffio ^ --include-data-dir=resources=resources ^ --include-data-dir=SupportFiles=SupportFiles ^ --include-data-files="resources/iproxy/*=resources/iproxy/" ^ diff --git a/resources/cert.pem b/resources/cert.pem new file mode 100644 index 0000000..baa7f31 --- /dev/null +++ b/resources/cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDizCCAnOgAwIBAgIUXpRsBS0IBEvAw22Ii/wtu/gdooUwDQYJKoZIhvcNAQEL +BQAwXTELMAkGA1UEBhMCQ04xDjAMBgNVBAgMBUxvY2FsMQ4wDAYDVQQHDAVMb2Nh +bDEMMAoGA1UECgwDRGV2MQwwCgYDVQQLDANEZXYxEjAQBgNVBAMMCWxvY2FsaG9z +dDAeFw0yNTExMTcwODI2MzdaFw0yNjExMTcwODI2MzdaMF0xCzAJBgNVBAYTAkNO +MQ4wDAYDVQQIDAVMb2NhbDEOMAwGA1UEBwwFTG9jYWwxDDAKBgNVBAoMA0RldjEM +MAoGA1UECwwDRGV2MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCixGQJ0gdzcQoqdOv4lLvfH5Kz4C/t4/WCZfheF3Z7 +cYiog0Ql8URyn6bF8ux97X4TtpJ621jM/lfxc2hKrqXpbsyO2EKgT3OEpuv/lyqC +YQqbHUUIAiXI9OF/iAANY5rBIeEDEUhcH4Ngt6EJ1FZdU+tfgV8V9zNUSeSJ+VtF +C+0LsTyWy7eqnXkXnPZvitVeZU85Zy5lWdC1mp9cOoyElmYuGxIQkW6ZtEzMjVp4 +Eim3RsZgk6ZYRAdMGfdaa6YrmDDqhEZVEEL55dstOqfIWKUppazC9HSs7FnGsaUn +xkdqOArACdhU9y3f+yyeLC93Xllx9kgFfvLueysUqSgdAgMBAAGjQzBBMCAGA1Ud +EQQZMBeCCWxvY2FsaG9zdIcEfwAAAYcEwKgB2jAdBgNVHQ4EFgQURY1BDcpSTUNO +D0olM9H84Gu/QQ0wDQYJKoZIhvcNAQELBQADggEBAFrCAqIlpzncH6owBImN8Ub5 +8ZwtTm+C3nQZF5FkCdsXHfqtPTEk4bX7IFHaj7saqroCYXfgopzvk2QX16wlPwk2 +SKA/pF6I2bNNozlcVN9QAf9ue6xa8g8AxwPT46gbKTKFyG5lg1umYXhCGKVIJ/1l +B4Bh8KmPfzNWxiKOzflGNx5j1BHPZ9S7jt9wtiEwENceGZXVE8ANMiNR44+suuM7 +6/syQUetPN+VWW+/14OrDeYQDLZTbUVigY75KIuLF41PxNWG745Qlcu/5nmvBnHv +5NNm2Zs8GYKLqLIlUQNU8x0R03FBLCYjDKRJsNpsZPqjI5cDSPj5vHZrzD3Jh0s= +-----END CERTIFICATE----- diff --git a/resources/key.pem b/resources/key.pem new file mode 100644 index 0000000..f019759 --- /dev/null +++ b/resources/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCixGQJ0gdzcQoq +dOv4lLvfH5Kz4C/t4/WCZfheF3Z7cYiog0Ql8URyn6bF8ux97X4TtpJ621jM/lfx +c2hKrqXpbsyO2EKgT3OEpuv/lyqCYQqbHUUIAiXI9OF/iAANY5rBIeEDEUhcH4Ng +t6EJ1FZdU+tfgV8V9zNUSeSJ+VtFC+0LsTyWy7eqnXkXnPZvitVeZU85Zy5lWdC1 +mp9cOoyElmYuGxIQkW6ZtEzMjVp4Eim3RsZgk6ZYRAdMGfdaa6YrmDDqhEZVEEL5 +5dstOqfIWKUppazC9HSs7FnGsaUnxkdqOArACdhU9y3f+yyeLC93Xllx9kgFfvLu +eysUqSgdAgMBAAECggEAGY29qvEHbG9Vyj6bAWbQbAI39PeAbte4JqW9rX//gPfd +HZ+mJlLPjTNVaoRt7oNHpO6n5pPjSCOySNz2hasPrytPACoho6t1lmDicjkYWmnD +0YBx4wT7S6ZudKg0YeW+WQ3plqKy+ouUA64woStt968CJ/dWp0stCtGjCKpWUuuB +TXvAUM91qrQlpIuqQI0QYRcKaoKPV6IckWPGeLvKi8EhVOFCNdya8jTP/ayxT06r +/cNucO+Tqe0PUe6jet6Ecx7Av4h8QXg9FMoF40RKxD6Q+sK/bM1c51KvUZVt9v4F +epAHjQyrzPjUozjtKzQvibexBvSwz/XaFCDgr7OwPQKBgQDOJtILcxjJDZfk8LUG +GNF8XVtgDu2FFVw79MSuQgERC+w2b+P2feffwdI4vu2+8jkWCHnttXNgdrS3r41C +gak3Xb2Jzx8M06eNC9QCtjicrWNTmJtcp5YZm1vS+fAPQG4DPLK+J4EmLNNyQ9nC +0vMfYRwKr3TX5FU6MyJbAsVnkwKBgQDKH/zUhMPdN6+0upEVoUJrI3BOhVdN8eEp +eqNWYU2Hhmg5mHsnoxz8Rjxw5ZKx108BT1JNea/rrNgOhe8TTpiAIVl5ZMN0OcO2 +INlfteCtXY5nzU8HilfFBPwroR8Msx662GpM8TRA9RfTGJ7nKmiXhC1jnmkPecwX ++f+LaLCfjwKBgQC11H3dxXYuF8RLFZjFuOxFIl7vOht8D9wbsggsn2ErdPWzCjvq +9SCpNt7CWH2At0tsyKsq5KnQgsNhZQFWkOD9SbxdKgf8G0+k07L7dVg3saNzX55h +OhvlmCeEzhlUioK+bjJGELgUQON73Kbc9Y2lttSyBBIuPmKCBAogdjBB6wKBgGnM +VIro85zXiSEQhuDLh/iMlDyFjy09bp5HkzejtvE5aVS8e7pDpuhl2z087YwpJzGI +U4w6Jds2neD8Oifg+/IVgsAH/kbX9ZlfmGiAyxnz3pZ24OcRgt+dvGEZ9Sawm2Ux +4nJjzvYxVEcqnAJkMFse1KNQR63SEwJ52Ukfg1QBAoGBAKByisJiMTi/gmSlPFf6 +3ZfxCd5ReMS/Ak4JIBN4S+GoAZp6yMZy1jYFHqV2k3slhmcswy+V9DGxtldHlDu9 ++90zV1btfaB3nq5ydkmj32SU9MRqpzP4axHYTi8cvmOBlOEpL5e7KWTg072rGHAC ++ih8VPn9LTKk2GCtCCp4r2jI +-----END PRIVATE KEY----- diff --git a/script/ScriptManager.py b/script/ScriptManager.py index 22e67c5..b7216ac 100644 --- a/script/ScriptManager.py +++ b/script/ScriptManager.py @@ -669,11 +669,7 @@ class ScriptManager(): event.wait(timeout=2) session.appium_settings({"snapshotMaxDepth": 12}) - LogManager.method_info(f"检查当前是否为视频页面", "关注打招呼", udid) - - is_back_enabled = ControlUtils.isClickBackEnabled(session) - # 最多尝试 3 次(第一次 + 再试两次) for attempt in range(3): is_back_enabled = ControlUtils.isClickBackEnabled(session)