Files
2026-06-09 21:18:33 +08:00

259 lines
9.4 KiB
Python

"""
应用创建节点
创建一个新的应用(子应用)
"""
import logging
from typing import Any, Dict, Optional
from ..base import BaseNode, NodeContext, NodeResult
from ..registry import NodeRegistry
logger = logging.getLogger(__name__)
@NodeRegistry.register
class AppCreateNode(BaseNode):
"""
应用创建节点
创建一个新的应用(低代码平台的顶层容器)
"""
node_type = 'app_create'
node_name = '创建应用'
node_category = 'application'
node_icon = 'app-window'
node_description = '创建一个新的应用(子应用)'
inputs = [
{
'name': 'name',
'type': 'string',
'description': '应用名称',
'required': True,
},
{
'name': 'code',
'type': 'string',
'description': '应用编码(唯一标识,用于URL路由,只能包含字母、数字、下划线和连字符,必须以字母开头)',
'required': True,
},
{
'name': 'description',
'type': 'string',
'description': '应用描述',
'required': False,
},
{
'name': 'icon',
'type': 'string',
'description': '应用图标',
'required': False,
},
{
'name': 'app_type',
'type': 'string',
'description': '应用类型: form-表单应用, workflow-流程应用, dashboard-数据应用, screen-大屏应用, mixed-混合应用',
'required': False,
},
]
outputs = [
{
'name': 'app_id',
'type': 'string',
'description': '创建的应用ID',
},
{
'name': 'app_code',
'type': 'string',
'description': '应用编码',
},
{
'name': 'app_name',
'type': 'string',
'description': '应用名称',
},
{
'name': 'success',
'type': 'boolean',
'description': '是否创建成功',
},
]
def execute(self, context: NodeContext) -> NodeResult:
"""执行节点(同步方法,通过运行异步方法实现)"""
import asyncio
loop = asyncio.get_event_loop()
if loop.is_running():
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(asyncio.run, self.execute_async(context))
return future.result()
else:
return loop.run_until_complete(self.execute_async(context))
async def execute_async(self, context: NodeContext) -> NodeResult:
"""异步执行节点"""
try:
# 获取输入参数并解析变量引用
name = context.resolve_template(self.config.get('name', ''))
code_base = context.resolve_template(self.config.get('code', ''))
description = context.resolve_template(self.config.get('description', ''))
icon = context.resolve_template(self.config.get('icon', ''))
app_type = context.resolve_template(self.config.get('app_type', 'mixed'))
# 配置选项
update_if_exists = self.config.get('update_if_exists', False)
logger.info(f'AppCreateNode - 开始创建应用: name={name}, code={code_base}')
# 验证必要参数
if not name:
return NodeResult(
success=False,
error='应用名称不能为空',
)
if not code_base:
return NodeResult(
success=False,
error='应用编码不能为空',
)
# 验证编码格式(字母开头,只能包含字母、数字、下划线和连字符)
import re
if not re.match(r'^[a-zA-Z][a-zA-Z0-9_-]*$', code_base):
return NodeResult(
success=False,
error='应用编码格式不正确,必须以字母开头,只能包含字母、数字、下划线和连字符',
)
# 导入服务和数据库会话
from app.database import AsyncSessionLocal
from core.application.service import ApplicationService
from core.application.schema import ApplicationCreate, ApplicationUpdate
async with AsyncSessionLocal() as db:
# 生成唯一的 code(如果已存在则自动添加后缀)
code = await self._get_unique_code(db, code_base, ApplicationService)
logger.info(f'AppCreateNode - 使用 code: {code} (原始: {code_base})')
# 检查应用编码是否已存在(理论上不应该存在,因为已经生成了唯一 code)
existing_app = await ApplicationService.get_by_code(db, code)
if existing_app:
if update_if_exists:
# 更新已存在的应用
update_data = ApplicationUpdate(
name=name,
description=description or None,
icon=icon or None,
app_type=app_type or None,
)
updated_app = await ApplicationService.update(
db,
record_id=existing_app.id,
data=update_data,
auto_commit=True
)
if updated_app:
logger.info(f'AppCreateNode - 应用已存在,更新成功: id={updated_app.id}')
return NodeResult(
success=True,
output=f'应用 {name} 更新成功',
output_variables={
'app_id': updated_app.id,
'app_code': updated_app.code,
'app_name': updated_app.name,
'success': True,
}
)
else:
return NodeResult(
success=False,
error=f'更新应用失败',
)
else:
# 应用已存在,返回已存在的应用信息
logger.info(f'AppCreateNode - 应用编码已存在: code={code}, id={existing_app.id}')
return NodeResult(
success=True,
output=f'应用编码 {code} 已存在',
output_variables={
'app_id': existing_app.id,
'app_code': existing_app.code,
'app_name': existing_app.name,
'success': False, # 标记为未创建新应用
}
)
# 创建新应用
create_data = ApplicationCreate(
name=name,
code=code,
description=description or '',
icon=icon or '',
app_type=app_type or 'mixed',
)
new_app = await ApplicationService.create(db, data=create_data, auto_commit=True)
if new_app:
logger.info(f'AppCreateNode - 应用创建成功: id={new_app.id}, code={new_app.code}')
return NodeResult(
success=True,
output=f'应用 {name} 创建成功',
output_variables={
'app_id': new_app.id,
'app_code': new_app.code,
'app_name': new_app.name,
'success': True,
}
)
else:
return NodeResult(
success=False,
error='创建应用失败',
)
except Exception as e:
logger.exception(f'AppCreateNode - 执行异常: {e}')
return NodeResult(
success=False,
error=f'创建应用异常: {str(e)}',
)
async def _get_unique_code(self, db, base_code: str, service) -> str:
"""
生成唯一的 code,如果已存在则自动添加数字后缀
Args:
db: 数据库会话
base_code: 基础 code
service: 服务类(需要有 get_by_code 方法)
Returns:
唯一的 code
"""
code = base_code
counter = 1
while True:
try:
existing = await service.get_by_code(db, code)
if not existing:
# code 不存在,可以使用
break
# code 已存在,添加后缀
code = f"{base_code}_{counter}"
counter += 1
except Exception:
# 查询出错,认为 code 可用
break
return code