From 4dd3eb59a463d3ce5ff4d40ed977c5e4b2c9e0e5 Mon Sep 17 00:00:00 2001
From: milk <53408947@qq.com>
Date: Mon, 22 Sep 2025 19:10:58 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=89=B9=E9=87=8F=E5=81=9C?=
=?UTF-8?q?=E6=AD=A2=E8=84=9A=E6=9C=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/workspace.xml | 65 ++++---
Module/FlaskService.py | 9 +-
.../__pycache__/FlaskService.cpython-312.pyc | Bin 27083 -> 26940 bytes
Module/__pycache__/Main.cpython-312.pyc | Bin 2843 -> 2843 bytes
Utils/ThreadManager.py | 163 ++++++------------
Utils/__pycache__/LogManager.cpython-312.pyc | Bin 14663 -> 14663 bytes
.../__pycache__/ThreadManager.cpython-312.pyc | Bin 7680 -> 4451 bytes
.../__pycache__/ScriptManager.cpython-312.pyc | Bin 41418 -> 41418 bytes
8 files changed, 85 insertions(+), 152 deletions(-)
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 7b6aac9..95075d2 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,12 +5,11 @@
+
+
-
-
-
-
-
+
+
@@ -53,35 +52,35 @@
- {
+ "keyToString": {
+ "ASKED_ADD_EXTERNAL_FILES": "true",
+ "ASKED_MARK_IGNORED_FILES_AS_EXCLUDED": "true",
+ "Python.12.executor": "Run",
+ "Python.123.executor": "Run",
+ "Python.DeviceInfo.executor": "Run",
+ "Python.Main.executor": "Run",
+ "Python.Test.executor": "Run",
+ "Python.test.executor": "Run",
+ "Python.tidevice_entry.executor": "Run",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
+ "RunOnceActivity.git.unshallow": "true",
+ "SHARE_PROJECT_CONFIGURATION_FILES": "true",
+ "git-widget-placeholder": "main",
+ "javascript.nodejs.core.library.configured.version": "20.17.0",
+ "javascript.nodejs.core.library.typings.version": "20.17.58",
+ "last_opened_file_path": "C:/Users/zhangkai/Desktop/last-item/Storage",
+ "node.js.detected.package.eslint": "true",
+ "node.js.detected.package.tslint": "true",
+ "node.js.selected.package.eslint": "(autodetect)",
+ "node.js.selected.package.tslint": "(autodetect)",
+ "nodejs_package_manager_path": "npm",
+ "settings.editor.selected.configurable": "org.jetbrains.plugins.gitlab.GitLabSettingsConfigurable",
+ "two.files.diff.last.used.file": "E:/share/iOSAI/Module/FlaskService.py",
+ "vue.rearranger.settings.migration": "true"
}
-}]]>
+}
diff --git a/Module/FlaskService.py b/Module/FlaskService.py
index 1473185..8c607d9 100644
--- a/Module/FlaskService.py
+++ b/Module/FlaskService.py
@@ -242,8 +242,7 @@ def growAccount():
manager = ScriptManager()
event = threading.Event()
# 启动脚本
- thread = threading.Thread(target=manager.growAccount, args=(udid, event))
- thread.start()
+ thread = threading.Thread(target=manager.growAccount, args=(udid, event,))
# 添加到线程管理
code, msg = ThreadManager.add(udid, thread, event)
return ResultData(data="", code=code, message=msg).toJson()
@@ -257,7 +256,6 @@ def watchLiveForGrowth():
manager = ScriptManager()
event = threading.Event()
thread = threading.Thread(target=manager.watchLiveForGrowth, args=(udid, event))
- thread.start()
# 添加到线程管理
ThreadManager.add(udid, thread, event)
return ResultData(data="").toJson()
@@ -300,7 +298,6 @@ def passAnchorData():
event = threading.Event()
# 启动脚本
thread = threading.Thread(target=manager.safe_greetNewFollowers, args=(udid, needReply, event))
- thread.start()
# 添加到线程管理
ThreadManager.add(udid, thread, event)
return ResultData(data="").toJson()
@@ -545,8 +542,8 @@ def delete_last_message():
@app.route("/stopAllTask", methods=['POST'])
def stopAllTask():
idList = request.get_json()
- code, data, msg = ThreadManager.batch_stop(idList)
- return ResultData(code, data, msg).toJson()
+ code, msg = ThreadManager.batch_stop(idList)
+ return ResultData(code, "", msg).toJson()
# @app.route("/killWda", methods=['POST'])
diff --git a/Module/__pycache__/FlaskService.cpython-312.pyc b/Module/__pycache__/FlaskService.cpython-312.pyc
index 4f003367b53c77e9cbacbf332ec7f2acfbdafbad..18b093ab171cede223314af1956b074f0fb3d994 100644
GIT binary patch
delta 784
zcmaKpUr1AN6vywm_d4Bnude)qH=VlXY&tjZHaDGrR>r0jp@9z(lBo=)f<3r_QCmzR
zQVcUrm=A?QA%pE9xevVP
ztiFZ(4W8FfB%WB(!ynGAcl+TAVl%J$$&GqTGfqhzB|^5uWSP#{*P|t50R5BzVX)J
zgjhjtzg}9jZvv=s%sHPc(-if9Qlq`KQmSq21?a=5
zYl;gS4$eDz9PMsn*(H_=n+|FLO;VeH{nh9Ryyqxqja1l-cijTiAygIOs+$uIwFkH)
zpCFTzAR9ipm!SlwnkJBKzLsA|NTGI;VwBA4@lNw2P+^_NBkXcQmeZ#>{X$@=Gwy%K
zIb>d?cK*u8Tb`b>2A_jkbEy3;X05fsFEZSUA1JWVF>;ttG9sWVF^4Yn8T!BCJaTr{E_;(N>vOq+CwXIk$yL
zA*HQ~yW{4VkOAi&od;&6sZmyxT#A#!MR-40Q&0g-N?nv`qGX@+I=BT|+dm!yHher{
F{tH;w+28;G
delta 770
zcmXw0T}V@L6#t+9z1y63bDBC`W4g_+ExdC%ow_2Y&IPimeGrrwPF6D|2p#&e<;+M0
zGwGoDkmQ383wa6q78-L}U^9D!am9tfdr3c{`|ajd!rsVh}&^
zy>J6N%S?FDX4OPrsr@nU4x2gQ8}nUn$2nV%(Aj(%&}uKl-HIH1!Yi@MUX1Tr3h}1B
zM*c_aF%%K&&G^-B!(mUY&~o@W0Ean_c||ru$Q`1@rmkY#_On95(E%`krrJq1qUbjv
zm<`y^8Sq(cPVQxvge#}TvLaS-7>se7s$#Ni-x=+#7LSgLau%h6nMYB!Mvk7aLYI4
zn{NNC(xp^}gvziGT=K?SU#qIq8imsFGaG-^cjy~iDcPV(uZ-E?s=S2RaDm955}8+q
z7aG1mgWz+8C^QKVkM^-}LNGKj3|tXRo&=R&m1bWP&y**qj+ZkqEjZf7GvJ=EezJ^(
zl<@Y<6qSFFlE(LvVrS0->1?FA`G!281ZA^pxh)4
zIfl@i$7@ScT3bSEiw`HY&asY|XR>sbdBf$;1{XX_B}uM4#qkM_PjXe;Y?a{djj#?5
z-0*`TluuKMri%!bP3XpS+nh1(j9X&5BsjiPC0L}SnzY0)9nzvKRIVw8I!U#ssmRh^
S!4-M|oc(_{2JD!3$?_KrW7`1$
diff --git a/Module/__pycache__/Main.cpython-312.pyc b/Module/__pycache__/Main.cpython-312.pyc
index 077ebd9c709050ba840e3b894f225747c88c6c65..284dcb7b3c63e9eba4f5518f0d68dddb55a1ca9f 100644
GIT binary patch
delta 20
acmbO&Hd~DQG%qg~0}veKytt8DfExfcJOs}G
delta 20
acmbO&Hd~DQG%qg~0}v>^y0DR3fExfdBn1!v
diff --git a/Utils/ThreadManager.py b/Utils/ThreadManager.py
index 857b996..845dd47 100644
--- a/Utils/ThreadManager.py
+++ b/Utils/ThreadManager.py
@@ -1,136 +1,73 @@
+import ctypes
import threading
-from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import Dict, Tuple, List
+
from Utils.LogManager import LogManager
+def _async_raise(tid: int, exc_type=KeyboardInterrupt):
+ """向指定线程抛异常,强制跳出"""
+ res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), ctypes.py_object(exc_type))
+ if res == 0:
+ raise ValueError("线程不存在")
+ elif res > 1:
+ ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), 0)
+
class ThreadManager:
_tasks: Dict[str, Dict] = {}
_lock = threading.Lock()
@classmethod
def add(cls, udid: str, thread: threading.Thread, event: threading.Event) -> Tuple[int, str]:
- """
- 添加一个线程到线程管理器。
- :param udid: 设备的唯一标识符
- :param thread: 线程对象
- :param event: 用于控制线程退出的 Event 对象
- :return: 状态码和信息
- """
+ LogManager.method_info(f"准备创建任务:{udid}", "task")
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 cls._tasks.get(udid, None) is not None:
+ return 1001, "当前设备已存在任务"
+ thread.start()
+ print(thread.ident)
- # 如果任务已经存在但已停止,清理旧任务记录
- 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] = {
+ "id": thread.ident,
"thread": thread,
- "event": event,
- "running": True
+ "event": event
}
- LogManager.method_info(f"设备 {udid} 开始任务成功", method="task")
- return 200, f"创建任务成功 {udid}"
+ return 200, "创建成功"
@classmethod
def stop(cls, udid: str) -> Tuple[int, str]:
- """
- 停止指定设备的线程。
- :param udid: 设备的唯一标识符
- :return: 状态码和信息
- """
- 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}"
-
- task = cls._tasks[udid]
- event = task["event"]
- thread = task["thread"]
-
- LogManager.method_info(f"设备 {udid} 的任务正在停止", method="task")
-
- # 设置停止标志位
- event.set()
-
- # 等待线程结束
- thread.join(timeout=5) # 可设置超时时间,避免阻塞
-
- # 清理任务记录
- del cls._tasks[udid] # 删除任务记录
- LogManager.method_info(f"设备 {udid} 的任务停止成功", method="task")
- return 200, f"当前任务停止成功 {udid}"
+ try:
+ print(cls._tasks)
+ obj = cls._tasks.get(udid, None)
+ obj["event"].set()
+ r = cls._kill_thread(obj.get("id"))
+ if r:
+ cls._tasks.pop(udid, None)
+ else:
+ print("好像有问题")
+ except Exception as e:
+ print(e)
+ return 200, "操作成功"
@classmethod
- def batch_stop(cls, udids: List[str]) -> Tuple[int, List[str], str]:
- if not udids:
- return 200, [], "无设备需要停止"
-
- fail_list: List[str] = []
-
- # ---------- 1. 瞬间置位 event ----------
- with cls._lock:
- for udid in udids:
- task = cls._tasks.get(udid)
- if task and task.get("running"):
- task["event"].set()
-
- # ---------- 2. 单设备重试 5 次 ----------
- def _wait_and_clean_with_retry(udid: str) -> Tuple[int, List[str], str]:
- """
- 内部重试 5 次,最终返回格式与 batch_stop 完全一致:
- (code, fail_list, msg) fail_list 空表示成功
- """
- for attempt in range(1, 6):
- with cls._lock:
- task = cls._tasks.get(udid)
- if not task:
- return 200, [], "任务已不存在" # 成功
- thread = task["thread"]
-
- thread.join(timeout=5)
- still_alive = thread.is_alive()
-
- # 最后一次重试才清理记录
- if attempt == 5:
- with cls._lock:
- cls._tasks.pop(udid, None)
-
- if not still_alive:
- return 200, [], "已停止"
-
- LogManager.warning(f"[batch_stop] {udid} 第{attempt}/5 次停止未立即结束,重试", udid)
-
- # 5 次都失败
- LogManager.error(f"[batch_stop] {udid} 停止失败(5 次重试后线程仍活)", udid)
- return 1001, [udid], "部分设备停止失败"
-
- # ---------- 3. 并发执行 ----------
- with ThreadPoolExecutor(max_workers=min(32, len(udids))) as executor:
- future_map = {executor.submit(_wait_and_clean_with_retry, udid): udid for udid in udids}
- for future in as_completed(future_map):
- udid = future_map[future]
- code, sub_fail_list, sub_msg = future.result() # ← 现在解包正确
- if code != 200:
- fail_list.extend(sub_fail_list) # 收集失败 udid
-
- # ---------- 4. 返回兼容格式 ----------
- if fail_list:
- return 1001, fail_list, "部分设备停止失败"
- return 200, [], "全部设备停止成功"
+ def batch_stop(cls, ids: List[str]) -> Tuple[int, str]:
+ try:
+ for udid in ids:
+ cls.stop(udid)
+ except Exception as e:
+ print(e)
+ return 200, "停止成功."
@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
+ def _kill_thread(cls, tid: int) -> bool:
+ """向原生线程 ID 抛 KeyboardInterrupt,强制跳出"""
+ res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid),
+ ctypes.py_object(KeyboardInterrupt))
+ if res == 0: # 线程已不存在
+ print("线程不存在")
+ return False
+ if res > 1: # 命中多个线程,重置
+ print("命中了多个线程")
+ ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), 0)
+ print("杀死线程成功")
+ return True
\ No newline at end of file
diff --git a/Utils/__pycache__/LogManager.cpython-312.pyc b/Utils/__pycache__/LogManager.cpython-312.pyc
index e8d07465556a14b4889341356139d7746934c48b..1a1e4120a9f536922dac4e8e46e0d1affff0bd29 100644
GIT binary patch
delta 20
acmX?Jbi9cBG%qg~0}wp?d0`{BjU@n2wFgK5
delta 20
acmX?Jbi9cBG%qg~0}#k8J-3nD#u5NXPzEgk
diff --git a/Utils/__pycache__/ThreadManager.cpython-312.pyc b/Utils/__pycache__/ThreadManager.cpython-312.pyc
index 3d4d6c7e312151f47ce4763b2a02e125709030a5..c24066db8add9ef243a9c66b53e40dcc5e8024c5 100644
GIT binary patch
literal 4451
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