From 63fd3d590befb449072198401f3fd9950bb5b2f4 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Tue, 16 Sep 2025 20:36:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=8E=E7=AB=AF=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=B8=A5=E6=A0=BC=E7=9A=84=E4=B8=8B=E6=8B=89=E8=A1=A8=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=A0=A1=E9=AA=8C=E9=80=BB=E8=BE=91=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=96=B0ws=E6=B6=88=E6=81=AF=E7=B1=BB=E5=9E=8Btask=5F?= =?UTF-8?q?dict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/core.py | 2 +- app/core/config.py | 26 ++++++- app/core/task_manager.py | 78 +++++++++++++++---- app/models/ConfigBase.py | 63 +++++++++------ ..._Task_Scheduling_and_WebSocket_Messages.md | 25 +++++- 5 files changed, 151 insertions(+), 43 deletions(-) diff --git a/app/api/core.py b/app/api/core.py index 839329b..d9384f8 100644 --- a/app/api/core.py +++ b/app/api/core.py @@ -48,7 +48,7 @@ async def connect_websocket(websocket: WebSocket): try: - data = await asyncio.wait_for(websocket.receive_json(), timeout=15.0) + data = await asyncio.wait_for(websocket.receive_json(), timeout=1000005.0) if data.get("type") == "Signal" and "Pong" in data.get("data", {}): last_pong = time.monotonic() elif data.get("type") == "Signal" and "Ping" in data.get("data", {}): diff --git a/app/core/config.py b/app/core/config.py index f238e8b..3b0d9a9 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -162,10 +162,17 @@ class GlobalConfig(ConfigBase): class QueueItem(ConfigBase): """队列项配置""" + related_config: dict[str, MultipleConfig] = {} + def __init__(self) -> None: super().__init__() - self.Info_ScriptId = ConfigItem("Info", "ScriptId", None, UidValidator()) + self.Info_ScriptId = ConfigItem( + "Info", + "ScriptId", + None, + MultipleUIDValidator(None, self.related_config, "ScriptConfig"), + ) class TimeSet(ConfigBase): @@ -175,7 +182,7 @@ class TimeSet(ConfigBase): super().__init__() self.Info_Enabled = ConfigItem("Info", "Enabled", False, BoolValidator()) - self.Info_Time = ConfigItem("Info", "Time", "00:00") + self.Info_Time = ConfigItem("Info", "Time", "00:00", DateTimeValidator("%H:%M")) class QueueConfig(ConfigBase): @@ -214,6 +221,8 @@ class QueueConfig(ConfigBase): class MaaUserConfig(ConfigBase): """MAA用户配置""" + related_config: dict[str, MultipleConfig] = {} + def __init__(self) -> None: super().__init__() @@ -222,7 +231,12 @@ class MaaUserConfig(ConfigBase): self.Info_Mode = ConfigItem( "Info", "Mode", "简洁", OptionsValidator(["简洁", "详细"]) ) - self.Info_StageMode = ConfigItem("Info", "StageMode", "Fixed") + self.Info_StageMode = ConfigItem( + "Info", + "StageMode", + "Fixed", + MultipleUIDValidator("Fixed", self.related_config, "PlanConfig"), + ) self.Info_Server = ConfigItem( "Info", "Server", @@ -501,7 +515,9 @@ class GeneralUserConfig(ConfigBase): ) self.Info_Notes = ConfigItem("Info", "Notes", "无") - self.Data_LastProxyDate = ConfigItem("Data", "LastProxyDate", "2000-01-01") + self.Data_LastProxyDate = ConfigItem( + "Data", "LastProxyDate", "2000-01-01", DateTimeValidator("%Y-%m-%d") + ) self.Data_ProxyTimes = ConfigItem( "Data", "ProxyTimes", 0, RangeValidator(0, 9999) ) @@ -643,6 +659,8 @@ class AppConfig(GlobalConfig): self.ScriptConfig = MultipleConfig([MaaConfig, GeneralConfig]) self.PlanConfig = MultipleConfig([MaaPlanConfig]) self.QueueConfig = MultipleConfig([QueueConfig]) + QueueItem.related_config["ScriptConfig"] = self.ScriptConfig + MaaUserConfig.related_config["PlanConfig"] = self.PlanConfig truststore.inject_into_ssl() diff --git a/app/core/task_manager.py b/app/core/task_manager.py index a2e05e2..995eb8c 100644 --- a/app/core/task_manager.py +++ b/app/core/task_manager.py @@ -125,38 +125,74 @@ class _TaskManager: else: + # 初始化任务列表 if task_id in Config.QueueConfig: queue = Config.QueueConfig[task_id] if not isinstance(queue, QueueConfig): - logger.error( - f"不支持的队列类型: {type(Config.QueueConfig[task_id]).__name__}" - ) - await Config.send_json( - WebSocketMessage( - id=str(task_id), - type="Info", - data={"Error": "队列类型不支持"}, - ).model_dump() - ) return task_list = [] for queue_item in queue.QueueItem.values(): if queue_item.get("Info", "ScriptId") is None: continue - uid = uuid.UUID(queue_item.get("Info", "ScriptId")) + script_id = uuid.UUID(queue_item.get("Info", "ScriptId")) + script = Config.ScriptConfig[script_id] + if not isinstance(script, (MaaConfig | GeneralConfig)): + logger.error(f"不支持的脚本类型: {type(script).__name__}") + continue task_list.append( { - "script_id": str(uid), + "script_id": str(script_id), "status": "等待", - "name": Config.ScriptConfig[uid].get("Info", "Name"), + "name": script.get("Info", "Name"), + "user_list": [ + { + "user_id": str(user_id), + "status": "等待", + "name": config.get("Info", "Name"), + } + for user_id, config in script.UserData.items() + if config.get("Info", "Status") + and config.get("Info", "RemainedDay") != 0 + ], } ) elif actual_id is not None and actual_id in Config.ScriptConfig: - task_list = [{"script_id": str(actual_id), "status": "等待"}] + script = Config.ScriptConfig[actual_id] + if not isinstance(script, (MaaConfig | GeneralConfig)): + logger.error(f"不支持的脚本类型: {type(script).__name__}") + return + + task_list = [ + { + "script_id": str(actual_id), + "status": "等待", + "name": script.get("Info", "Name"), + "user_list": [ + { + "user_id": str(user_id), + "status": "等待", + "name": config.get("Info", "Name"), + } + for user_id, config in script.UserData.items() + if config.get("Info", "Status") + and config.get("Info", "RemainedDay") != 0 + ], + } + ] + + await Config.send_json( + WebSocketMessage( + id=str(task_id), type="Update", data={"task_dict": task_list} + ).model_dump() + ) + + # 清理用户列表初值 + for task in task_list: + task.pop("user_list", None) for task in task_list: @@ -176,6 +212,20 @@ class _TaskManager: logger.info(f"跳过任务: {script_id}, 该任务已在运行列表中") continue + # 检查任务对应脚本是否仍存在 + if script_id in self.task_dict: + + task["status"] = "异常" + await Config.send_json( + WebSocketMessage( + id=str(task_id), + type="Update", + data={"task_list": task_list}, + ).model_dump() + ) + logger.info(f"跳过任务: {script_id}, 该任务对应脚本已被删除") + continue + # 标记为运行中 task["status"] = "运行" await Config.send_json( diff --git a/app/models/ConfigBase.py b/app/models/ConfigBase.py index 2663bf4..3fc662b 100644 --- a/app/models/ConfigBase.py +++ b/app/models/ConfigBase.py @@ -83,22 +83,6 @@ class OptionsValidator(ConfigValidator): return value if self.validate(value) else self.options[0] -class UidValidator(ConfigValidator): - """UID验证器""" - - def validate(self, value: Any) -> bool: - if value is None: - return True - try: - uuid.UUID(value) - return True - except (TypeError, ValueError): - return False - - def correct(self, value: Any) -> Any: - return value if self.validate(value) else None - - class UUIDValidator(ConfigValidator): """UUID验证器""" @@ -333,14 +317,20 @@ class ConfigItem: if not self.validator.validate(self.value): self.value = self.validator.correct(self.value) - def getValue(self) -> Any: + def getValue(self, if_decrypt: bool = True) -> Any: """ 获取配置项值 """ - if isinstance(self.validator, EncryptValidator): - return dpapi_decrypt(self.value) - return self.value + v = ( + self.value + if self.validator.validate(self.value) + else self.validator.correct(self.value) + ) + + if isinstance(self.validator, EncryptValidator) and if_decrypt: + return dpapi_decrypt(v) + return v def lock(self): """ @@ -452,9 +442,7 @@ class ConfigBase: if not data.get(item.group): data[item.group] = {} if item.name: - data[item.group][item.name] = ( - item.getValue() if if_decrypt else item.value - ) + data[item.group][item.name] = item.getValue(if_decrypt) elif not ignore_multi_config and isinstance(item, MultipleConfig): @@ -838,3 +826,32 @@ class MultipleConfig: """返回配置项的所有唯一标识符和实例的元组""" return zip(self.keys(), self.values()) + + +class MultipleUIDValidator(ConfigValidator): + """多配置管理类UID验证器""" + + def __init__( + self, default: Any, related_config: Dict[str, MultipleConfig], config_name: str + ): + self.default = default + self.related_config = related_config + self.config_name = config_name + + def validate(self, value: Any) -> bool: + if value == self.default: + return True + if not isinstance(value, str): + return False + try: + uid = uuid.UUID(value) + except (TypeError, ValueError): + return False + if uid in self.related_config.get(self.config_name, {}): + return True + return False + + def correct(self, value: Any) -> Any: + if self.validate(value): + return value + return self.default diff --git a/docs/Backend_Task_Scheduling_and_WebSocket_Messages.md b/docs/Backend_Task_Scheduling_and_WebSocket_Messages.md index 4f291ba..0f51b72 100644 --- a/docs/Backend_Task_Scheduling_and_WebSocket_Messages.md +++ b/docs/Backend_Task_Scheduling_and_WebSocket_Messages.md @@ -73,7 +73,7 @@ AUTO-MAS 后端采用基于 AsyncIO 的异步任务调度系统,主要由以 #### 3.2.1 Update 类型 - 数据更新 -**用途**: 通知前端更新界面数据 +**用途**: 通知前端更新界面数据,"user_list"仅给出当前处于`运行`状态的脚本的用户列表值 **常见数据格式:** @@ -93,6 +93,29 @@ AUTO-MAS 后端采用基于 AsyncIO 的异步任务调度系统,主要由以 } ``` +```json +{ + "id": "task-uuid", + "type": "Update", + "data": { + "task_dict": [ + { + "script_id": "脚本ID", + "status": "等待/运行/完成/跳过", + "name": "脚本名称", + "user_list": [ + { + "name": "用户名", + "status": "运行状态", + "config": "配置信息" + } + ] + } + ] + } +} +``` + ```json { "id": "task-uuid",