refactor: workflow_service改名app

This commit is contained in:
iHeyTang
2025-08-22 15:15:54 +08:00
parent 6ace2ed3c0
commit cf76f4af0d
25 changed files with 36 additions and 37 deletions

48
app/database/__init__.py Normal file
View File

@@ -0,0 +1,48 @@
"""
数据库模块
提供工作流和工作流运行的数据库操作接口
"""
from .models import Workflow, WorkflowRun, WorkflowRunNode, ComfyUIServer
from .api import (
init_db,
save_workflow,
get_all_workflows,
get_latest_workflow_by_base_name,
get_workflow_by_version,
get_workflow,
delete_workflow,
create_workflow_run,
update_workflow_run_status,
create_workflow_run_nodes,
update_workflow_run_node_status,
get_workflow_run,
get_workflow_run_nodes,
get_pending_workflow_runs,
get_running_workflow_runs,
)
__all__ = [
# 模型
"Workflow",
"WorkflowRun",
"WorkflowRunNode",
"ComfyUIServer",
# API函数
"init_db",
"save_workflow",
"get_all_workflows",
"get_latest_workflow_by_base_name",
"get_workflow_by_version",
"get_workflow",
"delete_workflow",
"create_workflow_run",
"update_workflow_run_status",
"create_workflow_run_nodes",
"update_workflow_run_node_status",
"get_workflow_run",
"get_workflow_run_nodes",
"get_pending_workflow_runs",
"get_running_workflow_runs",
]

282
app/database/api.py Normal file
View File

@@ -0,0 +1,282 @@
"""
数据库操作API
提供工作流相关的所有数据库操作函数
"""
from datetime import datetime
from typing import Optional, List
import json
import uuid
from sqlalchemy.future import select
from sqlalchemy import delete
from .models import Base, Workflow, WorkflowRun, WorkflowRunNode, ComfyUIServer
from .connection import async_engine, AsyncSessionLocal
async def init_db():
"""初始化数据库表结构"""
async with async_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
print(f"数据库表结构已创建完成。")
async def save_workflow(name: str, workflow_json: str):
"""保存工作流"""
version = datetime.now().strftime("%Y%m%d%H%M%S")
workflow = Workflow(
name=f"{name} [{version}]",
base_name=name,
version=version,
workflow_json=workflow_json,
)
async with AsyncSessionLocal() as session:
await session.merge(workflow) # 使用merge实现INSERT OR REPLACE
await session.commit()
async def get_all_workflows() -> List[dict]:
"""获取所有工作流(最新版本)"""
async with AsyncSessionLocal() as session:
# 使用SQLAlchemy ORM语法通过子查询获取每个base_name的最新版本
from sqlalchemy import func
# 子查询获取每个base_name的最新版本
latest_versions = (
select(Workflow.base_name, func.max(Workflow.version).label("max_version"))
.group_by(Workflow.base_name)
.subquery()
)
# 主查询:关联获取完整的工作流信息
stmt = (
select(Workflow)
.join(
latest_versions,
(Workflow.base_name == latest_versions.c.base_name)
& (Workflow.version == latest_versions.c.max_version),
)
.order_by(Workflow.base_name)
)
result = await session.execute(stmt)
workflows = result.scalars().all()
return [
{"name": wf.name, "workflow": json.loads(wf.workflow_json)}
for wf in workflows
]
async def get_latest_workflow_by_base_name(base_name: str) -> Optional[dict]:
"""根据基础名称获取最新版本的工作流"""
async with AsyncSessionLocal() as session:
stmt = (
select(Workflow)
.where(Workflow.base_name == base_name)
.order_by(Workflow.version.desc())
.limit(1)
)
result = await session.execute(stmt)
workflow = result.scalar_one_or_none()
return workflow.to_dict() if workflow else None
async def get_workflow_by_version(base_name: str, version: str) -> Optional[dict]:
"""根据版本获取工作流"""
name = f"{base_name} [{version}]"
async with AsyncSessionLocal() as session:
stmt = select(Workflow).where(Workflow.name == name)
result = await session.execute(stmt)
workflow = result.scalar_one_or_none()
return workflow.to_dict() if workflow else None
async def get_workflow(name: str, version: Optional[str] = None) -> Optional[dict]:
"""获取工作流"""
if version:
return await get_workflow_by_version(name, version)
else:
return await get_latest_workflow_by_base_name(name)
async def delete_workflow(name: str) -> bool:
"""删除工作流"""
async with AsyncSessionLocal() as session:
stmt = delete(Workflow).where(Workflow.name == name)
result = await session.execute(stmt)
await session.commit()
return result.rowcount > 0
async def create_workflow_run(
workflow_run_id: str,
workflow_name: str,
workflow_json: str,
api_spec: str,
request_data: str,
) -> str:
"""创建新的工作流运行记录"""
workflow_run = WorkflowRun(
id=workflow_run_id,
workflow_name=workflow_name,
workflow_json=workflow_json,
api_spec=api_spec,
request_data=request_data,
)
async with AsyncSessionLocal() as session:
session.add(workflow_run)
await session.commit()
return workflow_run_id
async def update_workflow_run_status(
workflow_run_id: str,
status: str,
server_url: str = None,
prompt_id: str = None,
client_id: str = None,
error_message: str = None,
result: str = None,
):
"""更新工作流运行状态"""
async with AsyncSessionLocal() as session:
workflow_run = await session.get(WorkflowRun, workflow_run_id)
if not workflow_run:
return
workflow_run.status = status
if status == "running":
workflow_run.server_url = server_url
workflow_run.prompt_id = prompt_id
workflow_run.client_id = client_id
workflow_run.started_at = datetime.utcnow()
elif status == "completed":
workflow_run.completed_at = datetime.utcnow()
workflow_run.result = result
elif status == "failed":
workflow_run.error_message = error_message
workflow_run.completed_at = datetime.utcnow()
await session.commit()
async def create_workflow_run_nodes(workflow_run_id: str, nodes_data: List[dict]):
"""创建工作流运行节点记录"""
nodes = []
for node in nodes_data:
node_obj = WorkflowRunNode(
id=str(uuid.uuid4()),
workflow_run_id=workflow_run_id,
node_id=node["id"],
node_type=node["type"],
)
nodes.append(node_obj)
async with AsyncSessionLocal() as session:
session.add_all(nodes)
await session.commit()
async def update_workflow_run_node_status(
workflow_run_id: str,
node_id: str,
status: str,
output_data: str = None,
error_message: str = None,
):
"""更新工作流运行节点状态"""
async with AsyncSessionLocal() as session:
stmt = select(WorkflowRunNode).where(
WorkflowRunNode.workflow_run_id == workflow_run_id,
WorkflowRunNode.node_id == node_id,
)
result = await session.execute(stmt)
node = result.scalar_one_or_none()
if not node:
return
node.status = status
if status == "running":
node.started_at = datetime.utcnow()
elif status == "completed":
node.output_data = output_data
node.completed_at = datetime.utcnow()
elif status == "failed":
node.error_message = error_message
node.completed_at = datetime.utcnow()
await session.commit()
async def get_workflow_run(workflow_run_id: str) -> Optional[WorkflowRun]:
"""获取工作流运行记录"""
async with AsyncSessionLocal() as session:
workflow_run = await session.get(WorkflowRun, workflow_run_id)
return workflow_run
async def get_workflow_run_nodes(workflow_run_id: str) -> List[dict]:
"""获取工作流运行节点记录"""
async with AsyncSessionLocal() as session:
stmt = select(WorkflowRunNode).where(
WorkflowRunNode.workflow_run_id == workflow_run_id
)
result = await session.execute(stmt)
nodes = result.scalars().all()
return [node.to_dict() for node in nodes]
async def get_pending_workflow_runs() -> List[dict]:
"""获取所有待处理的工作流运行记录"""
async with AsyncSessionLocal() as session:
stmt = (
select(WorkflowRun)
.where(WorkflowRun.status == "pending")
.order_by(WorkflowRun.created_at.asc())
)
result = await session.execute(stmt)
runs = result.scalars().all()
return [run.to_dict() for run in runs]
async def get_running_workflow_runs() -> List[dict]:
"""获取所有正在运行的工作流运行记录"""
async with AsyncSessionLocal() as session:
stmt = select(WorkflowRun).where(WorkflowRun.status == "running")
result = await session.execute(stmt)
runs = result.scalars().all()
return [run.to_dict() for run in runs]
async def get_workflow_runs_recent(
start_time: datetime, end_time: datetime
) -> List[dict]:
"""获取指定时间范围内的最近工作流运行记录"""
async with AsyncSessionLocal() as session:
stmt = (
select(WorkflowRun)
.where(
WorkflowRun.created_at >= start_time, WorkflowRun.created_at <= end_time
)
.order_by(WorkflowRun.created_at.desc())
)
result = await session.execute(stmt)
runs = result.scalars().all()
return [run.to_dict() for run in runs]

View File

@@ -0,0 +1,42 @@
"""
数据库连接配置
管理数据库引擎、会话和连接池
"""
from sqlalchemy import create_engine
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy.orm import sessionmaker
from app.config import Settings
# 数据库配置
DATABASE_FILE = Settings().DB_FILE
ASYNC_DATABASE_URL = f"sqlite+aiosqlite:///{DATABASE_FILE}"
SYNC_DATABASE_URL = f"sqlite:///{DATABASE_FILE}"
# 创建异步引擎
async_engine = create_async_engine(
ASYNC_DATABASE_URL, echo=False, pool_pre_ping=True, pool_recycle=3600
)
# 创建异步会话工厂
AsyncSessionLocal = async_sessionmaker(
async_engine, class_=AsyncSession, expire_on_commit=False
)
# 创建同步引擎和会话(用于初始化)
sync_engine = create_engine(
SYNC_DATABASE_URL, echo=False, pool_pre_ping=True, pool_recycle=3600
)
SessionLocal = sessionmaker(bind=sync_engine)
async def get_db_session() -> AsyncSession:
"""获取数据库会话"""
async with AsyncSessionLocal() as session:
return session
def get_sync_session():
"""获取同步数据库会话(用于初始化等场景)"""
return SessionLocal()

144
app/database/models.py Normal file
View File

@@ -0,0 +1,144 @@
"""
数据库模型定义
定义工作流相关的所有数据表结构
"""
from datetime import datetime
from sqlalchemy import Column, String, Text, DateTime, ForeignKey, Integer, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
# 创建基类
Base = declarative_base()
class Workflow(Base):
"""工作流模型"""
__tablename__ = "workflows"
name = Column(String, primary_key=True)
base_name = Column(String, nullable=False)
version = Column(String, nullable=False)
workflow_json = Column(Text, nullable=False)
def to_dict(self) -> dict:
return {
"name": self.name,
"base_name": self.base_name,
"version": self.version,
"workflow_json": self.workflow_json,
}
class WorkflowRun(Base):
"""工作流运行记录模型"""
__tablename__ = "workflow_run"
id = Column(String, primary_key=True)
workflow_name = Column(String, nullable=False)
prompt_id = Column(String)
client_id = Column(String)
status = Column(String, nullable=False, default="pending")
server_url = Column(String)
created_at = Column(DateTime, default=datetime.utcnow)
started_at = Column(DateTime)
completed_at = Column(DateTime)
error_message = Column(Text)
workflow_json = Column(Text, nullable=False)
api_spec = Column(Text, nullable=False)
request_data = Column(Text, nullable=False)
result = Column(Text)
# 关联关系
nodes = relationship(
"WorkflowRunNode", back_populates="workflow_run", cascade="all, delete-orphan"
)
def to_dict(self) -> dict:
return {
"id": self.id,
"workflow_name": self.workflow_name,
"prompt_id": self.prompt_id,
"client_id": self.client_id,
"status": self.status,
"server_url": self.server_url,
"created_at": self.created_at.isoformat() if self.created_at else None,
"started_at": self.started_at.isoformat() if self.started_at else None,
"completed_at": (
self.completed_at.isoformat() if self.completed_at else None
),
"error_message": self.error_message,
"workflow_json": self.workflow_json,
"api_spec": self.api_spec,
"request_data": self.request_data,
"result": self.result,
}
class WorkflowRunNode(Base):
"""工作流运行节点模型"""
__tablename__ = "workflow_run_nodes"
id = Column(String, primary_key=True)
workflow_run_id = Column(String, ForeignKey("workflow_run.id"), nullable=False)
node_id = Column(String, nullable=False)
node_type = Column(String, nullable=False)
status = Column(String, nullable=False, default="pending")
started_at = Column(DateTime)
completed_at = Column(DateTime)
output_data = Column(Text)
error_message = Column(Text)
# 关联关系
workflow_run = relationship("WorkflowRun", back_populates="nodes")
def to_dict(self) -> dict:
return {
"id": self.id,
"workflow_run_id": self.workflow_run_id,
"node_id": self.node_id,
"node_type": self.node_type,
"status": self.status,
"started_at": self.started_at.isoformat() if self.started_at else None,
"completed_at": (
self.completed_at.isoformat() if self.completed_at else None
),
"output_data": self.output_data,
"error_message": self.error_message,
}
class ComfyUIServer(Base):
"""ComfyUI 服务器模型"""
__tablename__ = "comfyui_servers"
name = Column(String, primary_key=True)
http_url = Column(String, nullable=False)
ws_url = Column(String, nullable=False)
status = Column(String, nullable=False, default="offline")
last_health_check = Column(DateTime)
current_tasks = Column(Integer, default=0)
max_concurrent_tasks = Column(Integer, default=1)
capabilities = Column(Text) # JSON 字符串
server_metadata = Column(Text) # JSON 字符串
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def to_dict(self) -> dict:
return {
"name": self.name,
"http_url": self.http_url,
"ws_url": self.ws_url,
"status": self.status,
"last_health_check": self.last_health_check.isoformat() if self.last_health_check else None,
"current_tasks": self.current_tasks,
"max_concurrent_tasks": self.max_concurrent_tasks,
"capabilities": self.capabilities,
"server_metadata": self.server_metadata,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}