Files
company-ai-platform/app/modules/workflows/service.py
JiuContinent dc8605ce3f ```
refactor(core): 重构核心模块结构并更新导入路径

- 将配置相关的设置从 app.core.config 移除
- 将常量定义从 app.core.constants 移除
- 将数据库相关功能从 app.core.database 移除
- 将基础数据库模型从 app.core.db_base 移除
- 将敏感信息掩码功能从 app.core.masking 移除
- 将中间件定义从 app.core.middleware 移除
- 将操作保护功能从 app.core.operation_guard 移除
- 将分页工具从 app.core.pagination 移除
- 将请求上下文管理从 app.core.request_context 移除
- 将调度器功能从 app.core.scheduler 移除
- 将安全认证逻辑从 app.core.security 移除
- 将任务队列相关功能从 app.core.task_queue 移除
- 将时间工具从 app.core.time 移除
- 更新 alembic 配置中的 Base 模型导入路径
- 更新各模块中对重构后组件的引用路径
```
2026-07-09 17:41:16 +08:00

135 lines
4.5 KiB
Python

from typing import Any
from fastapi import HTTPException, status
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from app.core.constants import ActorValue
from app.core.http.pagination import bounded_limit
from app.core.utils.time import utc_now
from app.modules.business.service import serialize_model
from app.modules.workflows.constants import (
WORKFLOW_ACTION_CODE_PREFIX,
WORKFLOW_CODE_PREFIX,
WorkflowErrorDetail,
WorkflowStatus,
)
from app.modules.workflows.models import WorkflowAction, WorkflowInstance
class WorkflowService:
"""Track V3 workflow instances and append-only workflow actions."""
def __init__(self, db: Session):
self.db = db
def start_or_update(
self,
workflow_type: str,
aggregate_type: str,
aggregate_id: str | int | None,
status_value: str,
action: str,
actor: str = ActorValue.SYSTEM,
payload: dict[str, Any] | None = None,
) -> WorkflowInstance:
aggregate_id_text = str(aggregate_id) if aggregate_id is not None else None
record = self.db.execute(
select(WorkflowInstance).where(
WorkflowInstance.workflow_type == workflow_type,
WorkflowInstance.aggregate_type == aggregate_type,
WorkflowInstance.aggregate_id == aggregate_id_text,
)
).scalar_one_or_none()
previous_status = None
now = utc_now()
if record is None:
record = WorkflowInstance(
code=f"{WORKFLOW_CODE_PREFIX}-{now:%Y%m%d%H%M%S%f}",
workflow_type=workflow_type,
aggregate_type=aggregate_type,
aggregate_id=aggregate_id_text,
status=status_value,
actor=actor,
current_step=action,
payload=payload or {},
)
self.db.add(record)
self.db.flush()
else:
previous_status = record.status
record.status = status_value
record.actor = actor
record.current_step = action
record.payload = payload or {}
record.updated_at = now
if status_value in {
WorkflowStatus.BLOCKED,
WorkflowStatus.COMPLETED,
WorkflowStatus.FAILED,
}:
record.completed_at = now
elif previous_status in {
WorkflowStatus.BLOCKED,
WorkflowStatus.COMPLETED,
WorkflowStatus.FAILED,
}:
record.completed_at = None
self.db.add(
WorkflowAction(
code=f"{WORKFLOW_ACTION_CODE_PREFIX}-{now:%Y%m%d%H%M%S%f}",
workflow_code=record.code,
action=action,
actor=actor,
from_status=previous_status,
to_status=status_value,
payload=payload or {},
)
)
self.db.commit()
self.db.refresh(record)
return record
def list_workflows(
self,
status_filter: str | None = None,
workflow_type: str | None = None,
limit: int = 100,
) -> list[dict[str, Any]]:
stmt = (
select(WorkflowInstance)
.order_by(WorkflowInstance.id.desc())
.limit(bounded_limit(limit))
)
if status_filter:
stmt = stmt.where(WorkflowInstance.status == status_filter)
if workflow_type:
stmt = stmt.where(WorkflowInstance.workflow_type == workflow_type)
return [serialize_model(item) for item in self.db.execute(stmt).scalars()]
def get_workflow(self, code: str) -> dict[str, Any]:
record = self.db.execute(
select(WorkflowInstance).where(WorkflowInstance.code == code)
).scalar_one_or_none()
if record is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=WorkflowErrorDetail.WORKFLOW_NOT_FOUND,
)
actions = self.db.execute(
select(WorkflowAction)
.where(WorkflowAction.workflow_code == code)
.order_by(WorkflowAction.id.asc())
).scalars()
data = serialize_model(record)
data["actions"] = [serialize_model(item) for item in actions]
return data
def count_by_status(self) -> dict[str, int]:
rows = self.db.execute(
select(WorkflowInstance.status, func.count()).group_by(WorkflowInstance.status)
).all()
return {str(status_value): int(count) for status_value, count in rows}