diff --git a/.idea/iOSAI.iml b/.idea/iOSAI.iml index 894b6b0..6cb8b9a 100644 --- a/.idea/iOSAI.iml +++ b/.idea/iOSAI.iml @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index c27b771..db8786c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 7bdb874..0f5a39b 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,7 +4,10 @@ - + + + + - - - - + - + - - - - - - - + + + - @@ -310,10 +279,6 @@ - - - - - - - - - - - @@ -403,25 +344,22 @@ - + - - + - - - + + \ No newline at end of file diff --git a/Entity/ResultData.py b/Entity/ResultData.py index 31495c5..cf50762 100644 --- a/Entity/ResultData.py +++ b/Entity/ResultData.py @@ -2,15 +2,15 @@ import json # 返回数据模型 class ResultData(object): - def __init__(self, code=200, data=None, massage="获取成功"): + def __init__(self, code=200, data=None, message="获取成功"): super(ResultData, self).__init__() self.code = code self.data = data - self.massage = massage + self.message = message def toJson(self): return json.dumps({ "code": self.code, "data": self.data, - "massage": self.massage + "message": self.message }, ensure_ascii=False) # ensure_ascii=False 确保中文不会被转义 \ No newline at end of file diff --git a/Module/DeviceInfo.py b/Module/DeviceInfo.py index a52d3a9..dde3e40 100644 --- a/Module/DeviceInfo.py +++ b/Module/DeviceInfo.py @@ -4,12 +4,12 @@ import signal import sys import time from concurrent.futures import ThreadPoolExecutor, as_completed - -import wda -import threading -import subprocess from pathlib import Path from typing import List, Dict, Optional +import threading +import subprocess + +import wda from tidevice import Usbmux, ConnectionType from tidevice._device import BaseDevice from Entity.DeviceModel import DeviceModel @@ -23,10 +23,8 @@ class Deviceinfo(object): """设备生命周期管理:以 deviceModelList 为唯一真理源""" def __init__(self): - ... - # ✅ 新增:连接线程池(最大 6 并发) + # ✅ 连接线程池(最大 6 并发) self._connect_pool = ThreadPoolExecutor(max_workers=6) - ... if os.name == "nt": self._si = subprocess.STARTUPINFO() @@ -44,11 +42,14 @@ class Deviceinfo(object): self._lock = threading.Lock() self._model_index: Dict[str, DeviceModel] = {} # udid -> model - # ✅ 1. 失踪时间戳记录(替代原来的 miss_count) + # ✅ 失踪时间戳记录(替代原来的 miss_count) self._last_seen: Dict[str, float] = {} self._port_pool: List[int] = [] self._port_in_use: set[int] = set() + # ✅ 新增:全局 iproxy 进程注册表 udid -> Popen + self._iproxy_registry: Dict[str, subprocess.Popen] = {} + # region iproxy 初始化(原逻辑不变) try: self.iproxy_path = self._iproxy_path() @@ -76,6 +77,9 @@ class Deviceinfo(object): args = [str(self.iproxy_path), "-u", udid, str(local_port), str(remote_port)] p = subprocess.Popen(args, **self._popen_kwargs) + # ✅ 注册到全局表 + self._iproxy_registry[udid] = p + def _pipe_to_log(name: str, stream): try: for line in iter(stream.readline, ''): @@ -127,6 +131,13 @@ class Deviceinfo(object): for udid in need_remove: self._remove_model(udid) + # ✅ 实时清理孤儿 iproxy(原 10 秒改为每次循环) + self._cleanup_orphan_iproxy() + + # ✅ 设备全空时核平所有 iproxy + if not self.deviceModelList: + self._kill_all_iproxy() + # 2. 发现新设备 → 并发连接 with self._lock: new_udids = [d.udid for d in lists @@ -146,7 +157,7 @@ class Deviceinfo(object): time.sleep(1) # ------------------------------------------------------------------ - # ✅ 3. USB 层枚举 SN(跨平台) + # ✅ USB 层枚举 SN(跨平台) # ------------------------------------------------------------------ def _usb_enumerate_sn(self) -> set[str]: try: @@ -155,7 +166,32 @@ class Deviceinfo(object): except Exception: return set() - # ===================== 以下代码与原文件完全一致 ===================== + # ---------------------------------------------------------- + # ✅ 清理孤儿 iproxy + # ---------------------------------------------------------- + def _cleanup_orphan_iproxy(self): + live_udids = set(self._model_index.keys()) + for udid, proc in list(self._iproxy_registry.items()): + if udid not in live_udids: + LogManager.warning(f"发现孤儿 iproxy 进程,UDID 不在线:{udid},正在清理") + self._terminate_proc(proc) + self._iproxy_registry.pop(udid, None) + + # ---------------------------------------------------------- + # ✅ 核平所有 iproxy(Windows / macOS 通用) + # ---------------------------------------------------------- + def _kill_all_iproxy(self): + try: + if os.name == "nt": + subprocess.run(["taskkill", "/F", "/IM", "iproxy.exe"], check=False) + else: + subprocess.run(["pkill", "-f", "iproxy"], check=False) + self._iproxy_registry.clear() + LogManager.info("已强制清理所有 iproxy 进程") + except Exception as e: + LogManager.warning(f"强制清理 iproxy 失败:{e}") + + # -------------------- 以下代码与原文件完全一致 -------------------- def _wda_health_checker(self): while True: time.sleep(1) @@ -229,6 +265,11 @@ class Deviceinfo(object): print(f"【删】待杀进程数 count={len(to_kill)} udid={udid}") LogManager.method_info(f"【删】待杀进程数 count={len(to_kill)} udid={udid}", method="device_count") + # ✅ 先清理注册表中的 iproxy + iproxy_proc = self._iproxy_registry.pop(udid, None) + if iproxy_proc: + self._terminate_proc(iproxy_proc) + for idx, item in enumerate(to_kill, 1): print(f"【删】杀进程 {idx}/{len(to_kill)} pid={item.get('target').pid} udid={udid}") LogManager.method_info(f"【删】杀进程 {idx}/{len(to_kill)} pid={item.get('target').pid} udid={udid}", method="device_count") @@ -335,13 +376,14 @@ class Deviceinfo(object): if not self._spawn_iproxy: LogManager.error("iproxy 启动器未就绪", udid) return None - while self._port_in_use and self._is_port_open(port): + for attempt in range(5): + if not self._is_port_open(port): + break + LogManager.warning(f"端口 {port} 仍被占用,第 {attempt+1} 次重试释放", udid) pid = self._get_pid_by_port(port) if pid and pid != os.getpid(): - LogManager.warning(f"端口 {port} 仍被 PID {pid} 占用,尝试释放", udid) self._kill_pid_gracefully(pid) - else: - break + time.sleep(0.2) try: p = self._spawn_iproxy(udid, port, 9100) self._port_in_use.add(port) @@ -405,4 +447,4 @@ class Deviceinfo(object): for p in candidates: if p.exists(): return p - raise FileNotFoundError(f"iproxy not found, tried: {[str(c) for c in candidates]}") + raise FileNotFoundError(f"iproxy not found, tried: {[str(c) for c in candidates]}") \ No newline at end of file diff --git a/Module/FlaskService.py b/Module/FlaskService.py index 6612b22..9d38520 100644 --- a/Module/FlaskService.py +++ b/Module/FlaskService.py @@ -244,8 +244,8 @@ def growAccount(): thread = threading.Thread(target=manager.growAccount, args=(udid, event)) thread.start() # 添加到线程管理 - ThreadManager.add(udid, thread, event) - return ResultData(data="").toJson() + code , msg = ThreadManager.add(udid, thread, event) + return ResultData(data="", code=code, message= msg).toJson() # 观看直播 @@ -269,7 +269,7 @@ def stopScript(): udid = body.get("udid") LogManager.method_info(f"接口收到 /stopScript udid={udid}", method="task") code, msg = ThreadManager.stop(udid) - return ResultData(code=code, data="", massage=msg).toJson() + return ResultData(code=code, data="", message=msg).toJson() # 关注打招呼 @@ -305,6 +305,7 @@ def passAnchorData(): return ResultData(data="").toJson() except Exception as e: LogManager.error(e) + return ResultData(data="", code=1001).toJson() # 获取私信数据 @@ -325,7 +326,7 @@ def addTempAnchorData(): """ data = request.get_json() if not data: - return ResultData(code=400, massage="请求数据为空").toJson() + return ResultData(code=400, message="请求数据为空").toJson() # 追加到 JSON 文件 AiUtils.save_aclist_flat_append(data, "log/acList.json") return ResultData(data="ok").toJson() @@ -359,7 +360,7 @@ def getChatTextInfo(): 'text': 'Unable to retrieve chat messages on the current screen. Please navigate to the TikTok chat page and try again!!!' } ] - return ResultData(data=data, massage="解析失败").toJson() + return ResultData(data=data, message="解析失败").toJson() # 监控消息 @@ -386,7 +387,7 @@ def upLoadLogLogs(): if ok: return ResultData(data="日志上传成功").toJson() else: - return ResultData(data="", massage="日志上传失败").toJson() + return ResultData(data="", message="日志上传失败").toJson() # 获取当前的主播列表数据 @@ -517,8 +518,8 @@ def update_last_message(): multi=False # 只改第一条匹配的 ) if updated_count > 0: - return ResultData(data=updated_count, massage="修改成功").toJson() - return ResultData(data=updated_count, massage="修改失败").toJson() + return ResultData(data=updated_count, message="修改成功").toJson() + return ResultData(data=updated_count, message="修改失败").toJson() @app.route("/delete_last_message", methods=['POST']) @@ -534,8 +535,8 @@ def delete_last_message(): multi=False # 只改第一条匹配的 ) if updated_count > 0: - return ResultData(data=updated_count, massage="修改成功").toJson() - return ResultData(data=updated_count, massage="修改失败").toJson() + return ResultData(data=updated_count, message="修改成功").toJson() + return ResultData(data=updated_count, message="修改失败").toJson() # @app.route("/killWda", methods=['POST']) diff --git a/Module/FlaskSubprocessManager.py b/Module/FlaskSubprocessManager.py index f948845..ec366d8 100644 --- a/Module/FlaskSubprocessManager.py +++ b/Module/FlaskSubprocessManager.py @@ -105,7 +105,6 @@ class FlaskSubprocessManager: # 守护线程:把子进程 stdout → LogManager.info/system threading.Thread(target=self._flush_stdout, daemon=True).start() - LogManager.info(f"Flask 子进程已启动,PID={self.process.pid},端口={self.comm_port}", udid="system") if not self._wait_port_open(timeout=10): @@ -122,6 +121,8 @@ class FlaskSubprocessManager: for line in iter(self.process.stdout.readline, ""): if line: LogManager.info(line.rstrip(), udid="system") + # 同时输出到控制台 + print(line.rstrip()) # 打印到主进程的控制台 self.process.stdout.close() # ---------- 发送 ---------- diff --git a/Module/Main.py b/Module/Main.py index c86317d..e8e5be3 100644 --- a/Module/Main.py +++ b/Module/Main.py @@ -1,12 +1,9 @@ import os import sys from pathlib import Path - from Module.DeviceInfo import Deviceinfo from Module.FlaskSubprocessManager import FlaskSubprocessManager from Utils.DevDiskImageDeployer import DevDiskImageDeployer -from Utils.LogManager import LogManager - # 确定 exe 或 py 文件所在目录 BASE = Path(getattr(sys, 'frozen', False) and sys.executable or __file__).resolve().parent diff --git a/SupportFiles/14.0.zip b/SupportFiles/14.0.zip new file mode 100644 index 0000000..bd7a539 Binary files /dev/null and b/SupportFiles/14.0.zip differ diff --git a/SupportFiles/14.1.zip b/SupportFiles/14.1.zip new file mode 100644 index 0000000..ceb8328 Binary files /dev/null and b/SupportFiles/14.1.zip differ diff --git a/SupportFiles/14.2.zip b/SupportFiles/14.2.zip new file mode 100644 index 0000000..6190de1 Binary files /dev/null and b/SupportFiles/14.2.zip differ diff --git a/SupportFiles/14.3.zip b/SupportFiles/14.3.zip new file mode 100644 index 0000000..1203d70 Binary files /dev/null and b/SupportFiles/14.3.zip differ diff --git a/SupportFiles/14.4.zip b/SupportFiles/14.4.zip new file mode 100644 index 0000000..a1025db Binary files /dev/null and b/SupportFiles/14.4.zip differ diff --git a/SupportFiles/14.5.zip b/SupportFiles/14.5.zip new file mode 100644 index 0000000..d91b781 Binary files /dev/null and b/SupportFiles/14.5.zip differ diff --git a/SupportFiles/14.6.zip b/SupportFiles/14.6.zip new file mode 100644 index 0000000..9ed5e41 Binary files /dev/null and b/SupportFiles/14.6.zip differ diff --git a/SupportFiles/14.7.zip b/SupportFiles/14.7.zip new file mode 100644 index 0000000..3a8f01a Binary files /dev/null and b/SupportFiles/14.7.zip differ diff --git a/SupportFiles/14.8.zip b/SupportFiles/14.8.zip new file mode 100644 index 0000000..ea9c3a6 Binary files /dev/null and b/SupportFiles/14.8.zip differ diff --git a/SupportFiles/15.0.zip b/SupportFiles/15.0.zip new file mode 100644 index 0000000..a91df82 Binary files /dev/null and b/SupportFiles/15.0.zip differ diff --git a/SupportFiles/15.1.zip b/SupportFiles/15.1.zip new file mode 100644 index 0000000..bd07873 Binary files /dev/null and b/SupportFiles/15.1.zip differ diff --git a/SupportFiles/15.2.zip b/SupportFiles/15.2.zip new file mode 100644 index 0000000..134c25b Binary files /dev/null and b/SupportFiles/15.2.zip differ diff --git a/SupportFiles/15.3.zip b/SupportFiles/15.3.zip new file mode 100644 index 0000000..1a75175 Binary files /dev/null and b/SupportFiles/15.3.zip differ diff --git a/SupportFiles/15.4.zip b/SupportFiles/15.4.zip new file mode 100644 index 0000000..b62fa2d Binary files /dev/null and b/SupportFiles/15.4.zip differ diff --git a/SupportFiles/15.5.zip b/SupportFiles/15.5.zip new file mode 100644 index 0000000..d84ff97 Binary files /dev/null and b/SupportFiles/15.5.zip differ diff --git a/SupportFiles/15.6.zip b/SupportFiles/15.6.zip new file mode 100644 index 0000000..bc450f3 Binary files /dev/null and b/SupportFiles/15.6.zip differ diff --git a/SupportFiles/15.7.zip b/SupportFiles/15.7.zip new file mode 100644 index 0000000..13ab765 Binary files /dev/null and b/SupportFiles/15.7.zip differ diff --git a/SupportFiles/15.8.zip b/SupportFiles/15.8.zip new file mode 100644 index 0000000..f413db6 Binary files /dev/null and b/SupportFiles/15.8.zip differ diff --git a/SupportFiles/16.0.zip b/SupportFiles/16.0.zip new file mode 100644 index 0000000..ab9a02a Binary files /dev/null and b/SupportFiles/16.0.zip differ diff --git a/SupportFiles/16.1.zip b/SupportFiles/16.1.zip new file mode 100644 index 0000000..07c9458 Binary files /dev/null and b/SupportFiles/16.1.zip differ diff --git a/SupportFiles/16.2.zip b/SupportFiles/16.2.zip new file mode 100644 index 0000000..b5f95da Binary files /dev/null and b/SupportFiles/16.2.zip differ diff --git a/SupportFiles/16.3.zip b/SupportFiles/16.3.zip new file mode 100644 index 0000000..bc4e184 Binary files /dev/null and b/SupportFiles/16.3.zip differ diff --git a/SupportFiles/16.4.zip b/SupportFiles/16.4.zip new file mode 100644 index 0000000..8a785e3 Binary files /dev/null and b/SupportFiles/16.4.zip differ diff --git a/SupportFiles/16.5.zip b/SupportFiles/16.5.zip new file mode 100644 index 0000000..fc2ec0b Binary files /dev/null and b/SupportFiles/16.5.zip differ diff --git a/SupportFiles/16.6.zip b/SupportFiles/16.6.zip new file mode 100644 index 0000000..1e4924d Binary files /dev/null and b/SupportFiles/16.6.zip differ diff --git a/SupportFiles/16.7.zip b/SupportFiles/16.7.zip new file mode 100644 index 0000000..f2a36f8 Binary files /dev/null and b/SupportFiles/16.7.zip differ diff --git a/Utils/ControlUtils.py b/Utils/ControlUtils.py index 9a42eea..9f7048a 100644 --- a/Utils/ControlUtils.py +++ b/Utils/ControlUtils.py @@ -79,10 +79,9 @@ class ControlUtils(object): "//Window[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]").exists: back = session.xpath( "//Window[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]") - if back.exists: back.click() - return True + return True else: return False except Exception as e: diff --git a/Utils/LogManager.py b/Utils/LogManager.py index 6ace65c..0c8bb94 100644 --- a/Utils/LogManager.py +++ b/Utils/LogManager.py @@ -28,43 +28,6 @@ def _force_utf8_everywhere(): # _force_utf8_everywhere() - -# ========= 全局:强制 UTF-8 + 关闭缓冲(运行期立刻生效) ========= -def _force_utf8_everywhere(): - os.environ.setdefault("PYTHONUTF8", "1") - # 等价于 -u:让 stdout/stderr 无缓冲 - os.environ.setdefault("PYTHONUNBUFFERED", "1") - os.environ.setdefault("PYTHONIOENCODING", "utf-8") - - # 若是 3.7+,优先用 reconfigure 实时改流 - try: - if hasattr(sys.stdout, "reconfigure"): - sys.stdout.reconfigure(encoding="utf-8", errors="replace", - line_buffering=True, write_through=True) - elif getattr(sys.stdout, "buffer", None): - # 退路:重新包一层,启用行缓冲 + 直写 - sys.stdout = io.TextIOWrapper( - sys.stdout.buffer, encoding="utf-8", - errors="replace", line_buffering=True - ) - except Exception: - pass - - try: - if hasattr(sys.stderr, "reconfigure"): - sys.stderr.reconfigure(encoding="utf-8", errors="replace", - line_buffering=True, write_through=True) - elif getattr(sys.stderr, "buffer", None): - sys.stderr = io.TextIOWrapper( - sys.stderr.buffer, encoding="utf-8", - errors="replace", line_buffering=True - ) - except Exception: - pass - -# =========================================================== - - class LogManager: """ 设备级与“设备+方法”级日志管理: diff --git a/Utils/ThreadManager.py b/Utils/ThreadManager.py index 3d1adce..6740d08 100644 --- a/Utils/ThreadManager.py +++ b/Utils/ThreadManager.py @@ -1,126 +1,77 @@ -import os -import signal -import sys import threading -import time -import psutil -import subprocess -from pathlib import Path -from threading import Event, Thread -from typing import Dict, Optional - +from typing import Dict, Tuple from Utils.LogManager import LogManager class ThreadManager: - """ - 对调用方完全透明: - add(udid, thread_obj, stop_event) 保持原签名 - stop(udid) 保持原签名 - 但内部把 thread_obj 当成“壳”,真正拉起的是子进程。 - """ - _pool: Dict[str, psutil.Process] = {} + _tasks: Dict[str, Dict] = {} _lock = threading.Lock() @classmethod - def add(cls, udid: str, dummy_thread, dummy_event: Event) -> None: - LogManager.method_info(f"【1】入口 udid={udid} 长度={len(udid)}", method="task") - if udid in cls._pool: - LogManager.method_warning(f"{udid} 仍在运行,先强制清理旧任务", method="task") - cls.stop(udid) - LogManager.method_info(f"【2】判断旧任务后 udid={udid} 长度={len(udid)}", method="task") - port = cls._find_free_port() - LogManager.method_info(f"【3】找端口后 udid={udid} 长度={len(udid)}", method="task") - proc = cls._start_worker_process(udid, port) - LogManager.method_info(f"【4】子进程启动后 udid={udid} 长度={len(udid)}", method="task") - cls._pool[udid] = proc - LogManager.method_info(f"【5】已写入字典,udid={udid} 长度={len(udid)}", method="task") + def add(cls, udid: str, thread: threading.Thread, event: threading.Event) -> Tuple[int, str]: + """ + 添加一个线程到线程管理器。 + :param udid: 设备的唯一标识符 + :param thread: 线程对象 + :param event: 用于控制线程退出的 Event 对象 + :return: 状态码和信息 + """ + with cls._lock: + if udid in cls._tasks and cls._tasks[udid].get("running", False): + LogManager.method_info(f"任务添加失败:设备 {udid} 已存在运行中的任务", method="task") + return 400, f"该设备中已存在任务 {udid}" + + # 如果任务已经存在但已停止,清理旧任务记录 + if udid in cls._tasks and not cls._tasks[udid].get("running", False): + LogManager.method_info(f"清理设备 {udid} 的旧任务记录", method="task") + del cls._tasks[udid] + + # 添加新任务记录 + cls._tasks[udid] = { + "thread": thread, + "event": event, + "running": True + } + LogManager.method_info(f"设备 {udid} 开始任务成功", method="task") + return 200, f"创建任务成功 {udid}" @classmethod - def stop(cls, udid: str) -> tuple[int, str]: - with cls._lock: # 类级锁 - proc = cls._pool.get(udid) # 1. 只读,不删 - if proc is None: - return 1001, f"无此任务 {udid}" - - try: - proc.terminate() - gone, alive = psutil.wait_procs([proc], timeout=3) - if alive: - for p in alive: - for child in p.children(recursive=True): - child.kill() - p.kill() - psutil.wait_procs(alive, timeout=2) - - # 正常退出 - cls._pool.pop(udid) - LogManager.method_info("任务停止成功", method="task") - return 200, f"停止线程成功 {udid}" - - except psutil.NoSuchProcess: # 精准捕获 - cls._pool.pop(udid) - LogManager.method_info("进程已自然退出", method="task") - return 200, f"进程已退出 {udid}" - - except Exception as e: # 真正的异常 - LogManager.method_error(f"停止异常: {e}", method="task") - return 1002, f"停止异常 {udid}" - - # ------------------------------------------------------ - # 以下全是内部工具,外部无需调用 - # ------------------------------------------------------ - @staticmethod - def _find_free_port(start: int = 50000) -> int: - """找个随机空闲端口,给子进程当通信口(可选)""" - import socket - for p in range(start, start + 1000): - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - if s.connect_ex(("127.0.0.1", p)) != 0: - return p - raise RuntimeError("无可用端口") - - @staticmethod - def _start_worker_process(udid: str, port: int) -> psutil.Process: + def stop(cls, udid: str) -> Tuple[int, str]: """ - 真正拉起子进程: - 打包环境:exe --udid=xxx - 源码环境:python -m Module.Worker --udid=xxx + 停止指定设备的线程。 + :param udid: 设备的唯一标识符 + :return: 状态码和信息 """ - exe_path = Path(sys.executable).resolve() - is_frozen = exe_path.suffix.lower() == ".exe" and exe_path.exists() + with cls._lock: + if udid not in cls._tasks or not cls._tasks[udid].get("running", False): + LogManager.method_info(f"任务停止失败:设备 {udid} 没有执行相关任务", method="task") + return 400, f"当前设备没有执行相关任务 {udid}" - if is_frozen: - # 打包后 - cmd = [str(exe_path), "--role=worker", f"--udid={udid}", f"--port={port}"] - cwd = str(exe_path.parent) - else: - # 源码运行 - cmd = [sys.executable, "-u", "-m", "Module.Worker", f"--udid={udid}", f"--port={port}"] - cwd = str(Path(__file__).resolve().parent.parent) + task = cls._tasks[udid] + event = task["event"] + thread = task["thread"] - # 核心:CREATE_NO_WINDOW + 独立会话,父进程死也不影响 - creation_flags = 0x08000000 if os.name == "nt" else 0 - proc = subprocess.Popen( - cmd, - stdin=subprocess.DEVNULL, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - encoding="utf-8", - errors="replace", - bufsize=1, - cwd=cwd, - start_new_session=True, # 独立进程组 - creationflags=creation_flags - ) - # 守护线程:把子进程 stdout 实时打到日志 - Thread(target=lambda: ThreadManager._log_stdout(proc, udid), daemon=True).start() - return psutil.Process(proc.pid) + LogManager.method_info(f"设备 {udid} 的任务正在停止", method="task") - @staticmethod - def _log_stdout(proc: subprocess.Popen, udid: str): - for line in iter(proc.stdout.readline, ""): - if line: - LogManager.info(line.rstrip(), udid) - proc.stdout.close() \ No newline at end of file + # 设置停止标志位 + event.set() + + # 等待线程结束 + thread.join(timeout=5) # 可设置超时时间,避免阻塞 + + # 清理任务记录 + del cls._tasks[udid] # 删除任务记录 + LogManager.method_info(f"设备 {udid} 的任务停止成功", method="task") + return 200, f"当前任务停止成功 {udid}" + + @classmethod + def is_task_running(cls, udid: str) -> bool: + """ + 检查任务是否正在运行。 + :param udid: 设备的唯一标识符 + :return: True 表示任务正在运行,False 表示没有任务运行 + """ + with cls._lock: + is_running = cls._tasks.get(udid, {}).get("running", False) + LogManager.method_info(f"检查设备 {udid} 的任务状态:{'运行中' if is_running else '未运行'}", method="task") + return is_running diff --git a/script/ScriptManager.py b/script/ScriptManager.py index d457980..26b177d 100644 --- a/script/ScriptManager.py +++ b/script/ScriptManager.py @@ -60,8 +60,6 @@ class ScriptManager(): # 设置手机的节点深度为15,判断该页面是否正确 session.appium_settings({"snapshotMaxDepth": 15}) - # 判断当前页面上是否有推荐按钮 - el = session.xpath( '//XCUIElementTypeButton[@name="top_tabs_recomend" or @name="推荐" or @label="推荐"]' ) @@ -274,7 +272,6 @@ class ScriptManager(): retries = 0 while not event.is_set(): try: - self.greetNewFollowers(udid, needReply, event) except Exception as e: @@ -294,13 +291,11 @@ class ScriptManager(): LogManager.method_info(f"是否要自动回复消息:{needReply}", "关注打招呼", udid) # 先关闭Tik Tok - ControlUtils.closeTikTok(session, udid) time.sleep(1) # 重新打开Tik Tok ControlUtils.openTikTok(session, udid) - time.sleep(3) LogManager.method_info(f"重启tiktok", "关注打招呼", udid) # 设置查找深度 @@ -374,6 +369,7 @@ class ScriptManager(): input.set_text(f"{aid or '暂无数据'}\n") # 定位 "关注" 按钮 通过关注按钮的位置点击主播首页 + session.appium_settings({"snapshotMaxDepth": 25}) try: @@ -606,7 +602,6 @@ class ScriptManager(): time.sleep(3) continue # 重新进入 while 循环,调用 monitorMessages - # 检查未读消息并回复 def monitorMessages(self, session, udid):