Merge remote-tracking branch 'upstream/feature/refactor' into feature/refactor
This commit is contained in:
@@ -124,22 +124,37 @@ class GlobalConfig(ConfigBase):
|
|||||||
|
|
||||||
Data_UID = ConfigItem("Data", "UID", str(uuid.uuid4()), UUIDValidator())
|
Data_UID = ConfigItem("Data", "UID", str(uuid.uuid4()), UUIDValidator())
|
||||||
Data_LastStatisticsUpload = ConfigItem(
|
Data_LastStatisticsUpload = ConfigItem(
|
||||||
"Data", "LastStatisticsUpload", "2000-01-01 00:00:00", DateTimeValidator()
|
"Data",
|
||||||
|
"LastStatisticsUpload",
|
||||||
|
"2000-01-01 00:00:00",
|
||||||
|
DateTimeValidator("%Y-%m-%d %H:%M:%S"),
|
||||||
)
|
)
|
||||||
Data_LastStageUpdated = ConfigItem(
|
Data_LastStageUpdated = ConfigItem(
|
||||||
"Data", "LastStageUpdated", "2000-01-01 00:00:00", DateTimeValidator()
|
"Data",
|
||||||
|
"LastStageUpdated",
|
||||||
|
"2000-01-01 00:00:00",
|
||||||
|
DateTimeValidator("%Y-%m-%d %H:%M:%S"),
|
||||||
)
|
)
|
||||||
Data_StageTimeStamp = ConfigItem(
|
Data_StageTimeStamp = ConfigItem(
|
||||||
"Data", "StageTimeStamp", "2000-01-01 00:00:00", DateTimeValidator()
|
"Data",
|
||||||
|
"StageTimeStamp",
|
||||||
|
"2000-01-01 00:00:00",
|
||||||
|
DateTimeValidator("%Y-%m-%d %H:%M:%S"),
|
||||||
)
|
)
|
||||||
Data_Stage = ConfigItem("Data", "Stage", "{ }", JSONValidator())
|
Data_Stage = ConfigItem("Data", "Stage", "{ }", JSONValidator())
|
||||||
Data_LastNoticeUpdated = ConfigItem(
|
Data_LastNoticeUpdated = ConfigItem(
|
||||||
"Data", "LastNoticeUpdated", "2000-01-01 00:00:00", DateTimeValidator()
|
"Data",
|
||||||
|
"LastNoticeUpdated",
|
||||||
|
"2000-01-01 00:00:00",
|
||||||
|
DateTimeValidator("%Y-%m-%d %H:%M:%S"),
|
||||||
)
|
)
|
||||||
Data_IfShowNotice = ConfigItem("Data", "IfShowNotice", True, BoolValidator())
|
Data_IfShowNotice = ConfigItem("Data", "IfShowNotice", True, BoolValidator())
|
||||||
Data_Notice = ConfigItem("Data", "Notice", "{ }", JSONValidator())
|
Data_Notice = ConfigItem("Data", "Notice", "{ }", JSONValidator())
|
||||||
Data_LastWebConfigUpdated = ConfigItem(
|
Data_LastWebConfigUpdated = ConfigItem(
|
||||||
"Data", "LastWebConfigUpdated", "2000-01-01 00:00:00", DateTimeValidator()
|
"Data",
|
||||||
|
"LastWebConfigUpdated",
|
||||||
|
"2000-01-01 00:00:00",
|
||||||
|
DateTimeValidator("%Y-%m-%d %H:%M:%S"),
|
||||||
)
|
)
|
||||||
Data_WebConfig = ConfigItem("Data", "WebConfig", "{ }", JSONValidator())
|
Data_WebConfig = ConfigItem("Data", "WebConfig", "{ }", JSONValidator())
|
||||||
|
|
||||||
@@ -265,11 +280,15 @@ class MaaUserConfig(ConfigBase):
|
|||||||
"Info", "SklandToken", "", EncryptValidator()
|
"Info", "SklandToken", "", EncryptValidator()
|
||||||
)
|
)
|
||||||
|
|
||||||
self.Data_LastProxyDate = ConfigItem("Data", "LastProxyDate", "2000-01-01")
|
self.Data_LastProxyDate = ConfigItem(
|
||||||
self.Data_LastAnnihilationDate = ConfigItem(
|
"Data", "LastProxyDate", "2000-01-01", DateTimeValidator("%Y-%m-%d")
|
||||||
"Data", "LastAnnihilationDate", "2000-01-01"
|
)
|
||||||
|
self.Data_LastAnnihilationDate = ConfigItem(
|
||||||
|
"Data", "LastAnnihilationDate", "2000-01-01", DateTimeValidator("%Y-%m-%d")
|
||||||
|
)
|
||||||
|
self.Data_LastSklandDate = ConfigItem(
|
||||||
|
"Data", "LastSklandDate", "2000-01-01", DateTimeValidator("%Y-%m-%d")
|
||||||
)
|
)
|
||||||
self.Data_LastSklandDate = ConfigItem("Data", "LastSklandDate", "2000-01-01")
|
|
||||||
self.Data_ProxyTimes = ConfigItem(
|
self.Data_ProxyTimes = ConfigItem(
|
||||||
"Data", "ProxyTimes", 0, RangeValidator(0, 9999)
|
"Data", "ProxyTimes", 0, RangeValidator(0, 9999)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -180,9 +180,7 @@ class _TaskManager:
|
|||||||
task["status"] = "运行"
|
task["status"] = "运行"
|
||||||
await Config.send_json(
|
await Config.send_json(
|
||||||
WebSocketMessage(
|
WebSocketMessage(
|
||||||
id=str(task_id),
|
id=str(task_id), type="Update", data={"task_list": task_list}
|
||||||
type="Update",
|
|
||||||
data={"task_list": task_list},
|
|
||||||
).model_dump()
|
).model_dump()
|
||||||
)
|
)
|
||||||
logger.info(f"任务开始: {script_id}")
|
logger.info(f"任务开始: {script_id}")
|
||||||
@@ -270,5 +268,19 @@ class _TaskManager:
|
|||||||
).model_dump()
|
).model_dump()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if mode == "自动代理" and task_id in Config.QueueConfig:
|
||||||
|
|
||||||
|
await Config.send_json(
|
||||||
|
WebSocketMessage(
|
||||||
|
id=str(task_id),
|
||||||
|
type="Signal",
|
||||||
|
data={
|
||||||
|
"power": Config.QueueConfig[task_id].get(
|
||||||
|
"Info", "AfterAccomplish"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
).model_dump()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
TaskManager = _TaskManager()
|
TaskManager = _TaskManager()
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ from typing import List, Any, Dict, Union, Optional
|
|||||||
|
|
||||||
|
|
||||||
from app.utils import dpapi_encrypt, dpapi_decrypt
|
from app.utils import dpapi_encrypt, dpapi_decrypt
|
||||||
from app.utils.constants import RESERVED_NAMES, ILLEGAL_CHARS
|
from app.utils.constants import RESERVED_NAMES, ILLEGAL_CHARS, DEFAULT_DATETIME
|
||||||
|
|
||||||
|
|
||||||
class ConfigValidator:
|
class ConfigValidator:
|
||||||
@@ -114,24 +114,30 @@ class UUIDValidator(ConfigValidator):
|
|||||||
|
|
||||||
|
|
||||||
class DateTimeValidator(ConfigValidator):
|
class DateTimeValidator(ConfigValidator):
|
||||||
|
"""日期时间验证器"""
|
||||||
|
|
||||||
|
def __init__(self, date_format: str) -> None:
|
||||||
|
if not date_format:
|
||||||
|
raise ValueError("日期时间格式不能为空")
|
||||||
|
self.date_format = date_format
|
||||||
|
|
||||||
def validate(self, value: Any) -> bool:
|
def validate(self, value: Any) -> bool:
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
|
datetime.strptime(value, self.date_format)
|
||||||
return True
|
return True
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def correct(self, value: Any) -> str:
|
def correct(self, value: Any) -> str:
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
return "2000-01-01 00:00:00"
|
return DEFAULT_DATETIME.strftime(self.date_format)
|
||||||
try:
|
try:
|
||||||
datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
|
datetime.strptime(value, self.date_format)
|
||||||
return value
|
return value
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return "2000-01-01 00:00:00"
|
return DEFAULT_DATETIME.strftime(self.date_format)
|
||||||
|
|
||||||
|
|
||||||
class JSONValidator(ConfigValidator):
|
class JSONValidator(ConfigValidator):
|
||||||
@@ -302,6 +308,8 @@ class ConfigItem:
|
|||||||
要设置的值, 可以是任何合法类型
|
要设置的值, 可以是任何合法类型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
print(self.group, self.name, value, self.value)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
dpapi_decrypt(self.value)
|
dpapi_decrypt(self.value)
|
||||||
if isinstance(self.validator, EncryptValidator)
|
if isinstance(self.validator, EncryptValidator)
|
||||||
|
|||||||
@@ -1339,7 +1339,7 @@ class MaaManager:
|
|||||||
shutil.copy(
|
shutil.copy(
|
||||||
(
|
(
|
||||||
Path.cwd()
|
Path.cwd()
|
||||||
/ f"data/{self.script_id}/{self.user_id}/ConfigFile/gui.json"
|
/ f"data/{self.script_id}/{self.user_list[self.index]['user_id']}/ConfigFile/gui.json"
|
||||||
),
|
),
|
||||||
self.maa_set_path,
|
self.maa_set_path,
|
||||||
)
|
)
|
||||||
@@ -1597,7 +1597,7 @@ class MaaManager:
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
Path.cwd()
|
Path.cwd()
|
||||||
/ f"data/{self.script_id}/{self.user_id}/Infrastructure/infrastructure.json"
|
/ f"data/{self.script_id}/{self.user_list[self.index]['user_id']}/Infrastructure/infrastructure.json"
|
||||||
).exists():
|
).exists():
|
||||||
|
|
||||||
data["Configurations"]["Default"][
|
data["Configurations"]["Default"][
|
||||||
@@ -1618,7 +1618,7 @@ class MaaManager:
|
|||||||
"Infrast.CustomInfrastFile"
|
"Infrast.CustomInfrastFile"
|
||||||
] = str(
|
] = str(
|
||||||
Path.cwd()
|
Path.cwd()
|
||||||
/ f"data/{self.script_id}/{self.user_id}/Infrastructure/infrastructure.json"
|
/ f"data/{self.script_id}/{self.user_list[self.index]['user_id']}/Infrastructure/infrastructure.json"
|
||||||
) # 自定义基建配置文件地址
|
) # 自定义基建配置文件地址
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
# Contact: DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
RESOURCE_STAGE_INFO = [
|
RESOURCE_STAGE_INFO = [
|
||||||
{"value": "-", "text": "当前/上次", "days": [1, 2, 3, 4, 5, 6, 7]},
|
{"value": "-", "text": "当前/上次", "days": [1, 2, 3, 4, 5, 6, 7]},
|
||||||
{"value": "1-7", "text": "1-7", "days": [1, 2, 3, 4, 5, 6, 7]},
|
{"value": "1-7", "text": "1-7", "days": [1, 2, 3, 4, 5, 6, 7]},
|
||||||
@@ -270,3 +272,6 @@ MIRROR_ERROR_INFO = {
|
|||||||
1: "未知错误类型",
|
1: "未知错误类型",
|
||||||
}
|
}
|
||||||
"""MirrorChyan错误代码映射表"""
|
"""MirrorChyan错误代码映射表"""
|
||||||
|
|
||||||
|
DEFAULT_DATETIME = datetime.strptime("2000-01-01 00:00:00", "%Y-%m-%d %H:%M:%S")
|
||||||
|
"""默认日期时间"""
|
||||||
|
|||||||
388
docs/Backend_Task_Scheduling_and_WebSocket_Messages.md
Normal file
388
docs/Backend_Task_Scheduling_and_WebSocket_Messages.md
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
# AUTO-MAS 后端任务调度逻辑与WebSocket消息格式说明
|
||||||
|
|
||||||
|
## 1. 任务调度架构概览
|
||||||
|
|
||||||
|
AUTO-MAS 后端采用基于 AsyncIO 的异步任务调度系统,主要由以下核心组件构成:
|
||||||
|
|
||||||
|
### 1.1 核心组件
|
||||||
|
|
||||||
|
- **TaskManager**: 任务调度器,负责任务的创建、运行、停止和清理
|
||||||
|
- **Broadcast**: 消息广播系统,负责在不同组件间传递消息
|
||||||
|
- **WebSocket**: 与前端的实时通信通道
|
||||||
|
- **Config**: 配置管理系统,包含脚本配置、队列配置等
|
||||||
|
|
||||||
|
### 1.2 任务类型
|
||||||
|
|
||||||
|
系统支持三种主要任务模式:
|
||||||
|
|
||||||
|
1. **设置脚本** - 直接执行单个脚本配置
|
||||||
|
2. **自动代理** - 按队列顺序自动执行多个脚本
|
||||||
|
3. **人工排查** - 手动排查和执行任务
|
||||||
|
|
||||||
|
## 2. 任务调度流程
|
||||||
|
|
||||||
|
### 2.1 任务创建流程
|
||||||
|
|
||||||
|
```
|
||||||
|
前端请求 → API接口 → TaskManager.add_task() → 任务验证 → 创建异步任务 → 返回任务ID
|
||||||
|
```
|
||||||
|
|
||||||
|
**具体步骤:**
|
||||||
|
|
||||||
|
1. **任务验证**: 根据模式和UID验证任务配置是否存在
|
||||||
|
2. **重复检查**: 确保相同任务未在运行中
|
||||||
|
3. **任务创建**: 使用`asyncio.create_task()`创建异步任务
|
||||||
|
4. **回调设置**: 添加任务完成回调用于清理工作
|
||||||
|
|
||||||
|
### 2.2 任务执行流程
|
||||||
|
|
||||||
|
#### 设置脚本模式
|
||||||
|
```
|
||||||
|
获取脚本配置 → 确定脚本类型(MAA/General) → 创建对应Manager → 执行任务
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 自动代理模式
|
||||||
|
```
|
||||||
|
获取队列配置 → 构建任务列表 → 逐个执行脚本 → 更新状态 → 发送完成信号
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 任务状态管理
|
||||||
|
|
||||||
|
- **等待**: 任务已加入队列但未开始执行
|
||||||
|
- **运行**: 任务正在执行中
|
||||||
|
- **跳过**: 任务因重复或其他原因被跳过
|
||||||
|
- **完成**: 任务执行完毕
|
||||||
|
|
||||||
|
## 3. WebSocket 消息系统
|
||||||
|
|
||||||
|
### 3.1 消息基础结构
|
||||||
|
|
||||||
|
所有WebSocket消息都遵循统一的JSON格式:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "消息ID或任务ID",
|
||||||
|
"type": "消息类型",
|
||||||
|
"data": {
|
||||||
|
"具体数据": "根据类型而定"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 消息类型详解
|
||||||
|
|
||||||
|
#### 3.2.1 Update 类型 - 数据更新
|
||||||
|
|
||||||
|
**用途**: 通知前端更新界面数据
|
||||||
|
|
||||||
|
**常见数据格式:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "task-uuid",
|
||||||
|
"type": "Update",
|
||||||
|
"data": {
|
||||||
|
"user_list": [
|
||||||
|
{
|
||||||
|
"name": "用户名",
|
||||||
|
"status": "运行状态",
|
||||||
|
"config": "配置信息"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "task-uuid",
|
||||||
|
"type": "Update",
|
||||||
|
"data": {
|
||||||
|
"task_list": [
|
||||||
|
{
|
||||||
|
"script_id": "脚本ID",
|
||||||
|
"status": "等待/运行/完成/跳过",
|
||||||
|
"name": "脚本名称"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "task-uuid",
|
||||||
|
"type": "Update",
|
||||||
|
"data": {
|
||||||
|
"log": "任务执行日志内容"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2.2 Info 类型 - 信息显示
|
||||||
|
|
||||||
|
**用途**: 向前端发送需要显示的信息,包括普通信息、警告和错误
|
||||||
|
|
||||||
|
**数据格式:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "task-uuid",
|
||||||
|
"type": "Info",
|
||||||
|
"data": {
|
||||||
|
"Error": "错误信息内容"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "task-uuid",
|
||||||
|
"type": "Info",
|
||||||
|
"data": {
|
||||||
|
"Warning": "警告信息内容"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "task-uuid",
|
||||||
|
"type": "Info",
|
||||||
|
"data": {
|
||||||
|
"Info": "普通信息内容"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2.3 Message 类型 - 对话框请求
|
||||||
|
|
||||||
|
**用途**: 请求前端弹出对话框显示重要信息
|
||||||
|
|
||||||
|
**数据格式:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "task-uuid",
|
||||||
|
"type": "Message",
|
||||||
|
"data": {
|
||||||
|
"title": "对话框标题",
|
||||||
|
"content": "对话框内容",
|
||||||
|
"type": "info/warning/error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2.4 Signal 类型 - 程序信号
|
||||||
|
|
||||||
|
**用途**: 发送程序控制信号和状态通知
|
||||||
|
|
||||||
|
**常见信号:**
|
||||||
|
|
||||||
|
**任务完成信号:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "task-uuid",
|
||||||
|
"type": "Signal",
|
||||||
|
"data": {
|
||||||
|
"Accomplish": "任务完成后调度台显示的日志内容"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**电源操作信号:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "task-uuid",
|
||||||
|
"type": "Signal",
|
||||||
|
"data": {
|
||||||
|
"power": "NoAction/KillSelf/Sleep/Hibernate/Shutdown/ShutdownForce",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**心跳信号:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "Main",
|
||||||
|
"type": "Signal",
|
||||||
|
"data": {
|
||||||
|
"Ping": "无描述"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "Main",
|
||||||
|
"type": "Signal",
|
||||||
|
"data": {
|
||||||
|
"Pong": "无描述"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 任务管理器详细说明
|
||||||
|
|
||||||
|
### 4.1 TaskManager 核心方法
|
||||||
|
|
||||||
|
#### add_task(mode: str, uid: str)
|
||||||
|
- **功能**: 添加新任务到调度队列
|
||||||
|
- **参数**:
|
||||||
|
- `mode`: 任务模式 ("设置脚本", "自动代理", "人工排查")
|
||||||
|
- `uid`: 任务唯一标识符
|
||||||
|
- **返回**: 任务UUID
|
||||||
|
|
||||||
|
#### stop_task(task_id: str)
|
||||||
|
- **功能**: 停止指定任务
|
||||||
|
- **参数**:
|
||||||
|
- `task_id`: 任务ID,支持 "ALL" 停止所有任务
|
||||||
|
|
||||||
|
#### run_task(mode: str, task_id: UUID, actual_id: Optional[UUID])
|
||||||
|
- **功能**: 执行具体任务逻辑
|
||||||
|
- **流程**: 根据模式选择相应的执行策略
|
||||||
|
|
||||||
|
### 4.2 任务执行器
|
||||||
|
|
||||||
|
#### GeneralManager
|
||||||
|
- **用途**: 处理通用脚本任务
|
||||||
|
- **特点**: 支持自定义脚本路径和参数
|
||||||
|
- **配置**: 基于 GeneralConfig 和 GeneralUserConfig
|
||||||
|
|
||||||
|
#### MaaManager
|
||||||
|
- **用途**: 处理MAA (明日方舟助手) 专用任务
|
||||||
|
- **特点**: 支持模拟器控制、ADB连接、游戏自动化
|
||||||
|
- **配置**: 基于 MaaConfig 和 MaaUserConfig
|
||||||
|
|
||||||
|
## 5. 消息广播系统
|
||||||
|
|
||||||
|
### 5.1 Broadcast 机制
|
||||||
|
|
||||||
|
- **设计模式**: 发布-订阅模式
|
||||||
|
- **功能**: 实现组件间解耦的消息传递
|
||||||
|
- **特点**: 支持多个订阅者同时接收消息
|
||||||
|
|
||||||
|
### 5.2 消息流向
|
||||||
|
|
||||||
|
```
|
||||||
|
任务执行器 → Broadcast → WebSocket → 前端界面
|
||||||
|
↓
|
||||||
|
其他订阅者
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 配置管理系统
|
||||||
|
|
||||||
|
### 6.1 配置类型
|
||||||
|
|
||||||
|
- **ScriptConfig**: 脚本配置,包含MAA和General两种类型
|
||||||
|
- **QueueConfig**: 队列配置,定义自动代理任务的执行顺序
|
||||||
|
- **GlobalConfig**: 全局系统配置
|
||||||
|
|
||||||
|
### 6.2 配置操作
|
||||||
|
|
||||||
|
- **锁机制**: 防止配置在使用时被修改
|
||||||
|
- **实时更新**: 支持动态加载配置变更
|
||||||
|
- **类型验证**: 确保配置数据的正确性
|
||||||
|
|
||||||
|
## 7. API 接口说明
|
||||||
|
|
||||||
|
### 7.1 任务控制接口
|
||||||
|
|
||||||
|
**创建任务**
|
||||||
|
- **端点**: `POST /api/dispatch/start`
|
||||||
|
- **请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mode": "自动代理|人工排查|设置脚本",
|
||||||
|
"taskId": "目标任务ID"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"status": "success",
|
||||||
|
"message": "操作成功",
|
||||||
|
"websocketId": "新任务ID"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**停止任务**
|
||||||
|
- **端点**: `POST /api/dispatch/stop`
|
||||||
|
- **请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"taskId": "要停止的任务ID"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**电源操作**
|
||||||
|
- **端点**: `POST /api/dispatch/power`
|
||||||
|
- **请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"signal": "NoAction|Shutdown|ShutdownForce|Hibernate|Sleep|KillSelf"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 WebSocket 连接
|
||||||
|
|
||||||
|
**端点**: `WS /api/core/ws`
|
||||||
|
|
||||||
|
**连接特性**:
|
||||||
|
- 同时只允许一个WebSocket连接
|
||||||
|
- 自动心跳检测 (15秒超时)
|
||||||
|
- 连接断开时自动清理资源
|
||||||
|
|
||||||
|
## 8. 错误处理机制
|
||||||
|
|
||||||
|
### 8.1 异常类型
|
||||||
|
|
||||||
|
- **ValueError**: 配置验证失败
|
||||||
|
- **RuntimeError**: 任务状态冲突
|
||||||
|
- **TimeoutError**: 操作超时
|
||||||
|
- **ConnectionError**: 连接相关错误
|
||||||
|
|
||||||
|
### 8.2 错误响应格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "相关任务ID",
|
||||||
|
"type": "Info",
|
||||||
|
"data": {
|
||||||
|
"Error": "具体错误描述"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 性能和监控
|
||||||
|
|
||||||
|
### 9.1 日志系统
|
||||||
|
|
||||||
|
- **分层日志**: 按模块划分日志记录器
|
||||||
|
- **实时监控**: 支持日志实时推送到前端
|
||||||
|
- **文件轮转**: 自动管理日志文件大小
|
||||||
|
|
||||||
|
### 9.2 资源管理
|
||||||
|
|
||||||
|
- **进程管理**: 自动清理子进程
|
||||||
|
- **内存监控**: 防止内存泄漏
|
||||||
|
- **连接池**: 复用数据库和网络连接
|
||||||
|
|
||||||
|
## 10. 安全考虑
|
||||||
|
|
||||||
|
### 10.1 输入验证
|
||||||
|
|
||||||
|
- **参数校验**: 使用 Pydantic 模型验证
|
||||||
|
- **路径安全**: 防止路径遍历攻击
|
||||||
|
- **命令注入**: 严格控制执行的命令参数
|
||||||
|
|
||||||
|
### 10.2 权限控制
|
||||||
|
|
||||||
|
- **单一连接**: 限制WebSocket连接数量
|
||||||
|
- **操作限制**: 防止重复或冲突操作
|
||||||
|
- **资源保护**: 防止资源滥用
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*此文档基于 AUTO-MAS v5.0.0 版本编写,详细的API文档和配置说明请参考相关配置文件和源代码注释。*
|
||||||
@@ -62,9 +62,9 @@ const mainMenuItems = [
|
|||||||
{ key: '/plans', label: '计划管理', icon: icon(CalendarOutlined) },
|
{ key: '/plans', label: '计划管理', icon: icon(CalendarOutlined) },
|
||||||
{ key: '/queue', label: '调度队列', icon: icon(UnorderedListOutlined) },
|
{ key: '/queue', label: '调度队列', icon: icon(UnorderedListOutlined) },
|
||||||
{ key: '/scheduler', label: '调度中心', icon: icon(ControlOutlined) },
|
{ key: '/scheduler', label: '调度中心', icon: icon(ControlOutlined) },
|
||||||
{ key: '/history', label: '历史记录', icon: icon(HistoryOutlined) },
|
|
||||||
]
|
]
|
||||||
const bottomMenuItems = [
|
const bottomMenuItems = [
|
||||||
|
{ key: '/history', label: '历史记录', icon: icon(HistoryOutlined) },
|
||||||
{ key: '/settings', label: '设置', icon: icon(SettingOutlined) },
|
{ key: '/settings', label: '设置', icon: icon(SettingOutlined) },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -37,24 +37,24 @@
|
|||||||
type="primary"
|
type="primary"
|
||||||
ghost
|
ghost
|
||||||
size="middle"
|
size="middle"
|
||||||
@click="handleMAAConfig(script)"
|
@click="handleStartMAAConfig(script)"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<SettingOutlined />
|
<SettingOutlined />
|
||||||
</template>
|
</template>
|
||||||
设置MAA全局配置
|
配置MAA
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button
|
<a-button
|
||||||
v-if="script.type === 'MAA' && props.activeConnections.has(script.id)"
|
v-if="script.type === 'MAA' && props.activeConnections.has(script.id)"
|
||||||
type="primary"
|
type="primary"
|
||||||
danger
|
|
||||||
size="middle"
|
size="middle"
|
||||||
@click="handleDisconnectMAA(script)"
|
style="background: #52c41a; border-color: #52c41a;"
|
||||||
|
@click="handleSaveMAAConfig(script)"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<StopOutlined />
|
<SaveOutlined />
|
||||||
</template>
|
</template>
|
||||||
断开配置连接
|
保存配置
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="default" size="middle" @click="handleEdit(script)">
|
<a-button type="default" size="middle" @click="handleEdit(script)">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
@@ -291,8 +291,8 @@ import type { Script, User } from '../types/script'
|
|||||||
import {
|
import {
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
|
SaveOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
StopOutlined,
|
|
||||||
UserAddOutlined,
|
UserAddOutlined,
|
||||||
} from '@ant-design/icons-vue'
|
} from '@ant-design/icons-vue'
|
||||||
|
|
||||||
@@ -312,9 +312,9 @@ interface Emits {
|
|||||||
|
|
||||||
(e: 'deleteUser', user: User): void
|
(e: 'deleteUser', user: User): void
|
||||||
|
|
||||||
(e: 'maaConfig', script: Script): void
|
(e: 'startMaaConfig', script: Script): void
|
||||||
|
|
||||||
(e: 'disconnectMaa', script: Script): void
|
(e: 'saveMaaConfig', script: Script): void
|
||||||
|
|
||||||
(e: 'toggleUserStatus', user: User): void
|
(e: 'toggleUserStatus', user: User): void
|
||||||
}
|
}
|
||||||
@@ -350,12 +350,12 @@ const handleDeleteUser = (user: User) => {
|
|||||||
emit('deleteUser', user)
|
emit('deleteUser', user)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMAAConfig = (script: Script) => {
|
const handleStartMAAConfig = (script: Script) => {
|
||||||
emit('maaConfig', script)
|
emit('startMaaConfig', script)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDisconnectMAA = (script: Script) => {
|
const handleSaveMAAConfig = (script: Script) => {
|
||||||
emit('disconnectMaa', script)
|
emit('saveMaaConfig', script)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleToggleUserStatus = (user: User) => {
|
const handleToggleUserStatus = (user: User) => {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -135,7 +135,7 @@
|
|||||||
:placeholder="getPlaceholder(record.taskName)" class="config-input-number"
|
:placeholder="getPlaceholder(record.taskName)" class="config-input-number"
|
||||||
:controls="false" :disabled="isColumnDisabled(column.key)" />
|
:controls="false" :disabled="isColumnDisabled(column.key)" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="['关卡选择', '备选关卡-1', '备选关卡-2', '备选关卡-3', '剩余理智'].includes(record.taskName)">
|
<template v-else-if="['关卡选择', '备选关卡-1', '备选关卡-2', '备选关卡-3', '剩余理智关卡'].includes(record.taskName)">
|
||||||
<a-select
|
<a-select
|
||||||
v-model:value="record[column.key]"
|
v-model:value="record[column.key]"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -436,14 +436,14 @@ const tableData = ref([
|
|||||||
{
|
{
|
||||||
key: 'SeriesNumb',
|
key: 'SeriesNumb',
|
||||||
taskName: '连战次数',
|
taskName: '连战次数',
|
||||||
ALL: '-1',
|
ALL: '0',
|
||||||
Monday: '-1',
|
Monday: '0',
|
||||||
Tuesday: '-1',
|
Tuesday: '0',
|
||||||
Wednesday: '-1',
|
Wednesday: '0',
|
||||||
Thursday: '-1',
|
Thursday: '0',
|
||||||
Friday: '-1',
|
Friday: '0',
|
||||||
Saturday: '-1',
|
Saturday: '0',
|
||||||
Sunday: '-1',
|
Sunday: '0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'Stage',
|
key: 'Stage',
|
||||||
@@ -495,7 +495,7 @@ const tableData = ref([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'Stage_Remain',
|
key: 'Stage_Remain',
|
||||||
taskName: '剩余理智',
|
taskName: '剩余理智关卡',
|
||||||
ALL: '-',
|
ALL: '-',
|
||||||
Monday: '-',
|
Monday: '-',
|
||||||
Tuesday: '-',
|
Tuesday: '-',
|
||||||
@@ -535,10 +535,10 @@ const STAGE_DAILY_INFO = [
|
|||||||
{ value: '1-7', text: '1-7', days: [1, 2, 3, 4, 5, 6, 7] },
|
{ value: '1-7', text: '1-7', days: [1, 2, 3, 4, 5, 6, 7] },
|
||||||
{ value: 'R8-11', text: 'R8-11', days: [1, 2, 3, 4, 5, 6, 7] },
|
{ value: 'R8-11', text: 'R8-11', days: [1, 2, 3, 4, 5, 6, 7] },
|
||||||
{ value: '12-17-HARD', text: '12-17-HARD', days: [1, 2, 3, 4, 5, 6, 7] },
|
{ value: '12-17-HARD', text: '12-17-HARD', days: [1, 2, 3, 4, 5, 6, 7] },
|
||||||
|
{ value: 'LS-6', text: '经验-6/5', days: [1, 2, 3, 4, 5, 6, 7] },
|
||||||
{ value: 'CE-6', text: '龙门币-6/5', days: [2, 4, 6, 7] },
|
{ value: 'CE-6', text: '龙门币-6/5', days: [2, 4, 6, 7] },
|
||||||
{ value: 'AP-5', text: '红票-5', days: [1, 4, 6, 7] },
|
{ value: 'AP-5', text: '红票-5', days: [1, 4, 6, 7] },
|
||||||
{ value: 'CA-5', text: '技能-5', days: [2, 3, 5, 7] },
|
{ value: 'CA-5', text: '技能-5', days: [2, 3, 5, 7] },
|
||||||
{ value: 'LS-6', text: '经验-6/5', days: [1, 2, 3, 4, 5, 6, 7] },
|
|
||||||
{ value: 'SK-5', text: '碳-5', days: [1, 3, 5, 6] },
|
{ value: 'SK-5', text: '碳-5', days: [1, 3, 5, 6] },
|
||||||
{ value: 'PR-A-1', text: '奶/盾芯片', days: [1, 4, 5, 7] },
|
{ value: 'PR-A-1', text: '奶/盾芯片', days: [1, 4, 5, 7] },
|
||||||
{ value: 'PR-A-2', text: '奶/盾芯片组', days: [1, 4, 5, 7] },
|
{ value: 'PR-A-2', text: '奶/盾芯片组', days: [1, 4, 5, 7] },
|
||||||
@@ -590,20 +590,20 @@ const getSelectOptions = (columnKey: string, taskName: string, currentValue?: st
|
|||||||
switch (taskName) {
|
switch (taskName) {
|
||||||
case '连战次数':
|
case '连战次数':
|
||||||
return [
|
return [
|
||||||
{ label: '不选择', value: '0' },
|
{ label: 'AUTO', value: '0' },
|
||||||
{ label: '1', value: '1' },
|
{ label: '1', value: '1' },
|
||||||
{ label: '2', value: '2' },
|
{ label: '2', value: '2' },
|
||||||
{ label: '3', value: '3' },
|
{ label: '3', value: '3' },
|
||||||
{ label: '4', value: '4' },
|
{ label: '4', value: '4' },
|
||||||
{ label: '5', value: '5' },
|
{ label: '5', value: '5' },
|
||||||
{ label: '6', value: '6' },
|
{ label: '6', value: '6' },
|
||||||
{ label: 'AUTO', value: '-1' },
|
{ label: '不切换', value: '-1' },
|
||||||
]
|
]
|
||||||
case '关卡选择':
|
case '关卡选择':
|
||||||
case '备选关卡-1':
|
case '备选关卡-1':
|
||||||
case '备选关卡-2':
|
case '备选关卡-2':
|
||||||
case '备选关卡-3':
|
case '备选关卡-3':
|
||||||
case '剩余理智': {
|
case '剩余理智关卡': {
|
||||||
const dayNumber = getDayNumber(columnKey)
|
const dayNumber = getDayNumber(columnKey)
|
||||||
|
|
||||||
// 基础关卡选项
|
// 基础关卡选项
|
||||||
@@ -611,14 +611,14 @@ const getSelectOptions = (columnKey: string, taskName: string, currentValue?: st
|
|||||||
if (dayNumber === 0) {
|
if (dayNumber === 0) {
|
||||||
// 如果是全局列,显示所有选项
|
// 如果是全局列,显示所有选项
|
||||||
baseOptions = STAGE_DAILY_INFO.map(stage => ({
|
baseOptions = STAGE_DAILY_INFO.map(stage => ({
|
||||||
label: stage.text,
|
label: taskName === '剩余理智关卡' && stage.value === '-' ? '不选择' : stage.text,
|
||||||
value: stage.value,
|
value: stage.value,
|
||||||
isCustom: false
|
isCustom: false
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
// 根据星期过滤可用的关卡
|
// 根据星期过滤可用的关卡
|
||||||
baseOptions = STAGE_DAILY_INFO.filter(stage => stage.days.includes(dayNumber)).map(stage => ({
|
baseOptions = STAGE_DAILY_INFO.filter(stage => stage.days.includes(dayNumber)).map(stage => ({
|
||||||
label: stage.text,
|
label: taskName === '剩余理智关卡' && stage.value === '-' ? '不选择' : stage.text,
|
||||||
value: stage.value,
|
value: stage.value,
|
||||||
isCustom: false
|
isCustom: false
|
||||||
}))
|
}))
|
||||||
@@ -670,8 +670,8 @@ const getPlaceholder = (taskName: string) => {
|
|||||||
case '备选关卡-2':
|
case '备选关卡-2':
|
||||||
case '备选关卡-3':
|
case '备选关卡-3':
|
||||||
return '1-7'
|
return '1-7'
|
||||||
case '剩余理智':
|
case '剩余理智关卡':
|
||||||
return '-'
|
return '不选择'
|
||||||
default:
|
default:
|
||||||
return '请选择'
|
return '请选择'
|
||||||
}
|
}
|
||||||
@@ -1561,52 +1561,78 @@ const disableAllStages = (stageValue: string) => {
|
|||||||
|
|
||||||
/* 任务名称单元格背景色 */
|
/* 任务名称单元格背景色 */
|
||||||
.config-table :deep(.task-row-MedicineNumb td:first-child) {
|
.config-table :deep(.task-row-MedicineNumb td:first-child) {
|
||||||
background: rgba(59, 130, 246, 0.1);
|
background: #EBF4FF !important; /* 不透明的蓝色背景 */
|
||||||
color: #3B82F6;
|
color: #3B82F6;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.config-table :deep(.ant-table-tbody > tr.task-row-MedicineNumb:hover > td:first-child) {
|
.config-table :deep(.ant-table-tbody > tr.task-row-MedicineNumb:hover > td:first-child) {
|
||||||
background: rgba(59, 130, 246, 0.2);
|
background: #DBEAFE !important; /* 悬停时稍深的蓝色 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-table :deep(.task-row-SeriesNumb td:first-child) {
|
.config-table :deep(.task-row-SeriesNumb td:first-child) {
|
||||||
background: rgba(34, 197, 94, 0.1);
|
background: #ECFDF5 !important; /* 不透明的绿色背景 */
|
||||||
color: #22C55E;
|
color: #22C55E;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.config-table :deep(.ant-table-tbody > tr.task-row-SeriesNumb:hover > td:first-child) {
|
.config-table :deep(.ant-table-tbody > tr.task-row-SeriesNumb:hover > td:first-child) {
|
||||||
background: rgba(34, 197, 94, 0.2);
|
background: #D1FAE5 !important; /* 悬停时稍深的绿色 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-table :deep(.task-row-Stage td:first-child) {
|
.config-table :deep(.task-row-Stage td:first-child) {
|
||||||
background: rgba(249, 115, 22, 0.1);
|
background: #FFF7ED !important; /* 不透明的橙色背景 */
|
||||||
color: #F97316;
|
color: #F97316;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.config-table :deep(.ant-table-tbody > tr.task-row-Stage:hover > td:first-child) {
|
.config-table :deep(.ant-table-tbody > tr.task-row-Stage:hover > td:first-child) {
|
||||||
background: rgba(249, 115, 22, 0.2);
|
background: #FED7AA !important; /* 悬停时稍深的橙色 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-table :deep(.task-row-Stage_1 td:first-child),
|
.config-table :deep(.task-row-Stage_1 td:first-child),
|
||||||
.config-table :deep(.task-row-Stage_2 td:first-child),
|
.config-table :deep(.task-row-Stage_2 td:first-child),
|
||||||
.config-table :deep(.task-row-Stage_3 td:first-child) {
|
.config-table :deep(.task-row-Stage_3 td:first-child) {
|
||||||
background: rgba(168, 85, 247, 0.1);
|
background: #FAF5FF !important; /* 不透明的紫色背景 */
|
||||||
color: #A855F7;
|
color: #A855F7;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.config-table :deep(.ant-table-tbody > tr.task-row-Stage_1:hover > td:first-child),
|
.config-table :deep(.ant-table-tbody > tr.task-row-Stage_1:hover > td:first-child),
|
||||||
.config-table :deep(.ant-table-tbody > tr.task-row-Stage_2:hover > td:first-child),
|
.config-table :deep(.ant-table-tbody > tr.task-row-Stage_2:hover > td:first-child),
|
||||||
.config-table :deep(.ant-table-tbody > tr.task-row-Stage_3:hover > td:first-child) {
|
.config-table :deep(.ant-table-tbody > tr.task-row-Stage_3:hover > td:first-child) {
|
||||||
background: rgba(168, 85, 247, 0.2);
|
background: #F3E8FF !important; /* 悬停时稍深的紫色 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-table :deep(.task-row-Stage_Remain td:first-child) {
|
.config-table :deep(.task-row-Stage_Remain td:first-child) {
|
||||||
background: rgba(14, 165, 233, 0.1);
|
background: #F0F9FF !important; /* 不透明的天蓝色背景 */
|
||||||
color: #0EA5E9;
|
color: #0EA5E9;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.config-table :deep(.ant-table-tbody > tr.task-row-Stage_Remain:hover > td:first-child) {
|
.config-table :deep(.ant-table-tbody > tr.task-row-Stage_Remain:hover > td:first-child) {
|
||||||
background: rgba(14, 165, 233, 0.2);
|
background: #E0F2FE !important; /* 悬停时稍深的天蓝色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保固定列在滚动时背景不透明 */
|
||||||
|
.config-table :deep(.ant-table-fixed-left) {
|
||||||
|
background: var(--ant-color-bg-container) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-table :deep(.ant-table-thead > tr > th.ant-table-fixed-left) {
|
||||||
|
background: var(--ant-color-bg-container) !important;
|
||||||
|
border-right: 1px solid var(--ant-color-border);
|
||||||
|
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 专门处理"配置项"表头单元格 */
|
||||||
|
.config-table :deep(.ant-table-thead > tr > th:first-child) {
|
||||||
|
background: var(--ant-color-bg-container) !important;
|
||||||
|
border-right: 1px solid var(--ant-color-border);
|
||||||
|
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.05);
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-table :deep(.ant-table-tbody > tr > td.ant-table-fixed-left) {
|
||||||
|
background: inherit !important;
|
||||||
|
border-right: 1px solid var(--ant-color-border-secondary);
|
||||||
|
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 禁用列标题样式 */
|
/* 禁用列标题样式 */
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -40,8 +40,8 @@
|
|||||||
@add-user="handleAddUser"
|
@add-user="handleAddUser"
|
||||||
@edit-user="handleEditUser"
|
@edit-user="handleEditUser"
|
||||||
@delete-user="handleDeleteUser"
|
@delete-user="handleDeleteUser"
|
||||||
@maa-config="handleMAAConfig"
|
@start-maa-config="handleStartMAAConfig"
|
||||||
@disconnect-maa="handleDisconnectMAA"
|
@save-maa-config="handleSaveMAAConfig"
|
||||||
@toggle-user-status="handleToggleUserStatus"
|
@toggle-user-status="handleToggleUserStatus"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -232,6 +232,8 @@ import { useScriptApi } from '@/composables/useScriptApi'
|
|||||||
import { useUserApi } from '@/composables/useUserApi'
|
import { useUserApi } from '@/composables/useUserApi'
|
||||||
import { useWebSocket } from '@/composables/useWebSocket'
|
import { useWebSocket } from '@/composables/useWebSocket'
|
||||||
import { useTemplateApi, type WebConfigTemplate } from '@/composables/useTemplateApi'
|
import { useTemplateApi, type WebConfigTemplate } from '@/composables/useTemplateApi'
|
||||||
|
import { Service } from '@/api/services/Service'
|
||||||
|
import { TaskCreateIn } from '@/api/models/TaskCreateIn'
|
||||||
import MarkdownIt from 'markdown-it'
|
import MarkdownIt from 'markdown-it'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -494,51 +496,89 @@ const handleDeleteUser = async (user: User) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMAAConfig = async (script: Script) => {
|
const handleStartMAAConfig = async (script: Script) => {
|
||||||
try {
|
try {
|
||||||
// 检查是否已有连接
|
// 检查是否已有连接
|
||||||
const existingConnection = activeConnections.value.get(script.id)
|
const existingConnection = activeConnections.value.get(script.id)
|
||||||
if (existingConnection) {
|
if (existingConnection) {
|
||||||
message.warning('该脚本已在配置中,请先断开连接')
|
message.warning('该脚本已在配置中,请先保存配置')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新订阅
|
// 调用启动配置任务API
|
||||||
subscribe(script.id, {
|
const response = await Service.addTaskApiDispatchStartPost({
|
||||||
onError: error => {
|
taskId: script.id,
|
||||||
console.error(`脚本 ${script.name} 连接错误:`, error)
|
mode: TaskCreateIn.mode.SettingScriptMode
|
||||||
message.error(`MAA配置连接失败: ${error}`)
|
|
||||||
activeConnections.value.delete(script.id)
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 记录连接
|
if (response.code === 200) {
|
||||||
activeConnections.value.set(script.id, script.id)
|
// 订阅WebSocket消息
|
||||||
message.success(`已开始配置 ${script.name}`)
|
subscribe(response.websocketId, {
|
||||||
|
onError: error => {
|
||||||
// 可选:设置自动断开连接的定时器(比如30分钟后)
|
console.error(`脚本 ${script.name} 连接错误:`, error)
|
||||||
setTimeout(
|
message.error(`MAA配置连接失败: ${error}`)
|
||||||
() => {
|
|
||||||
if (activeConnections.value.has(script.id)) {
|
|
||||||
unsubscribe(script.id)
|
|
||||||
activeConnections.value.delete(script.id)
|
activeConnections.value.delete(script.id)
|
||||||
message.info(`${script.name} 配置会话已超时断开`)
|
},
|
||||||
|
onResult: (data: any) => {
|
||||||
|
// 处理配置完成消息(兼容任何结构)
|
||||||
|
if (data.Accomplish) {
|
||||||
|
message.success(`${script.name} 配置已完成`)
|
||||||
|
activeConnections.value.delete(script.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
30 * 60 * 1000
|
|
||||||
) // 30分钟
|
// 记录连接和websocketId
|
||||||
|
activeConnections.value.set(script.id, response.websocketId)
|
||||||
|
message.success(`已启动 ${script.name} 的MAA配置`)
|
||||||
|
|
||||||
|
// 设置自动断开连接的定时器(30分钟后)
|
||||||
|
setTimeout(
|
||||||
|
() => {
|
||||||
|
if (activeConnections.value.has(script.id)) {
|
||||||
|
const wsId = activeConnections.value.get(script.id)
|
||||||
|
if (wsId) {
|
||||||
|
unsubscribe(wsId)
|
||||||
|
}
|
||||||
|
activeConnections.value.delete(script.id)
|
||||||
|
message.info(`${script.name} 配置会话已超时断开`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
30 * 60 * 1000
|
||||||
|
) // 30分钟
|
||||||
|
} else {
|
||||||
|
message.error(response.message || '启动MAA配置失败')
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('MAA配置失败:', error)
|
console.error('启动MAA配置失败:', error)
|
||||||
message.error('MAA配置失败')
|
message.error('启动MAA配置失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDisconnectMAA = (script: Script) => {
|
const handleSaveMAAConfig = async (script: Script) => {
|
||||||
const connectionId = activeConnections.value.get(script.id)
|
try {
|
||||||
if (connectionId) {
|
const websocketId = activeConnections.value.get(script.id)
|
||||||
unsubscribe(script.id)
|
if (!websocketId) {
|
||||||
activeConnections.value.delete(script.id)
|
message.error('未找到活动的配置会话')
|
||||||
message.success(`已断开 ${script.name} 的配置连接`)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用停止配置任务API
|
||||||
|
const response = await Service.stopTaskApiDispatchStopPost({
|
||||||
|
taskId: websocketId
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
// 取消订阅
|
||||||
|
unsubscribe(websocketId)
|
||||||
|
activeConnections.value.delete(script.id)
|
||||||
|
message.success(`${script.name} 的配置已保存`)
|
||||||
|
} else {
|
||||||
|
message.error(response.message || '保存配置失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存MAA配置失败:', error)
|
||||||
|
message.error('保存MAA配置失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import { useUpdateChecker } from '@/composables/useUpdateChecker'
|
|||||||
import { Service, type VersionOut } from '@/api'
|
import { Service, type VersionOut } from '@/api'
|
||||||
import UpdateModal from '@/components/UpdateModal.vue'
|
import UpdateModal from '@/components/UpdateModal.vue'
|
||||||
import { mirrorManager } from '@/utils/mirrorManager'
|
import { mirrorManager } from '@/utils/mirrorManager'
|
||||||
import { request } from '@/api/core/request'
|
|
||||||
import { OpenAPI } from '@/api'
|
|
||||||
|
|
||||||
// 引入拆分后的 Tab 组件
|
// 引入拆分后的 Tab 组件
|
||||||
import TabBasic from './TabBasic.vue'
|
import TabBasic from './TabBasic.vue'
|
||||||
@@ -243,7 +241,7 @@ const testingNotify = ref(false)
|
|||||||
const testNotify = async () => {
|
const testNotify = async () => {
|
||||||
testingNotify.value = true
|
testingNotify.value = true
|
||||||
try {
|
try {
|
||||||
const res: any = await request<any>(OpenAPI, { method: 'POST', url: '/api/setting/test_notify' })
|
const res = await Service.testNotifyApiSettingTestNotifyPost()
|
||||||
if (res?.code && res.code !== 200) message.warning(res?.message || '测试通知发送结果未知')
|
if (res?.code && res.code !== 200) message.warning(res?.message || '测试通知发送结果未知')
|
||||||
else message.success('测试通知已发送')
|
else message.success('测试通知已发送')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user