VWED_server/services/task_edit_service.py

1054 lines
42 KiB
Python
Raw Normal View History

2025-04-30 16:57:46 +08:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
任务编辑服务模块
提供任务编辑相关的业务逻辑
"""
import json
import uuid
from typing import Dict, List, Any, Optional
from datetime import datetime
from sqlalchemy import select, and_, func, update
from routes.model.task_edit_model import (
TaskBasicInfo, TaskBackupRequest, SubTaskListParams,
TaskEditRunRequest
)
from data.models.taskdef import VWEDTaskDef
from data.models.taskrecord import VWEDTaskRecord
from data.session import get_async_session
from utils.component_manager import component_manager
from data.enum.task_def_enum import EnableStatus, PeriodicTaskStatus, TaskStatusEnum
from data.enum.task_record_enum import TaskStatus
from utils.logger import get_logger
# 获取日志记录器
logger = get_logger("services.task_edit_service")
class TaskEditService:
"""任务编辑服务类"""
@staticmethod
async def get_blocks() -> List[Dict[str, Any]]:
"""
获取系统中所有可用的块数据
用于任务编辑页面左侧的组件面板显示
Returns:
List[Dict[str, Any]]: 所有组件分类及其组件
"""
try:
# 通过组件管理器获取所有块数据
blocks = component_manager.get_all_blocks()
return blocks
except Exception as e:
logger.error(f"获取块数据失败: {str(e)}")
raise
@staticmethod
async def get_task_source(task_id: str) -> Optional[Dict[str, Any]]:
"""
获取指定任务的源码详情数据
Args:
task_id: 任务ID
Returns:
Optional[Dict[str, Any]]: 任务详情数据
"""
try:
async with get_async_session() as session:
# 查询任务定义
result = await session.execute(
select(VWEDTaskDef).where(VWEDTaskDef.id == task_id)
)
task_def = result.scalars().first()
if not task_def:
logger.warning(f"任务不存在: {task_id}")
return None
# 解析详情JSON
detail = json.loads(task_def.detail) if task_def.detail else {}
# 构建响应数据
response = {
"id": task_def.id,
"label": task_def.label,
"version": task_def.version,
"detail": detail,
"delay": task_def.delay,
"ifEnable": task_def.if_enable,
"period": task_def.period,
"periodicTask": task_def.periodic_task,
"remark": task_def.remark,
"status": task_def.status,
"templateName": task_def.template_name,
"templateDescription": task_def.template_name, # 使用模板名称作为描述
"createDate": task_def.created_at,
"createdBy": task_def.created_by
}
return response
except Exception as e:
logger.log_error_with_trace("获取任务源码详情失败", e)
raise
@staticmethod
async def save_task_edit(task_def_data: Dict[str, Any]) -> Dict[str, Any]:
"""
保存任务编辑数据
Args:
task_def_data: 任务定义数据已通过TaskSaveRequest模型验证
必须包含id和detail字段
label字段是可选的
detail字段已被验证包含inputParamsoutputParams和rootBlock结构
Returns:
Dict[str, Any]: 保存结果
"""
try:
task_id = task_def_data.get("id")
if not task_id:
logger.error("缺少任务ID")
return {"code": 400, "message": "缺少任务ID", "success": False}
# 在API层已经调用check_task_changes此处不再重复检查
async with get_async_session() as session:
# 查询任务是否存在
result = await session.execute(
select(VWEDTaskDef).where(VWEDTaskDef.id == task_id)
)
task_def = result.scalars().first()
if not task_def:
logger.error(f"任务不存在: {task_id}")
return {"code": 404, "message": "任务不存在", "success": False}
# 更新版本号
new_version = task_def.version + 1
# 只有当提供了label且非空时才更新任务名称
label = task_def_data.get("label")
if label and label.strip():
task_def.label = label
# 转换detail为JSON字符串 - 使用ensure_ascii=False确保中文直接存储而不被编码
detail = task_def_data.get("detail")
if detail:
# detail字段已通过TaskDetailSave模型验证可以直接转换为JSON字符串
task_def.detail = json.dumps(detail, ensure_ascii=False)
task_def.version = new_version
# 保存到数据库
await session.commit()
# 获取当前时间作为更新时间
update_time = datetime.now()
return {
"code": 200,
"message": "保存成功",
"success": True,
"id": task_def.id,
"version": new_version,
"updateTime": update_time
}
except Exception as e:
logger.error(f"保存任务编辑数据失败: {str(e)}")
raise
@staticmethod
async def backup_task(task_id: str, backup_request: TaskBackupRequest) -> Optional[Dict[str, Any]]:
"""
备份任务
Args:
task_id: 源任务ID
backup_request: 备份请求
Returns:
Optional[Dict[str, Any]]: 备份结果
"""
try:
async with get_async_session() as session:
# 查询源任务
result = await session.execute(
select(VWEDTaskDef).where(VWEDTaskDef.id == task_id)
)
source_task = result.scalars().first()
if not source_task:
logger.error(f"源任务不存在: {task_id}")
return None
# 创建新任务ID
new_task_id = str(uuid.uuid4())
# 设置备份任务名称
backup_name = backup_request.backup_name
if not backup_name:
backup_name = f"{source_task.label}-备份"
# 创建备份任务
backup_task = VWEDTaskDef(
id=new_task_id,
label=backup_name,
version=1,
detail=source_task.detail,
delay=source_task.delay,
if_enable=EnableStatus.DISABLED, # 默认禁用
period=source_task.period,
periodic_task=source_task.periodic_task,
remark=backup_request.remark,
status=TaskStatusEnum.PENDING,
template_name=source_task.template_name,
created_by=source_task.created_by,
tenant_id=source_task.tenant_id,
release_sites=source_task.release_sites
)
session.add(backup_task)
await session.commit()
return {
"id": backup_task.id,
"label": backup_task.label,
"version": backup_task.version,
"templateName": backup_task.template_name,
"periodicTask": backup_task.periodic_task,
"ifEnable": backup_task.if_enable,
"status": backup_task.status,
"createDate": backup_task.created_at,
"sourceTaskId": source_task.id,
"remark": backup_task.remark
}
except Exception as e:
logger.error(f"备份任务失败: {str(e)}")
raise
@staticmethod
async def get_subtasks_list(params: SubTaskListParams) -> Dict[str, Any]:
"""
获取子任务列表
Args:
params: 查询参数
Returns:
Dict[str, Any]: 子任务列表数据
"""
try:
async with get_async_session() as session:
# 构建查询条件
query = select(VWEDTaskDef)
# 添加条件
conditions = []
# 排除指定ID
if params.exclude_id:
conditions.append(VWEDTaskDef.id != params.exclude_id)
# 关键词搜索
if params.keyword:
conditions.append(VWEDTaskDef.label.like(f"%{params.keyword}%"))
if conditions:
query = query.where(and_(*conditions))
# 计算总数
count_result = await session.execute(select(
func.count(VWEDTaskDef.id)).where(query.whereclause))
total = count_result.scalar() or 0
# 分页
query = query.order_by(VWEDTaskDef.created_at.desc())
query = query.offset((params.pageNum - 1) * params.pageSize).limit(params.pageSize)
# 执行查询
result = await session.execute(query)
tasks = result.scalars().all()
# 构建返回数据
task_list = []
for task in tasks:
try:
# 解析输入参数
detail = json.loads(task.detail) if task.detail else {}
input_params = detail.get("inputParams", [])
task_list.append({
"id": task.id,
"label": task.label,
"version": task.version,
"templateName": task.template_name,
"remark": task.remark,
"createDate": task.created_at,
"createdBy": task.created_by,
"status": task.status,
"inputParams": input_params
})
except Exception as e:
logger.error(f"解析子任务数据失败: {str(e)}")
return {
"total": total,
"list": task_list
}
except Exception as e:
logger.error(f"获取子任务列表失败: {str(e)}")
raise
@staticmethod
async def run_task(run_request: TaskEditRunRequest, client_ip: str = None, client_info: str = None, tf_api_token: str = None) -> Optional[Dict[str, Any]]:
"""
运行任务
Args:
run_request: 运行任务请求
client_ip: 客户端IP地址
client_info: 客户端设备信息
tf_api_token: 系统任务令牌
Returns:
Optional[Dict[str, Any]]: 运行结果
"""
try:
# 获取任务定义,从数据库加载任务
async with get_async_session() as session:
# 查询任务是否存在
from sqlalchemy import select
from data.models import VWEDTaskDef
result = await session.execute(
select(VWEDTaskDef).where(VWEDTaskDef.id == run_request.taskId)
)
task_def = result.scalars().first()
if not task_def:
logger.error(f"任务不存在: {run_request.taskId}")
return None
# 导入增强版任务调度器
from services.enhanced_scheduler import scheduler
# 转换参数对象为字典格式解决JSON序列化问题
params = []
if run_request.params:
# 使用字典推导式将参数列表转换为字典
params = [param.model_dump() for param in run_request.params]
# 获取当前时间作为任务运行时间
now = datetime.now()
# 设置任务来源信息
source_type = run_request.source_type
source_system = run_request.source_system
source_device = run_request.source_device
map_id = task_def.map_id
source_ip = client_ip # 获取IP地址
source_time = now # 始终使用当前时间
# 记录任务请求信息
logger.info(f"准备启动任务: {run_request.taskId}, 来源: {source_system}, 设备: {source_device}")
logger.debug(f"任务参数: {params}")
# 区分定时任务和普通任务处理
if task_def.periodic_task == PeriodicTaskStatus.PERIODIC:
# 定时任务处理流程
logger.info(f"启动定时任务: {run_request.taskId}")
# 1. 确保定时任务启用
if task_def.if_enable != EnableStatus.ENABLED:
# 如果定时任务未启用,先启用它
async with get_async_session() as session:
task_def.if_enable = EnableStatus.ENABLED
await session.commit()
logger.info(f"已启用定时任务: {run_request.taskId}")
# 2. 通知调度器更新定时任务状态
await scheduler.update_periodic_task(run_request.taskId, True)
# 4. 更新任务定义状态为运行中(1)
async with get_async_session() as session:
from sqlalchemy import update
await session.execute(
update(VWEDTaskDef)
.where(VWEDTaskDef.id == run_request.taskId)
.values(status=TaskStatusEnum.RUNNING, user_token=tf_api_token)
)
await session.commit()
logger.info(f"更新任务定义状态为运行中: {run_request.taskId}")
# 3. 立即执行一次任务
result = await scheduler.run_task(
task_def_id=run_request.taskId,
params=params,
source_type=source_type,
source_system=source_system,
source_device=source_device,
source_ip=source_ip,
source_time=source_time,
source_client_info=client_info,
tf_api_token=tf_api_token,
map_id=map_id
)
if not result.get("success", False):
logger.error(f"启动定时任务失败: {result.get('message')}")
return None
logger.info(f"定时任务启动成功: {run_request.taskId}, 记录ID: {result.get('taskRecordId')}")
# 返回定时任务记录信息
return {
"taskRecordId": result.get("taskRecordId"),
"status": result.get("status"),
"createTime": result.get("createTime"),
"isPeriodic": True,
"period": task_def.period,
"message": "定时任务已启动,将按照设定的周期自动执行"
}
else:
# 普通任务处理流程
logger.info(f"启动普通任务: {run_request.taskId}")
# 4. 更新任务定义状态为运行中(1)
async with get_async_session() as session:
from sqlalchemy import update
await session.execute(
update(VWEDTaskDef)
.where(VWEDTaskDef.id == run_request.taskId)
.values(user_token=tf_api_token)
)
await session.commit()
# 执行一次性任务
result = await scheduler.run_task(
task_def_id=run_request.taskId,
params=params,
source_type=source_type,
source_system=source_system,
source_device=source_device,
source_ip=source_ip,
source_time=source_time,
source_client_info=client_info,
tf_api_token=tf_api_token,
map_id=map_id
)
if not result.get("success", False):
logger.error(f"启动任务失败: {result.get('message')}")
return None
logger.info(f"普通任务启动成功: {run_request.taskId}, 记录ID: {result.get('taskRecordId')}")
# 返回任务记录信息
return {
"taskRecordId": result.get("taskRecordId"),
"status": result.get("status"),
"createTime": result.get("createTime"),
"isPeriodic": False
}
except Exception as e:
logger.log_error_with_trace("运行任务失败", e)
raise
@staticmethod
async def save_input_params(task_id: str, input_params: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
保存任务输入参数配置
Args:
task_id: 任务ID
input_params: 输入参数列表
Returns:
Dict[str, Any]: 保存结果包含是否成功是否有变化的信息
"""
try:
async with get_async_session() as session:
# 查询任务定义
result = await session.execute(
select(VWEDTaskDef).where(VWEDTaskDef.id == task_id)
)
task_def = result.scalars().first()
if not task_def:
logger.error(f"任务不存在: {task_id}")
return {"success": False, "changed": False, "message": "任务不存在"}
# 解析详情JSON
detail = json.loads(task_def.detail) if task_def.detail else {}
# 获取现有输入参数
current_params = detail.get("inputParams", [])
# 比较是否有变化 - 将两个列表转为JSON字符串比较
current_params_str = json.dumps(current_params, sort_keys=True, ensure_ascii=False)
new_params_str = json.dumps(input_params, sort_keys=True, ensure_ascii=False)
has_changes = current_params_str != new_params_str
if not has_changes:
return {"success": True, "changed": False, "message": "数据未发生变化"}
# 更新输入参数
detail["inputParams"] = input_params
# 更新任务定义 - 使用ensure_ascii=False确保中文直接存储而不被编码
task_def.detail = json.dumps(detail, ensure_ascii=False)
task_def.version += 1
# 保存到数据库
await session.commit()
# 获取当前时间作为更新时间
update_time = datetime.now()
return {
"success": True,
"changed": True,
"message": "保存成功",
"version": task_def.version,
"updateTime": update_time
}
except Exception as e:
logger.error(f"保存输入参数失败: {str(e)}")
raise
@staticmethod
async def save_basic_settings(task_id: str, basic_info: TaskBasicInfo) -> Dict[str, Any]:
"""
保存任务基本设置
Args:
task_id: 任务ID
basic_info: 基本信息
Returns:
Dict[str, Any]: 保存结果包含是否成功是否有变化的信息
"""
try:
async with get_async_session() as session:
# 查询任务定义
result = await session.execute(
select(VWEDTaskDef).where(VWEDTaskDef.id == task_id)
)
task_def = result.scalars().first()
if not task_def:
logger.error(f"任务不存在: {task_id}")
return {"success": False, "changed": False, "message": "任务不存在"}
# 检查是否有变化
release_sites_new = 1 if basic_info.releaseSites else 0
has_changes = (
task_def.label != basic_info.label or
task_def.remark != basic_info.remark or
task_def.release_sites != release_sites_new
)
if not has_changes:
return {
"success": True,
"changed": False,
"message": "数据未发生变化",
"id": task_def.id,
"label": task_def.label,
"remark": task_def.remark,
"releaseSites": bool(task_def.release_sites)
}
# 更新基本信息
task_def.label = basic_info.label
task_def.remark = basic_info.remark
task_def.release_sites = release_sites_new
# 保存到数据库
await session.commit()
# 获取当前时间作为更新时间
update_time = datetime.now()
return {
"success": True,
"changed": True,
"message": "保存成功",
"id": task_def.id,
"label": task_def.label,
"remark": task_def.remark,
"releaseSites": bool(task_def.release_sites),
"updateTime": update_time
}
except Exception as e:
logger.error(f"保存基本设置失败: {str(e)}")
raise
@staticmethod
async def get_common_params() -> List[Dict[str, Any]]:
"""
获取常用参数字段
Returns:
List[Dict[str, Any]]: 常用参数字段列表
"""
try:
# 导入内置函数管理模块
from utils.built_in_functions import get_function_list
# 获取所有内置函数
built_in_functions = get_function_list()
# 常用参数字段列表
common_params = [
{
"id": "robotId",
"label": "机器人id",
"type": "String",
"description": "机器人唯一标识",
"order": 1,
"values": [
{"id": "robot_001", "name": "AGV-001", "description": "1号AGV机器人"},
{"id": "robot_002", "name": "AGV-002", "description": "2号AGV机器人"},
{"id": "robot_003", "name": "AGV-003", "description": "3号AGV机器人"}
]
},
{
"id": "robotGroup",
"label": "机器人组",
"type": "Array",
"description": "机器人分组信息",
"order": 2,
"values": [
{"id": "group1", "name": "分拣组", "description": "负责物品分拣的机器人组"},
{"id": "group2", "name": "运输组", "description": "负责物品运输的机器人组"},
{"id": "group3", "name": "装配组", "description": "负责物品装配的机器人组"}
]
},
{
"id": "siteId",
"label": "库位id",
"type": "String",
"description": "库位唯一标识",
"order": 3,
"values": [
{"id": "site_001", "name": "A01", "description": "A区1号库位"},
{"id": "site_002", "name": "A02", "description": "A区2号库位"},
{"id": "site_003", "name": "B01", "description": "B区1号库位"}
]
},
{
"id": "area",
"label": "库区",
"type": "String",
"description": "库区信息",
"order": 4,
"values": [
{"id": "area_A", "name": "A区", "description": "A区储存区"},
{"id": "area_B", "name": "B区", "description": "B区储存区"},
{"id": "area_C", "name": "C区", "description": "C区装配区"}
]
},
{
"id": "binTask",
"label": "binTask",
"type": "String",
"description": "动作类型",
"order": 5,
"values": [
{"id": "QuickLoad", "name": "入库", "description": "物品入库"},
{"id": "QuickUnLoad", "name": "出库", "description": "物品出库"},
]
},
{
"id": "user",
"label": "用户",
"type": "String",
"description": "用户信息",
"order": 6,
"values": [
{"id": "user_001", "name": "张三", "description": "张三用户"},
{"id": "user_002", "name": "李四", "description": "李四用户"},
]
},
{
"id": "cache",
"label": "缓存",
"type": "String",
"description": "缓存信息",
"order": 7,
"values": [
{"id": "cache_001", "name": "cache1", "description": "cache1"},
{"id": "cache_002", "name": "cache2", "description": "cache2"},
]
},
{
"id": "builtInFunction",
"label": "内置函数",
"type": "String",
"description": "系统内置函数",
"order": 8,
"values": [func for func in built_in_functions]
}
]
return common_params
except Exception as e:
logger.error(f"获取常用参数字段失败: {str(e)}")
raise
@staticmethod
async def get_task_input_params(task_id: str) -> Dict[str, Any]:
"""
获取指定任务的输入参数配置
Args:
task_id: 任务ID
Returns:
Dict[str, Any]: 包含输入参数配置和系统参数类型信息
"""
try:
async with get_async_session() as session:
# 查询任务定义
result = await session.execute(
select(VWEDTaskDef).where(VWEDTaskDef.id == task_id)
)
task_def = result.scalars().first()
if not task_def:
logger.error(f"任务不存在: {task_id}")
return {"inputParams": [], "taskInfo": None, "paramTypes": []}
# 解析详情JSON
detail = json.loads(task_def.detail) if task_def.detail else {}
# 获取输入参数
input_params = detail.get("inputParams", [])
# 获取任务基本信息
task_info = {
"id": task_def.id,
"label": task_def.label,
"version": task_def.version,
"remark": task_def.remark,
"templateName": task_def.template_name
}
# 获取可用的参数类型与get_input_param_types相同的逻辑
param_types = [
{
"code": "STRING",
"label": "字符串",
"description": "文本类型数据"
},
{
"code": "BOOLEAN",
"label": "布尔",
"description": "布尔类型值为true或false"
},
{
"code": "INTEGER",
"label": "整数",
"description": "整数类型"
},
{
"code": "FLOAT",
"label": "浮点数",
"description": "浮点数类型"
},
{
"code": "OBJECT",
"label": "JSON对象",
"description": "JSON对象类型{\"key\":\"value\"}"
},
{
"code": "ARRAY",
"label": "JSON数组",
"description": "JSON数组类型如[\"item1\",\"item2\"]"
}
]
# 返回完整信息
return {
"inputParams": input_params,
"taskInfo": task_info,
"paramTypes": param_types
}
except Exception as e:
logger.error(f"获取任务输入参数配置失败: {str(e)}")
raise
@staticmethod
async def get_basic_settings(task_id: str) -> Dict[str, Any]:
"""
获取任务的基本设置信息
Args:
task_id: 任务ID
Returns:
Dict[str, Any]: 任务基本信息
"""
try:
async with get_async_session() as session:
# 查询任务定义
result = await session.execute(
select(VWEDTaskDef).where(VWEDTaskDef.id == task_id)
)
task_def = result.scalars().first()
if not task_def:
logger.error(f"任务不存在: {task_id}")
return {"success": False, "message": "任务不存在"}
# 返回基本信息
return {
"success": True,
"data": {
"id": task_def.id,
"label": task_def.label,
"remark": task_def.remark,
"releaseSites": bool(task_def.release_sites),
"version": task_def.version,
"createDate": task_def.created_at
}
}
except Exception as e:
logger.error(f"获取任务基本设置失败: {str(e)}")
raise
@staticmethod
async def check_task_changes(task_id: str, task_def_data: Dict[str, Any]) -> Dict[str, Any]:
"""
检查任务数据是否有变化
Args:
task_id: 任务ID
task_def_data: 待保存的任务数据
Returns:
Dict[str, Any]: 检查结果包含是否发生变化的标志
"""
try:
if not task_id:
return {"changed": True} # 无法检查,默认为已变化
async with get_async_session() as session:
# 查询现有任务数据
result = await session.execute(
select(VWEDTaskDef).where(VWEDTaskDef.id == task_id)
)
task_def = result.scalars().first()
if not task_def:
return {"changed": True} # 任务不存在,视为有变化
# 检查label是否有变化
label = task_def_data.get("label")
if label and label.strip() and label.strip() != task_def.label:
return {"changed": True, "reason": "label变化"}
# 检查detail是否有变化
detail = task_def_data.get("detail")
if not detail:
# 没有提供detail可能只更新了label
return {"changed": label and label.strip() and label.strip() != task_def.label}
# 比较detail
current_detail = json.loads(task_def.detail) if task_def.detail else {}
# 将提供的detail转为JSON字符串排序键以确保一致的比较结果
current_detail_str = json.dumps(current_detail, sort_keys=True, ensure_ascii=False)
new_detail_str = json.dumps(detail, sort_keys=True, ensure_ascii=False)
if current_detail_str != new_detail_str:
return {"changed": True, "reason": "detail变化"}
# 数据未变化
return {
"changed": False,
"id": task_def.id,
"version": task_def.version
}
except Exception as e:
logger.error(f"检查任务数据变化失败: {str(e)}")
# 出错时默认为有变化,以确保数据安全
return {"changed": True, "error": str(e)}
@staticmethod
async def stop_task_def(task_def_id: str) -> Dict[str, Any]:
"""
停止指定任务定义下的所有运行任务实例同时禁用定时任务
Args:
task_def_id: 任务定义ID
Returns:
字典包含停止结果信息
- success: 是否操作成功
- message: 操作结果消息
- is_periodic: 是否为定时任务
- total_running: 运行中的任务总数
- stopped_count: 成功停止的任务数量
- failed_count: 停止失败的任务数量
- failed_tasks: 停止失败的任务记录ID列表
"""
# 导入增强版调度器
from services.enhanced_scheduler import scheduler
try:
async with get_async_session() as session:
# 查询任务定义
result = await session.execute(
select(VWEDTaskDef).where(VWEDTaskDef.id == task_def_id)
)
task_def = result.scalars().first()
if not task_def:
return {
"success": False,
"message": f"未找到任务定义: {task_def_id}"
}
is_periodic = task_def.periodic_task == PeriodicTaskStatus.PERIODIC
# 初始化计数器
total_running = 0
stopped_count = 0
failed_count = 0
failed_tasks = []
# 如果是定时任务则禁用
if is_periodic and task_def.if_enable == EnableStatus.ENABLED:
# 更新任务定义状态为禁用
await session.execute(
update(VWEDTaskDef)
.where(VWEDTaskDef.id == task_def_id)
.values(if_enable=EnableStatus.DISABLED)
)
await session.commit()
# 通知调度器
update_result = await scheduler.update_periodic_task(task_def_id, enable=False)
# 将定时任务的禁用也计入停止任务的数量
total_running += 1
if update_result.get("success", True): # 假设通知调度器成功,除非明确返回失败
stopped_count += 1
else:
failed_count += 1
failed_tasks.append({
"taskRecordId": "periodic_" + task_def_id,
"reason": update_result.get("message", "禁用定时任务失败")
})
# 查找所有正在运行的任务记录
running_tasks_query = await session.execute(
select(VWEDTaskRecord)
.where(
VWEDTaskRecord.def_id == task_def_id,
VWEDTaskRecord.status == TaskStatus.RUNNING # 执行中状态码
)
)
running_tasks = running_tasks_query.scalars().all()
# 更新总计数
total_running += len(running_tasks)
# 取消所有运行中的任务
for task_record in running_tasks:
cancel_result = await scheduler.cancel_task(task_record.id)
if cancel_result.get("success", False):
stopped_count += 1
else:
failed_count += 1
failed_tasks.append({
"taskRecordId": task_record.id,
"reason": cancel_result.get("message", "未知原因")
})
# 更新任务定义状态为已结束(0)
await session.execute(
update(VWEDTaskDef)
.where(VWEDTaskDef.id == task_def_id)
.values(status=TaskStatusEnum.PENDING)
)
await session.commit()
return {
"success": True,
"message": "操作完成",
"is_periodic": is_periodic,
"total_running": total_running,
"stopped_count": stopped_count,
"failed_count": failed_count,
"failed_tasks": failed_tasks
}
except Exception as e:
return {
"success": False,
"message": f"停止任务失败: {str(e)}"
}
@staticmethod
async def check_running_task_for_device(task_id: str, device_id: str) -> Dict[str, Any]:
"""
检查指定设备是否有正在运行的相同任务
Args:
task_id: 任务定义ID
device_id: 设备ID
Returns:
Dict[str, Any]: 检查结果包含是否允许启动任务的信息
{
"has_running_task": bool, # 是否有运行中的任务
"allow_restart": bool, # 是否允许重启
"message": str # 提示消息
}
"""
try:
from data.enum.task_record_enum import TaskStatus
async with get_async_session() as session:
# 查询是否存在相同任务ID且相同设备且正在运行的任务
result = await session.execute(
select(VWEDTaskRecord).where(
VWEDTaskRecord.def_id == task_id,
VWEDTaskRecord.source_device == device_id,
VWEDTaskRecord.status == TaskStatus.RUNNING # 状态为"进行中"
)
)
running_tasks = result.scalars().all()
# 如果没有运行中的任务,直接允许启动
if not running_tasks:
return {
"has_running_task": False,
"allow_restart": True,
"message": "没有运行中的任务,可以启动"
}
# 如果存在运行中的任务检查allow_restart_same_location字段
allow_restart = True
for task in running_tasks:
# 如果任何一个任务不允许重启,则整体不允许
if not task.allow_restart_same_location:
allow_restart = False
break
return {
"has_running_task": True,
"allow_restart": allow_restart,
"message": "相同设备已有此任务正在运行中" + (",但允许再次启动" if allow_restart else ",请等待任务完成后再次启动")
}
except Exception as e:
logger.error(f"检查运行中任务失败: {str(e)}")
raise