255 lines
8.7 KiB
Python
255 lines
8.7 KiB
Python
|
#!/usr/bin/env python
|
|||
|
# -*- coding: utf-8 -*-
|
|||
|
|
|||
|
"""
|
|||
|
VWED其他模块
|
|||
|
提供各种杂项工具函数
|
|||
|
"""
|
|||
|
|
|||
|
import uuid
|
|||
|
import hashlib
|
|||
|
import base64
|
|||
|
import json
|
|||
|
import smtplib
|
|||
|
import math
|
|||
|
from typing import List, Dict, Any, Optional
|
|||
|
from email.mime.text import MIMEText
|
|||
|
from email.mime.multipart import MIMEMultipart
|
|||
|
import xml.etree.ElementTree as ET
|
|||
|
from utils.json_parser import safe_parse_dict, safe_parse_list
|
|||
|
|
|||
|
from utils.logger import get_logger
|
|||
|
|
|||
|
logger = get_logger("services.online_script.other_module")
|
|||
|
|
|||
|
|
|||
|
class VWEDOtherModule:
|
|||
|
"""VWED其他模块 - 提供各种杂项工具函数"""
|
|||
|
|
|||
|
def __init__(self, script_id: str):
|
|||
|
self.script_id = script_id
|
|||
|
|
|||
|
def create_uuid(self) -> str:
|
|||
|
"""创建唯一的 ID 编号"""
|
|||
|
try:
|
|||
|
return str(uuid.uuid4())
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"[{self.script_id}] 创建UUID失败: {e}")
|
|||
|
raise
|
|||
|
|
|||
|
def send_mail(self, param: str) -> None:
|
|||
|
"""向目标收件方发送简单邮件
|
|||
|
|
|||
|
Args:
|
|||
|
param: 邮件详情JSON字符串,包含to, subject, text等字段
|
|||
|
"""
|
|||
|
try:
|
|||
|
mail_config = json.loads(param)
|
|||
|
|
|||
|
# 验证必填字段
|
|||
|
required_fields = ['to', 'subject', 'text']
|
|||
|
for field in required_fields:
|
|||
|
if field not in mail_config:
|
|||
|
raise ValueError(f"缺少必填字段: {field}")
|
|||
|
|
|||
|
# TODO: 实际的邮件发送逻辑需要根据系统配置实现
|
|||
|
# 这里仅记录日志,实际使用时需要配置SMTP服务器
|
|||
|
logger.info(f"[{self.script_id}] 发送邮件: {mail_config['subject']} -> {mail_config['to']}")
|
|||
|
|
|||
|
# 示例实现(需要配置SMTP服务器信息)
|
|||
|
"""
|
|||
|
msg = MIMEMultipart()
|
|||
|
msg['From'] = smtp_config['from']
|
|||
|
msg['To'] = mail_config['to']
|
|||
|
msg['Subject'] = mail_config['subject']
|
|||
|
|
|||
|
if 'cc' in mail_config:
|
|||
|
msg['Cc'] = mail_config['cc']
|
|||
|
|
|||
|
msg.attach(MIMEText(mail_config['text'], 'plain', 'utf-8'))
|
|||
|
|
|||
|
server = smtplib.SMTP(smtp_config['host'], smtp_config['port'])
|
|||
|
server.starttls()
|
|||
|
server.login(smtp_config['username'], smtp_config['password'])
|
|||
|
server.send_message(msg)
|
|||
|
server.quit()
|
|||
|
"""
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"[{self.script_id}] 发送邮件失败: {e}")
|
|||
|
raise
|
|||
|
|
|||
|
def send_user_message(self, level: str, message_title: str, message_body: str) -> None:
|
|||
|
"""发送用户消息
|
|||
|
|
|||
|
Args:
|
|||
|
level: 消息等级 ERROR, WARN, INFO
|
|||
|
message_title: 消息标题
|
|||
|
message_body: 消息内容
|
|||
|
"""
|
|||
|
try:
|
|||
|
if level not in ['ERROR', 'WARN', 'INFO']:
|
|||
|
level = 'INFO'
|
|||
|
|
|||
|
# TODO: 实际的用户消息发送逻辑需要与RDS消息管理系统集成
|
|||
|
logger.info(f"[{self.script_id}] 用户消息 [{level}] {message_title}: {message_body}")
|
|||
|
|
|||
|
# 这里应该调用实际的消息推送API
|
|||
|
# 可能需要WebSocket推送或者写入消息队列
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"[{self.script_id}] 发送用户消息失败: {e}")
|
|||
|
# 该方法不抛出异常
|
|||
|
|
|||
|
def json_to_xml(self, param: str) -> str:
|
|||
|
"""将JSON字符串转化为XML字符串
|
|||
|
|
|||
|
Args:
|
|||
|
param: JSON字符串
|
|||
|
|
|||
|
Returns:
|
|||
|
XML字符串
|
|||
|
"""
|
|||
|
try:
|
|||
|
# data = json.loads(param)
|
|||
|
data = safe_parse_dict(param, self.script_id)
|
|||
|
|
|||
|
def dict_to_xml(tag, d):
|
|||
|
"""递归将字典转换为XML元素"""
|
|||
|
elem = ET.Element(tag)
|
|||
|
for key, val in d.items():
|
|||
|
if isinstance(val, dict):
|
|||
|
elem.append(dict_to_xml(key, val))
|
|||
|
elif isinstance(val, list):
|
|||
|
for item in val:
|
|||
|
if isinstance(item, dict):
|
|||
|
elem.append(dict_to_xml(key, item))
|
|||
|
else:
|
|||
|
child = ET.SubElement(elem, key)
|
|||
|
child.text = str(item)
|
|||
|
else:
|
|||
|
child = ET.SubElement(elem, key)
|
|||
|
child.text = str(val)
|
|||
|
return elem
|
|||
|
|
|||
|
root = dict_to_xml('root', data)
|
|||
|
|
|||
|
# 格式化XML输出 - 使用简单的格式化方式避免minidom兼容性问题
|
|||
|
ET.indent(root, space=" ", level=0)
|
|||
|
return ET.tostring(root, encoding='unicode')
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"[{self.script_id}] JSON转XML失败: {e}")
|
|||
|
return ""
|
|||
|
|
|||
|
def md5_encode(self, param: str) -> str:
|
|||
|
"""将字符串进行MD5加密
|
|||
|
|
|||
|
Args:
|
|||
|
param: 要加密的字符串
|
|||
|
|
|||
|
Returns:
|
|||
|
32位小写MD5字符串
|
|||
|
"""
|
|||
|
try:
|
|||
|
md5_hash = hashlib.md5()
|
|||
|
md5_hash.update(param.encode('utf-8'))
|
|||
|
return md5_hash.hexdigest()
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"[{self.script_id}] MD5加密失败: {e}")
|
|||
|
raise
|
|||
|
|
|||
|
def base64_encode(self, param: str) -> str:
|
|||
|
"""将字符串进行Base64重编码
|
|||
|
|
|||
|
Args:
|
|||
|
param: 要编码的字符串
|
|||
|
|
|||
|
Returns:
|
|||
|
Base64编码后的字符串
|
|||
|
"""
|
|||
|
try:
|
|||
|
encoded_bytes = base64.b64encode(param.encode('utf-8'))
|
|||
|
return encoded_bytes.decode('utf-8')
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"[{self.script_id}] Base64编码失败: {e}")
|
|||
|
raise
|
|||
|
|
|||
|
def trans_to_page_obj(self, total_count: int, current_page: int, page_size: int,
|
|||
|
total_page: int, page_list: str) -> str:
|
|||
|
"""转换成分页对象
|
|||
|
|
|||
|
Args:
|
|||
|
total_count: 分页数据的总条数
|
|||
|
current_page: 当前是第几页
|
|||
|
page_size: 每页显示的数量
|
|||
|
total_page: 总页数
|
|||
|
page_list: 当前页的数据
|
|||
|
|
|||
|
Returns:
|
|||
|
转换后的分页对象字符串
|
|||
|
"""
|
|||
|
try:
|
|||
|
page_obj = {
|
|||
|
"total_count": total_count,
|
|||
|
"current_page": current_page,
|
|||
|
"page_size": page_size,
|
|||
|
"total_page": total_page,
|
|||
|
"page_list": json.loads(page_list) if isinstance(page_list, str) else page_list
|
|||
|
}
|
|||
|
return json.dumps(page_obj, ensure_ascii=False)
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"[{self.script_id}] 转换分页对象失败: {e}")
|
|||
|
return "{}"
|
|||
|
|
|||
|
async def get_script_value(self, key: str) -> str:
|
|||
|
"""获取天风任务参数
|
|||
|
|
|||
|
Args:
|
|||
|
key: 变量表达式,如 task.variables.status, blocks.b4.site_id
|
|||
|
|
|||
|
Returns:
|
|||
|
参数值的字符串,不存在时返回 "NULL"
|
|||
|
"""
|
|||
|
try:
|
|||
|
# TODO: 这里需要与任务执行上下文集成,获取实际的任务变量
|
|||
|
# 应该从task_context中获取对应的值
|
|||
|
from services.execution.task_context import get_current_context
|
|||
|
|
|||
|
context = get_current_context()
|
|||
|
if context:
|
|||
|
value = context.get_variable(key)
|
|||
|
return str(value) if value is not None else "NULL"
|
|||
|
|
|||
|
logger.warning(f"[{self.script_id}] 无法获取任务参数,没有活跃的任务上下文")
|
|||
|
return "NULL"
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"[{self.script_id}] 获取脚本值失败: {e}")
|
|||
|
return "NULL"
|
|||
|
|
|||
|
def release_wait_pass(self, agv_ids: List[str]) -> bool:
|
|||
|
"""批量放行机器人
|
|||
|
|
|||
|
Args:
|
|||
|
agv_ids: 需放行的机器人ID列表
|
|||
|
|
|||
|
Returns:
|
|||
|
True: 放行成功, False: 放行失败
|
|||
|
"""
|
|||
|
try:
|
|||
|
if not agv_ids:
|
|||
|
return True
|
|||
|
|
|||
|
# TODO: 这里需要与机器人调度系统集成,实现实际的放行逻辑
|
|||
|
logger.info(f"[{self.script_id}] 批量放行机器人: {agv_ids}")
|
|||
|
|
|||
|
# 应该调用机器人调度服务的放行接口
|
|||
|
# from services.robot_scheduling import release_robots
|
|||
|
# return release_robots(agv_ids)
|
|||
|
|
|||
|
return True
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"[{self.script_id}] 批量放行失败: {e}")
|
|||
|
return False
|