20250904-初步功能已完成

This commit is contained in:
2025-09-18 21:45:31 +08:00
38 changed files with 186 additions and 299 deletions

View File

@@ -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)
# ----------------------------------------------------------
# ✅ 核平所有 iproxyWindows / 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]}")

View File

@@ -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'])

View File

@@ -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()
# ---------- 发送 ----------

View File

@@ -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