#!/usr/bin/env python # -*- coding: utf-8 -*- """ 服务调用节点处理器 处理外部 HTTP 服务调用 """ import json import logging import re from typing import Dict, Optional, TYPE_CHECKING import httpx from online_dev.workflow.engine.handlers.base_handler import BaseNodeHandler if TYPE_CHECKING: from online_dev.workflow.engine.base import ExecutionContext logger = logging.getLogger(__name__) class ServiceHandler(BaseNodeHandler): """ 服务调用节点处理器 支持: - HTTP 方法: GET/POST/PUT/DELETE/PATCH - 请求头配置 - 参数变量替换 - 重试机制 - 失败处理策略 """ async def execute(self, context: 'ExecutionContext', node: Dict) -> None: """ 执行服务调用节点 """ node_id = self.get_node_id(node) node_name = self.get_node_name(node) node_config = self.get_node_config(node) url = node_config.get('url', '') method = node_config.get('method', 'POST') headers = {h['key']: h['value'] for h in node_config.get('headers', []) if h.get('key')} params = node_config.get('params', '') body = node_config.get('body', '') timeout = node_config.get('timeout', 30) retry_count = node_config.get('retryCount', 0) fail_action = node_config.get('failAction', 'stop') result_variable = node_config.get('resultVariable', '') logger.info(f"服务调用节点 - {method} {url}") # 更新实例当前节点 await self.update_instance_node(context, node) success = False response_data = None error_message = '' # 尝试调用服务 async with httpx.AsyncClient() as client: for attempt in range(retry_count + 1): try: # 解析参数和请求体(支持变量替换) parsed_params = self._parse_params(params, context.form_data) parsed_body = self._parse_params(body, context.form_data) response = await client.request( method=method, url=url, headers=headers, params=parsed_params if method == 'GET' else None, json=parsed_body if method != 'GET' and parsed_body else None, timeout=timeout, ) response.raise_for_status() response_data = response.json() if response.text else {} success = True break except Exception as e: error_message = str(e) logger.warning(f"服务调用失败 (尝试 {attempt + 1}/{retry_count + 1}): {e}") # 记录日志 await self.create_log( context, node, 'service_call', comment=f"{'成功' if success else '失败'}: {method} {url}", extra_data={ 'url': url, 'method': method, 'success': success, 'response': response_data, 'error': error_message, }, ) if success: # 存储结果到流程变量 if result_variable and response_data: context.form_data[result_variable] = response_data await self.advance_to_next(context, node) else: # 处理失败 if fail_action == 'continue': logger.warning(f"服务调用失败,继续流程") await self.advance_to_next(context, node) elif fail_action == 'stop': logger.error(f"服务调用失败,终止流程") await self.engine._end_instance(context, 'rejected') def _parse_params(self, params_str: str, form_data: Dict) -> Optional[Dict]: """ 解析服务调用参数,支持变量替换 变量格式: ${field_name} """ if not params_str: return None # 替换变量 def replace_var(match): var_name = match.group(1) return str(form_data.get(var_name, '')) replaced = re.sub(r'\$\{(\w+)\}', replace_var, params_str) try: return json.loads(replaced) except json.JSONDecodeError: return None