mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-04 17:14:21 +00:00
refactor(task): 重构定时任务与存储路由
- 移除各路由文件顶部冗余注释 - 将 JobRouter/NodeRouter 重命名为 CornJobRouter/CornJobNodeRouter - 新增存储浏览、节点、传输、工作流路由注册 - 调整路由导入来源与任务表名 - 优化 main.py 启动方式及环境配置加载
This commit is contained in:
@@ -1,5 +1,3 @@
|
|||||||
"""ai 路由清单:仅聚合路由,不含业务逻辑。"""
|
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from app.modules.ai.chat.controller import ChatRouter
|
from app.modules.ai.chat.controller import ChatRouter
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from app.modules.common.file.controller import FileRouter
|
||||||
|
|
||||||
|
file_router = APIRouter()
|
||||||
|
|
||||||
|
file_router.include_router(FileRouter)
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
"""file 路由清单:仅聚合路由,不含业务逻辑。"""
|
|
||||||
|
|
||||||
from fastapi import APIRouter
|
|
||||||
|
|
||||||
from app.modules.file.file.controller import FileRouter
|
|
||||||
|
|
||||||
file_router = APIRouter(prefix="/file")
|
|
||||||
|
|
||||||
file_router.include_router(FileRouter)
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
"""generator 路由清单:仅聚合路由,不含业务逻辑。"""
|
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from app.modules.generator.gencode.controller import GenRouter
|
from app.modules.generator.gencode.controller import GenRouter
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
"""monitor 路由清单:仅聚合路由,不含业务逻辑。"""
|
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from app.modules.monitor.cache.controller import CacheRouter
|
from app.modules.monitor.cache.controller import CacheRouter
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
"""system 路由清单:仅聚合路由,不含业务逻辑。"""
|
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from app.modules.system.auth.controller import AuthRouter
|
from app.modules.system.auth.controller import AuthRouter
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
"""task 路由清单:仅聚合路由,不含业务逻辑。"""
|
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from app.modules.task.cronjob.job.controller import JobRouter
|
from app.modules.task.cronjob.job.controller import CornJobRouter
|
||||||
from app.modules.task.cronjob.node.controller import NodeRouter
|
from app.modules.task.cronjob.node.controller import CornJobNodeRouter
|
||||||
|
from app.modules.task.storage.browse.controller import StorageBrowseRouter
|
||||||
|
from app.modules.task.storage.node.controller import StorageNodeRouter
|
||||||
|
from app.modules.task.storage.transfer.controller import StorageTransferRouter
|
||||||
|
from app.modules.task.storage.workflow.controller import StorageWorkflowRouter
|
||||||
|
|
||||||
task_router = APIRouter(prefix="/task")
|
task_router = APIRouter(prefix="/task")
|
||||||
|
|
||||||
task_router.include_router(JobRouter)
|
task_router.include_router(CornJobRouter)
|
||||||
task_router.include_router(NodeRouter)
|
task_router.include_router(CornJobNodeRouter)
|
||||||
|
task_router.include_router(StorageNodeRouter)
|
||||||
|
task_router.include_router(StorageBrowseRouter)
|
||||||
|
task_router.include_router(StorageTransferRouter)
|
||||||
|
task_router.include_router(StorageWorkflowRouter)
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
"""workflow 路由清单:仅聚合路由,不含业务逻辑。"""
|
|
||||||
|
|
||||||
from fastapi import APIRouter
|
|
||||||
|
|
||||||
from app.modules.workflow.flow.controller import WorkflowFlowRouter
|
|
||||||
from app.modules.workflow.source.controller import StorageSourceRouter
|
|
||||||
from app.modules.workflow.storage.controller import StorageFileRouter
|
|
||||||
from app.modules.workflow.transfer.controller import StorageTransferRouter
|
|
||||||
|
|
||||||
workflow_router = APIRouter(prefix="/workflow")
|
|
||||||
|
|
||||||
workflow_router.include_router(StorageSourceRouter)
|
|
||||||
workflow_router.include_router(StorageFileRouter)
|
|
||||||
workflow_router.include_router(StorageTransferRouter)
|
|
||||||
workflow_router.include_router(WorkflowFlowRouter)
|
|
||||||
@@ -75,12 +75,11 @@ def register_exceptions(app: FastAPI) -> None:
|
|||||||
|
|
||||||
def register_routers(app: FastAPI) -> None:
|
def register_routers(app: FastAPI) -> None:
|
||||||
from app.api.v1.ai import ai_router
|
from app.api.v1.ai import ai_router
|
||||||
from app.api.v1.file import file_router
|
from app.api.v1.common import file_router
|
||||||
from app.api.v1.generator import generator_router
|
from app.api.v1.generator import generator_router
|
||||||
from app.api.v1.monitor import monitor_router
|
from app.api.v1.monitor import monitor_router
|
||||||
from app.api.v1.system import system_router
|
from app.api.v1.system import system_router
|
||||||
from app.api.v1.task import task_router
|
from app.api.v1.task import task_router
|
||||||
from app.api.v1.workflow import workflow_router
|
|
||||||
|
|
||||||
app.include_router(file_router)
|
app.include_router(file_router)
|
||||||
app.include_router(monitor_router)
|
app.include_router(monitor_router)
|
||||||
@@ -88,7 +87,6 @@ def register_routers(app: FastAPI) -> None:
|
|||||||
app.include_router(ai_router)
|
app.include_router(ai_router)
|
||||||
app.include_router(generator_router)
|
app.include_router(generator_router)
|
||||||
app.include_router(task_router)
|
app.include_router(task_router)
|
||||||
app.include_router(workflow_router)
|
|
||||||
|
|
||||||
from app.core.discover import dynamic_router
|
from app.core.discover import dynamic_router
|
||||||
dynamic_router.init_app(app)
|
dynamic_router.init_app(app)
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ from app.utils.upload_util import UploadUtil
|
|||||||
|
|
||||||
from .service import FileService
|
from .service import FileService
|
||||||
|
|
||||||
FileRouter = APIRouter(route_class=OperationLogRoute, tags=["文件管理"])
|
FileRouter = APIRouter(route_class=OperationLogRoute, prefix="/file", tags=["文件管理"])
|
||||||
|
|
||||||
|
|
||||||
@FileRouter.post("/upload", summary="上传文件", response_model=ResponseSchema[UploadResponseSchema], dependencies=[Security(AuthPermission(["module_common:file:upload"]))])
|
@FileRouter.post("/upload", summary="上传文件", response_model=ResponseSchema[UploadResponseSchema], dependencies=[Security(AuthPermission(["module_common:file:upload"]))])
|
||||||
@@ -1 +0,0 @@
|
|||||||
"""定时任务子包:调度器(job)与 APScheduler 节点(node)。"""
|
|
||||||
@@ -18,58 +18,58 @@ from .schema import (
|
|||||||
)
|
)
|
||||||
from .service import JobService
|
from .service import JobService
|
||||||
|
|
||||||
JobRouter = APIRouter(route_class=OperationLogRoute, prefix="/cronjob/job", tags=["定时任务管理"])
|
CornJobRouter = APIRouter(route_class=OperationLogRoute, prefix="/cronjob/job", tags=["定时任务管理"])
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.get("/scheduler/status", summary="获取调度器状态", response_model=ResponseSchema[SchedulerStatusSchema], dependencies=[Security(AuthPermission(["module_task:cronjob:job:query"]))])
|
@CornJobRouter.get("/scheduler/status", summary="获取调度器状态", response_model=ResponseSchema[SchedulerStatusSchema], dependencies=[Security(AuthPermission(["module_task:cronjob:job:query"]))])
|
||||||
async def get_scheduler_status_controller() -> JSONResponse:
|
async def get_scheduler_status_controller() -> JSONResponse:
|
||||||
data: SchedulerStatusSchema = JobService.get_scheduler_status()
|
data: SchedulerStatusSchema = JobService.get_scheduler_status()
|
||||||
return SuccessResponse(data=data, msg="获取调度器状态成功")
|
return SuccessResponse(data=data, msg="获取调度器状态成功")
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.get("/scheduler/jobs", summary="获取调度器任务列表", response_model=ResponseSchema[list[SchedulerJobSchema]], dependencies=[Security(AuthPermission(["module_task:cronjob:job:query"]))])
|
@CornJobRouter.get("/scheduler/jobs", summary="获取调度器任务列表", response_model=ResponseSchema[list[SchedulerJobSchema]], dependencies=[Security(AuthPermission(["module_task:cronjob:job:query"]))])
|
||||||
async def get_scheduler_jobs_controller() -> JSONResponse:
|
async def get_scheduler_jobs_controller() -> JSONResponse:
|
||||||
data: list[SchedulerJobSchema] = JobService.get_scheduler_jobs()
|
data: list[SchedulerJobSchema] = JobService.get_scheduler_jobs()
|
||||||
return SuccessResponse(data=data, msg="获取调度器任务列表成功")
|
return SuccessResponse(data=data, msg="获取调度器任务列表成功")
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.post("/scheduler/start", summary="启动调度器", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:scheduler"]))])
|
@CornJobRouter.post("/scheduler/start", summary="启动调度器", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:scheduler"]))])
|
||||||
async def start_scheduler_controller() -> JSONResponse:
|
async def start_scheduler_controller() -> JSONResponse:
|
||||||
await JobService.start_scheduler()
|
await JobService.start_scheduler()
|
||||||
return SuccessResponse(msg="调度器已启动")
|
return SuccessResponse(msg="调度器已启动")
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.post("/scheduler/pause", summary="暂停调度器", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:scheduler"]))])
|
@CornJobRouter.post("/scheduler/pause", summary="暂停调度器", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:scheduler"]))])
|
||||||
async def pause_scheduler_controller() -> JSONResponse:
|
async def pause_scheduler_controller() -> JSONResponse:
|
||||||
JobService.pause_scheduler()
|
JobService.pause_scheduler()
|
||||||
return SuccessResponse(msg="调度器已暂停")
|
return SuccessResponse(msg="调度器已暂停")
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.post("/scheduler/resume", summary="恢复调度器", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:scheduler"]))])
|
@CornJobRouter.post("/scheduler/resume", summary="恢复调度器", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:scheduler"]))])
|
||||||
async def resume_scheduler_controller() -> JSONResponse:
|
async def resume_scheduler_controller() -> JSONResponse:
|
||||||
JobService.resume_scheduler()
|
JobService.resume_scheduler()
|
||||||
return SuccessResponse(msg="调度器已恢复")
|
return SuccessResponse(msg="调度器已恢复")
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.post("/scheduler/shutdown", summary="关闭调度器", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:scheduler"]))])
|
@CornJobRouter.post("/scheduler/shutdown", summary="关闭调度器", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:scheduler"]))])
|
||||||
async def shutdown_scheduler_controller() -> JSONResponse:
|
async def shutdown_scheduler_controller() -> JSONResponse:
|
||||||
JobService.shutdown_scheduler()
|
JobService.shutdown_scheduler()
|
||||||
return SuccessResponse(msg="调度器已关闭")
|
return SuccessResponse(msg="调度器已关闭")
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.delete("/scheduler/jobs/clear", summary="清空所有任务", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:task"]))])
|
@CornJobRouter.delete("/scheduler/jobs/clear", summary="清空所有任务", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:task"]))])
|
||||||
async def clear_jobs_controller() -> JSONResponse:
|
async def clear_jobs_controller() -> JSONResponse:
|
||||||
JobService.clear_scheduler_jobs()
|
JobService.clear_scheduler_jobs()
|
||||||
return SuccessResponse(msg="已清空所有任务")
|
return SuccessResponse(msg="已清空所有任务")
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.get("/scheduler/console", summary="获取调度器控制台信息", response_model=ResponseSchema[str], dependencies=[Security(AuthPermission(["module_task:cronjob:job:query"]))])
|
@CornJobRouter.get("/scheduler/console", summary="获取调度器控制台信息", response_model=ResponseSchema[str], dependencies=[Security(AuthPermission(["module_task:cronjob:job:query"]))])
|
||||||
async def get_scheduler_console_controller() -> JSONResponse:
|
async def get_scheduler_console_controller() -> JSONResponse:
|
||||||
console_output = JobService.get_scheduler_console()
|
console_output = JobService.get_scheduler_console()
|
||||||
return SuccessResponse(data=console_output, msg="获取控制台信息成功")
|
return SuccessResponse(data=console_output, msg="获取控制台信息成功")
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.post("/task/pause/{job_id}", summary="暂停任务", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:task"]))])
|
@CornJobRouter.post("/task/pause/{job_id}", summary="暂停任务", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:task"]))])
|
||||||
async def pause_job_controller(
|
async def pause_job_controller(
|
||||||
job_id: Annotated[str, Path(description="调度器任务ID")],
|
job_id: Annotated[str, Path(description="调度器任务ID")],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
@@ -77,7 +77,7 @@ async def pause_job_controller(
|
|||||||
return SuccessResponse(msg="暂停任务成功")
|
return SuccessResponse(msg="暂停任务成功")
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.post("/task/resume/{job_id}", summary="恢复任务", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:task"]))])
|
@CornJobRouter.post("/task/resume/{job_id}", summary="恢复任务", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:task"]))])
|
||||||
async def resume_job_controller(
|
async def resume_job_controller(
|
||||||
job_id: Annotated[str, Path(description="调度器任务ID")],
|
job_id: Annotated[str, Path(description="调度器任务ID")],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
@@ -85,7 +85,7 @@ async def resume_job_controller(
|
|||||||
return SuccessResponse(msg="恢复任务成功")
|
return SuccessResponse(msg="恢复任务成功")
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.post("/task/run/{job_id}", summary="立即执行任务", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:task"]))])
|
@CornJobRouter.post("/task/run/{job_id}", summary="立即执行任务", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:task"]))])
|
||||||
async def run_job_controller(
|
async def run_job_controller(
|
||||||
job_id: Annotated[str, Path(description="调度器任务ID")],
|
job_id: Annotated[str, Path(description="调度器任务ID")],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
@@ -93,7 +93,7 @@ async def run_job_controller(
|
|||||||
return SuccessResponse(msg="立即执行任务成功")
|
return SuccessResponse(msg="立即执行任务成功")
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.put("/task/modify/{job_id}", summary="修改任务", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:task"]))])
|
@CornJobRouter.put("/task/modify/{job_id}", summary="修改任务", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:task"]))])
|
||||||
async def modify_job_controller(
|
async def modify_job_controller(
|
||||||
job_id: Annotated[str, Path(description="调度器任务ID")],
|
job_id: Annotated[str, Path(description="调度器任务ID")],
|
||||||
data: Annotated[SchedulerJobModifySchema, Body(description="要修改的任务属性,如 name、coalesce、max_instances 等")],
|
data: Annotated[SchedulerJobModifySchema, Body(description="要修改的任务属性,如 name、coalesce、max_instances 等")],
|
||||||
@@ -102,7 +102,7 @@ async def modify_job_controller(
|
|||||||
return SuccessResponse(msg="修改任务成功")
|
return SuccessResponse(msg="修改任务成功")
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.delete("/task/remove/{job_id}", summary="移除任务", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:delete"]))])
|
@CornJobRouter.delete("/task/remove/{job_id}", summary="移除任务", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:delete"]))])
|
||||||
async def remove_job_controller(
|
async def remove_job_controller(
|
||||||
job_id: Annotated[str, Path(description="调度器任务ID")],
|
job_id: Annotated[str, Path(description="调度器任务ID")],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
@@ -110,7 +110,7 @@ async def remove_job_controller(
|
|||||||
return SuccessResponse(msg="移除任务成功")
|
return SuccessResponse(msg="移除任务成功")
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.get("/log/list", summary="查询执行日志列表", response_model=ResponseSchema[PageResultSchema[JobOutSchema]])
|
@CornJobRouter.get("/log/list", summary="查询执行日志列表", response_model=ResponseSchema[PageResultSchema[JobOutSchema]])
|
||||||
async def get_job_log_list_controller(
|
async def get_job_log_list_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:job:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:job:query"]))],
|
||||||
page: Annotated[PaginationQueryParam, Depends()],
|
page: Annotated[PaginationQueryParam, Depends()],
|
||||||
@@ -126,7 +126,7 @@ async def get_job_log_list_controller(
|
|||||||
return SuccessResponse(data=result_dict, msg="查询执行日志列表成功")
|
return SuccessResponse(data=result_dict, msg="查询执行日志列表成功")
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.get("/log/detail/{id}", summary="获取执行日志详情", response_model=ResponseSchema[JobOutSchema])
|
@CornJobRouter.get("/log/detail/{id}", summary="获取执行日志详情", response_model=ResponseSchema[JobOutSchema])
|
||||||
async def get_job_log_detail_controller(
|
async def get_job_log_detail_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:job:detail"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:job:detail"]))],
|
||||||
id: Annotated[int, Path(description="日志ID")],
|
id: Annotated[int, Path(description="日志ID")],
|
||||||
@@ -136,7 +136,7 @@ async def get_job_log_detail_controller(
|
|||||||
return SuccessResponse(data=result_dict, msg="获取执行日志详情成功")
|
return SuccessResponse(data=result_dict, msg="获取执行日志详情成功")
|
||||||
|
|
||||||
|
|
||||||
@JobRouter.delete("/log/delete", summary="删除执行日志", response_model=ResponseSchema[None])
|
@CornJobRouter.delete("/log/delete", summary="删除执行日志", response_model=ResponseSchema[None])
|
||||||
async def delete_job_log_controller(
|
async def delete_job_log_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:job:delete"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:job:delete"]))],
|
||||||
ids: Annotated[list[int], Body(description="ID列表")],
|
ids: Annotated[list[int], Body(description="ID列表")],
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class JobModel(ModelMixin):
|
|||||||
"""任务执行日志表
|
"""任务执行日志表
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__: str = "task_job"
|
__tablename__: str = "task_corn_job"
|
||||||
__table_args__: dict[str, str] = {"comment": "任务执行日志表"}
|
__table_args__: dict[str, str] = {"comment": "任务执行日志表"}
|
||||||
|
|
||||||
job_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment="任务ID")
|
job_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment="任务ID")
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ from app.core.router_class import OperationLogRoute
|
|||||||
from .schema import NodeCreateSchema, NodeOutSchema, NodeQueryParam, NodeRunResultSchema, NodeUpdateSchema
|
from .schema import NodeCreateSchema, NodeOutSchema, NodeQueryParam, NodeRunResultSchema, NodeUpdateSchema
|
||||||
from .service import NodeService
|
from .service import NodeService
|
||||||
|
|
||||||
NodeRouter = APIRouter(route_class=OperationLogRoute, prefix="/cronjob/node", tags=["定时任务节点管理"])
|
CornJobNodeRouter = APIRouter(route_class=OperationLogRoute, prefix="/cronjob/node", tags=["定时任务节点管理"])
|
||||||
|
|
||||||
|
|
||||||
@NodeRouter.get("/options", summary="获取定时任务节点列表", response_model=ResponseSchema[list[NodeOutSchema]])
|
@CornJobNodeRouter.get("/options", summary="获取定时任务节点列表", response_model=ResponseSchema[list[NodeOutSchema]])
|
||||||
async def get_node_options_controller(
|
async def get_node_options_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:query"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
@@ -25,7 +25,7 @@ async def get_node_options_controller(
|
|||||||
return SuccessResponse(data=result, msg="获取定时任务节点选项成功")
|
return SuccessResponse(data=result, msg="获取定时任务节点选项成功")
|
||||||
|
|
||||||
|
|
||||||
@NodeRouter.get("/detail/{id}", summary="获取节点详情", response_model=ResponseSchema[NodeOutSchema])
|
@CornJobNodeRouter.get("/detail/{id}", summary="获取节点详情", response_model=ResponseSchema[NodeOutSchema])
|
||||||
async def get_obj_detail_controller(
|
async def get_obj_detail_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:detail"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:detail"]))],
|
||||||
id: Annotated[int, Path(description="节点ID")],
|
id: Annotated[int, Path(description="节点ID")],
|
||||||
@@ -36,7 +36,7 @@ async def get_obj_detail_controller(
|
|||||||
return SuccessResponse(data=result_dict, msg="获取节点详情成功")
|
return SuccessResponse(data=result_dict, msg="获取节点详情成功")
|
||||||
|
|
||||||
|
|
||||||
@NodeRouter.get("/list", summary="查询节点", response_model=ResponseSchema[PageResultSchema[NodeOutSchema]])
|
@CornJobNodeRouter.get("/list", summary="查询节点", response_model=ResponseSchema[PageResultSchema[NodeOutSchema]])
|
||||||
async def get_obj_list_controller(
|
async def get_obj_list_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:query"]))],
|
||||||
page: Annotated[PaginationQueryParam, Depends()],
|
page: Annotated[PaginationQueryParam, Depends()],
|
||||||
@@ -53,7 +53,7 @@ async def get_obj_list_controller(
|
|||||||
return SuccessResponse(data=result_dict, msg="查询节点列表成功")
|
return SuccessResponse(data=result_dict, msg="查询节点列表成功")
|
||||||
|
|
||||||
|
|
||||||
@NodeRouter.post("/create", status_code=status.HTTP_201_CREATED, summary="创建节点", response_model=ResponseSchema[NodeOutSchema])
|
@CornJobNodeRouter.post("/create", status_code=status.HTTP_201_CREATED, summary="创建节点", response_model=ResponseSchema[NodeOutSchema])
|
||||||
async def create_obj_controller(
|
async def create_obj_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:create"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:create"]))],
|
||||||
data: Annotated[NodeCreateSchema, Body(description="创建节点参数")],
|
data: Annotated[NodeCreateSchema, Body(description="创建节点参数")],
|
||||||
@@ -64,7 +64,7 @@ async def create_obj_controller(
|
|||||||
return SuccessResponse(data=result_dict, msg="创建节点成功")
|
return SuccessResponse(data=result_dict, msg="创建节点成功")
|
||||||
|
|
||||||
|
|
||||||
@NodeRouter.put("/update/{id}", summary="修改节点", response_model=ResponseSchema[NodeOutSchema])
|
@CornJobNodeRouter.put("/update/{id}", summary="修改节点", response_model=ResponseSchema[NodeOutSchema])
|
||||||
async def update_obj_controller(
|
async def update_obj_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:update"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:update"]))],
|
||||||
id: Annotated[int, Path(description="节点ID")],
|
id: Annotated[int, Path(description="节点ID")],
|
||||||
@@ -76,7 +76,7 @@ async def update_obj_controller(
|
|||||||
return SuccessResponse(data=result_dict, msg="修改节点成功")
|
return SuccessResponse(data=result_dict, msg="修改节点成功")
|
||||||
|
|
||||||
|
|
||||||
@NodeRouter.delete("/delete", summary="删除节点", response_model=ResponseSchema[None])
|
@CornJobNodeRouter.delete("/delete", summary="删除节点", response_model=ResponseSchema[None])
|
||||||
async def delete_obj_controller(
|
async def delete_obj_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:delete"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:delete"]))],
|
||||||
ids: Annotated[list[int], Body(description="ID列表")],
|
ids: Annotated[list[int], Body(description="ID列表")],
|
||||||
@@ -87,7 +87,7 @@ async def delete_obj_controller(
|
|||||||
return SuccessResponse(msg="删除节点成功")
|
return SuccessResponse(msg="删除节点成功")
|
||||||
|
|
||||||
|
|
||||||
@NodeRouter.delete("/clear", summary="清空节点", response_model=ResponseSchema[None])
|
@CornJobNodeRouter.delete("/clear", summary="清空节点", response_model=ResponseSchema[None])
|
||||||
async def clear_obj_controller(
|
async def clear_obj_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:delete"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:delete"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
@@ -97,7 +97,7 @@ async def clear_obj_controller(
|
|||||||
return SuccessResponse(msg="清空节点成功")
|
return SuccessResponse(msg="清空节点成功")
|
||||||
|
|
||||||
|
|
||||||
@NodeRouter.post("/execute/{id}", summary="手动执行节点(立即执行一次)", response_model=ResponseSchema[NodeRunResultSchema])
|
@CornJobNodeRouter.post("/execute/{id}", summary="手动执行节点(立即执行一次)", response_model=ResponseSchema[NodeRunResultSchema])
|
||||||
async def execute_job_controller(
|
async def execute_job_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:execute"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:execute"]))],
|
||||||
id: Annotated[int, Path(description="节点ID")],
|
id: Annotated[int, Path(description="节点ID")],
|
||||||
@@ -108,7 +108,7 @@ async def execute_job_controller(
|
|||||||
return SuccessResponse(data=result, msg="节点已触发执行")
|
return SuccessResponse(data=result, msg="节点已触发执行")
|
||||||
|
|
||||||
|
|
||||||
@NodeRouter.patch("/status/batch", summary="批量设置节点状态", response_model=ResponseSchema[None])
|
@CornJobNodeRouter.patch("/status/batch", summary="批量设置节点状态", response_model=ResponseSchema[None])
|
||||||
async def batch_set_status_controller(
|
async def batch_set_status_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:update"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:update"]))],
|
||||||
data: Annotated[BatchSetAvailable, Body(description="状态设置")],
|
data: Annotated[BatchSetAvailable, Body(description="状态设置")],
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class NodeModel(ModelMixin, UserMixin):
|
|||||||
"""节点类型模型 - 动态定义节点类型
|
"""节点类型模型 - 动态定义节点类型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__: str = "task_node"
|
__tablename__: str = "task_cornjob_node"
|
||||||
__table_args__: dict[str, str] = {"comment": "节点类型表"}
|
__table_args__: dict[str, str] = {"comment": "节点类型表"}
|
||||||
|
|
||||||
name: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment="节点名称")
|
name: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment="节点名称")
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"""存储传输模块:
|
||||||
|
|
||||||
|
- ``node``: 存储源管理,即“节点”(OSS/COS/OBS/S3/SFTP/FTP 等连接配置)
|
||||||
|
- ``core``: 对象存储协议适配器与工厂
|
||||||
|
- ``browse``: 存储文件浏览
|
||||||
|
- ``transfer``: 传输任务引擎
|
||||||
|
- ``workflow``: 传输流程编排(画布 CRUD、发布、执行 API)
|
||||||
|
|
||||||
|
路由统一挂在 ``/task/storage`` 下(由 ``app/api/v1/task.py`` 将各 controller 的 ``/storage/*`` 前缀聚合进 task 域)。
|
||||||
|
"""
|
||||||
+22
-22
@@ -9,12 +9,12 @@ from app.common.response import ResponseSchema, SuccessResponse, UploadFileRespo
|
|||||||
from app.core.base_schema import AuthSchema
|
from app.core.base_schema import AuthSchema
|
||||||
from app.core.dependencies import AuthPermission, db_getter
|
from app.core.dependencies import AuthPermission, db_getter
|
||||||
from app.core.router_class import OperationLogRoute
|
from app.core.router_class import OperationLogRoute
|
||||||
from app.modules.workflow.core.base import StorageObject, StoragePage
|
from app.modules.task.storage.core.base import StorageObject, StoragePage
|
||||||
|
|
||||||
from .schema import StoragePathCreateSchema, StoragePathResultSchema, StorageUploadResultSchema
|
from .schema import StoragePathCreateSchema, StoragePathResultSchema, StorageUploadResultSchema
|
||||||
from .service import StorageFileService
|
from .service import StorageFileService
|
||||||
|
|
||||||
StorageFileRouter = APIRouter(route_class=OperationLogRoute, prefix="/storage", tags=["存储管理"])
|
StorageBrowseRouter = APIRouter(route_class=OperationLogRoute, prefix="/storage/browse", tags=["存储管理"])
|
||||||
|
|
||||||
|
|
||||||
def _delete_temp_file(path: str) -> None:
|
def _delete_temp_file(path: str) -> None:
|
||||||
@@ -25,9 +25,9 @@ def _delete_temp_file(path: str) -> None:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@StorageFileRouter.post("/upload", summary="上传文件到存储源", response_model=ResponseSchema[StorageUploadResultSchema])
|
@StorageBrowseRouter.post("/upload", summary="上传文件到存储源", response_model=ResponseSchema[StorageUploadResultSchema])
|
||||||
async def upload_storage_file_controller(
|
async def upload_storage_file_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:storage:upload"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:browse:upload"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
file: Annotated[UploadFile, File(description="上传文件")],
|
file: Annotated[UploadFile, File(description="上传文件")],
|
||||||
source_id: Annotated[int | None, Form(description="存储源ID(不传使用默认存储源)")] = None,
|
source_id: Annotated[int | None, Form(description="存储源ID(不传使用默认存储源)")] = None,
|
||||||
@@ -40,9 +40,9 @@ async def upload_storage_file_controller(
|
|||||||
return SuccessResponse(data=result, msg="上传文件成功")
|
return SuccessResponse(data=result, msg="上传文件成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageFileRouter.post("/download", summary="下载存储源文件", response_model=None)
|
@StorageBrowseRouter.post("/download", summary="下载存储源文件", response_model=None)
|
||||||
async def download_storage_file_controller(
|
async def download_storage_file_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:storage:download"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:browse:download"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
remote_path: Annotated[str, Body(description="远端文件路径")],
|
remote_path: Annotated[str, Body(description="远端文件路径")],
|
||||||
@@ -56,9 +56,9 @@ async def download_storage_file_controller(
|
|||||||
return UploadFileResponse(file_path=local_path, filename=file_name)
|
return UploadFileResponse(file_path=local_path, filename=file_name)
|
||||||
|
|
||||||
|
|
||||||
@StorageFileRouter.post("/download_dir", summary="下载存储源目录(递归打包ZIP)", response_model=None)
|
@StorageBrowseRouter.post("/download_dir", summary="下载存储源目录(递归打包ZIP)", response_model=None)
|
||||||
async def download_dir_storage_file_controller(
|
async def download_dir_storage_file_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:storage:download"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:browse:download"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
remote_path: Annotated[str, Body(description="远端目录路径")],
|
remote_path: Annotated[str, Body(description="远端目录路径")],
|
||||||
@@ -72,9 +72,9 @@ async def download_dir_storage_file_controller(
|
|||||||
return UploadFileResponse(file_path=local_path, filename=file_name)
|
return UploadFileResponse(file_path=local_path, filename=file_name)
|
||||||
|
|
||||||
|
|
||||||
@StorageFileRouter.delete("/delete", summary="删除存储源文件", response_model=ResponseSchema[None])
|
@StorageBrowseRouter.delete("/delete", summary="删除存储源文件", response_model=ResponseSchema[None])
|
||||||
async def delete_storage_file_controller(
|
async def delete_storage_file_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:storage:delete"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:browse:delete"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
remote_path: Annotated[str, Body(description="远端文件路径")],
|
remote_path: Annotated[str, Body(description="远端文件路径")],
|
||||||
source_id: Annotated[int | None, Body(description="存储源ID(不传使用默认存储源)")] = None,
|
source_id: Annotated[int | None, Body(description="存储源ID(不传使用默认存储源)")] = None,
|
||||||
@@ -84,9 +84,9 @@ async def delete_storage_file_controller(
|
|||||||
return SuccessResponse(msg="删除文件成功")
|
return SuccessResponse(msg="删除文件成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageFileRouter.get("/list", summary="查询存储源文件列表", response_model=None)
|
@StorageBrowseRouter.get("/list", summary="查询存储源文件列表", response_model=None)
|
||||||
async def list_storage_file_controller(
|
async def list_storage_file_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:storage:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:browse:query"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
source_id: Annotated[int | None, Query(description="存储源ID(不传使用默认存储源)")] = None,
|
source_id: Annotated[int | None, Query(description="存储源ID(不传使用默认存储源)")] = None,
|
||||||
prefix: Annotated[str | None, Query(description="目录前缀(可选)")] = None,
|
prefix: Annotated[str | None, Query(description="目录前缀(可选)")] = None,
|
||||||
@@ -100,9 +100,9 @@ async def list_storage_file_controller(
|
|||||||
return SuccessResponse(data=result, msg="查询文件列表成功")
|
return SuccessResponse(data=result, msg="查询文件列表成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageFileRouter.get("/buckets", summary="查询存储源桶列表", response_model=ResponseSchema[list[str]])
|
@StorageBrowseRouter.get("/buckets", summary="查询存储源桶列表", response_model=ResponseSchema[list[str]])
|
||||||
async def list_storage_buckets_controller(
|
async def list_storage_buckets_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:storage:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:browse:query"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
source_id: Annotated[int | None, Query(description="存储源ID(不传使用默认存储源)")] = None,
|
source_id: Annotated[int | None, Query(description="存储源ID(不传使用默认存储源)")] = None,
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
@@ -110,9 +110,9 @@ async def list_storage_buckets_controller(
|
|||||||
return SuccessResponse(data=result, msg="查询桶列表成功")
|
return SuccessResponse(data=result, msg="查询桶列表成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageFileRouter.post("/copy", summary="复制/移动文件", response_model=ResponseSchema[StoragePathResultSchema])
|
@StorageBrowseRouter.post("/copy", summary="复制/移动文件", response_model=ResponseSchema[StoragePathResultSchema])
|
||||||
async def copy_or_move_storage_file_controller(
|
async def copy_or_move_storage_file_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:storage:update"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:browse:update"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
source_id: Annotated[int | None, Body(description="源存储源ID(不传使用默认存储源)")] = None,
|
source_id: Annotated[int | None, Body(description="源存储源ID(不传使用默认存储源)")] = None,
|
||||||
source_path: Annotated[str, Body(description="源文件路径")] = "",
|
source_path: Annotated[str, Body(description="源文件路径")] = "",
|
||||||
@@ -132,9 +132,9 @@ async def copy_or_move_storage_file_controller(
|
|||||||
return SuccessResponse(data=result, msg="操作文件成功")
|
return SuccessResponse(data=result, msg="操作文件成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageFileRouter.put("/rename", summary="重命名/移动文件", response_model=ResponseSchema[StoragePathResultSchema])
|
@StorageBrowseRouter.put("/rename", summary="重命名/移动文件", response_model=ResponseSchema[StoragePathResultSchema])
|
||||||
async def rename_storage_file_controller(
|
async def rename_storage_file_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:storage:update"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:browse:update"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
source_id: Annotated[int | None, Body(description="存储源ID(不传使用默认存储源)")] = None,
|
source_id: Annotated[int | None, Body(description="存储源ID(不传使用默认存储源)")] = None,
|
||||||
source_path: Annotated[str, Body(description="原路径")] = "",
|
source_path: Annotated[str, Body(description="原路径")] = "",
|
||||||
@@ -147,9 +147,9 @@ async def rename_storage_file_controller(
|
|||||||
return SuccessResponse(data=result, msg="重命名成功")
|
return SuccessResponse(data=result, msg="重命名成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageFileRouter.post("/mkdir", summary="新建目录", response_model=ResponseSchema[StoragePathCreateSchema])
|
@StorageBrowseRouter.post("/mkdir", summary="新建目录", response_model=ResponseSchema[StoragePathCreateSchema])
|
||||||
async def mkdir_storage_file_controller(
|
async def mkdir_storage_file_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:storage:update"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:browse:update"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
source_id: Annotated[int | None, Body(description="存储源ID(不传使用默认存储源)")] = None,
|
source_id: Annotated[int | None, Body(description="存储源ID(不传使用默认存储源)")] = None,
|
||||||
remote_dir: Annotated[str, Body(description="目录路径")] = "",
|
remote_dir: Annotated[str, Body(description="目录路径")] = "",
|
||||||
@@ -159,9 +159,9 @@ async def mkdir_storage_file_controller(
|
|||||||
return SuccessResponse(data=result, msg="新建目录成功")
|
return SuccessResponse(data=result, msg="新建目录成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageFileRouter.post("/share", summary="生成分享链接", response_model=ResponseSchema[str | None])
|
@StorageBrowseRouter.post("/share", summary="生成分享链接", response_model=ResponseSchema[str | None])
|
||||||
async def share_storage_file_controller(
|
async def share_storage_file_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:storage:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:browse:query"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
remote_path: Annotated[str, Body(description="远端文件路径")] = "",
|
remote_path: Annotated[str, Body(description="远端文件路径")] = "",
|
||||||
source_id: Annotated[int | None, Body(description="存储源ID(不传使用默认存储源)")] = None,
|
source_id: Annotated[int | None, Body(description="存储源ID(不传使用默认存储源)")] = None,
|
||||||
+5
-5
@@ -11,15 +11,15 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
|
|
||||||
from app.core.base_schema import AuthSchema
|
from app.core.base_schema import AuthSchema
|
||||||
from app.core.exceptions import CustomException
|
from app.core.exceptions import CustomException
|
||||||
from app.modules.workflow.core.base import (
|
from app.modules.task.storage.core.base import (
|
||||||
_OBJECT_STORE_PROTOCOLS,
|
_OBJECT_STORE_PROTOCOLS,
|
||||||
BaseStorageAdapter,
|
BaseStorageAdapter,
|
||||||
StorageAdapterConfig,
|
StorageAdapterConfig,
|
||||||
StorageObject,
|
StorageObject,
|
||||||
StoragePage,
|
StoragePage,
|
||||||
)
|
)
|
||||||
from app.modules.workflow.core.factory import StorageAdapterFactory
|
from app.modules.task.storage.core.factory import StorageAdapterFactory
|
||||||
from app.modules.workflow.source.service import StorageSourceService
|
from app.modules.task.storage.node.service import StorageNodeService
|
||||||
from app.utils.upload_util import UploadUtil
|
from app.utils.upload_util import UploadUtil
|
||||||
|
|
||||||
from .schema import StoragePathCreateSchema, StoragePathResultSchema, StorageUploadResultSchema
|
from .schema import StoragePathCreateSchema, StoragePathResultSchema, StorageUploadResultSchema
|
||||||
@@ -46,8 +46,8 @@ class StorageFileService:
|
|||||||
|
|
||||||
async def _get_source(self, source_id: int | None) -> StorageAdapterConfig:
|
async def _get_source(self, source_id: int | None) -> StorageAdapterConfig:
|
||||||
"""获取存储源并构造适配器配置(密码已解密,含 SDK 高级配置)。"""
|
"""获取存储源并构造适配器配置(密码已解密,含 SDK 高级配置)。"""
|
||||||
source = await StorageSourceService(self.auth, self.db).get_active_source(source_id)
|
source = await StorageNodeService(self.auth, self.db).get_active_source(source_id)
|
||||||
return StorageSourceService._build_config(source)
|
return StorageNodeService._build_config(source)
|
||||||
|
|
||||||
async def _get_adapter(self, source_id: int | None, bucket: str | None = None) -> BaseStorageAdapter:
|
async def _get_adapter(self, source_id: int | None, bucket: str | None = None) -> BaseStorageAdapter:
|
||||||
"""构造适配器并切换当前操作桶(对象存储多桶浏览:桶随请求传入,适配器按请求创建)。"""
|
"""构造适配器并切换当前操作桶(对象存储多桶浏览:桶随请求传入,适配器按请求创建)。"""
|
||||||
+1
-1
@@ -98,7 +98,7 @@ _STREAM_THRESHOLD = 100 * 1024 * 1024 * 1024
|
|||||||
|
|
||||||
|
|
||||||
class StorageAdapterConfig(BaseModel):
|
class StorageAdapterConfig(BaseModel):
|
||||||
"""存储适配器配置(从 StorageSourceModel 剥离加密字段后注入,解耦 ORM 与协议层)"""
|
"""存储适配器配置(从 StorageNodeModel 剥离加密字段后注入,解耦 ORM 与协议层)"""
|
||||||
|
|
||||||
protocol: StorageProtocol = Field(description="存储协议")
|
protocol: StorageProtocol = Field(description="存储协议")
|
||||||
host: str | None = Field(default=None, description="主机地址(对象存储可不填)")
|
host: str | None = Field(default=None, description="主机地址(对象存储可不填)")
|
||||||
+1
-1
@@ -4,7 +4,7 @@ from qcloud_cos import CosConfig, CosS3Client
|
|||||||
|
|
||||||
from app.core.exceptions import CustomException
|
from app.core.exceptions import CustomException
|
||||||
from app.core.logger import logger
|
from app.core.logger import logger
|
||||||
from app.modules.workflow.core.base import (
|
from app.modules.task.storage.core.base import (
|
||||||
BaseStorageAdapter,
|
BaseStorageAdapter,
|
||||||
CosAdvancedConfig,
|
CosAdvancedConfig,
|
||||||
StorageObject,
|
StorageObject,
|
||||||
+8
-8
@@ -1,12 +1,12 @@
|
|||||||
from app.core.exceptions import CustomException
|
from app.core.exceptions import CustomException
|
||||||
from app.modules.workflow.core.base import BaseStorageAdapter, StorageAdapterConfig, StorageProtocol
|
from app.modules.task.storage.core.base import BaseStorageAdapter, StorageAdapterConfig, StorageProtocol
|
||||||
from app.modules.workflow.core.cos_adapter import CosStorageAdapter
|
from app.modules.task.storage.core.cos_adapter import CosStorageAdapter
|
||||||
from app.modules.workflow.core.ftp_ftps_adapter import FtpStorageAdapter
|
from app.modules.task.storage.core.ftp_ftps_adapter import FtpStorageAdapter
|
||||||
from app.modules.workflow.core.local_adapter import LocalStorageAdapter
|
from app.modules.task.storage.core.local_adapter import LocalStorageAdapter
|
||||||
from app.modules.workflow.core.obs_adapter import ObsStorageAdapter
|
from app.modules.task.storage.core.obs_adapter import ObsStorageAdapter
|
||||||
from app.modules.workflow.core.oss_adapter import OssStorageAdapter
|
from app.modules.task.storage.core.oss_adapter import OssStorageAdapter
|
||||||
from app.modules.workflow.core.s3_adapter import S3StorageAdapter
|
from app.modules.task.storage.core.s3_adapter import S3StorageAdapter
|
||||||
from app.modules.workflow.core.sftp_adapter import SftpStorageAdapter
|
from app.modules.task.storage.core.sftp_adapter import SftpStorageAdapter
|
||||||
|
|
||||||
# 协议 → 适配器类映射(FTPS 复用 FTP 适配器,由配置区分显式/隐式 TLS)
|
# 协议 → 适配器类映射(FTPS 复用 FTP 适配器,由配置区分显式/隐式 TLS)
|
||||||
_STORAGE_ADAPTERS: dict[str, type[BaseStorageAdapter]] = {
|
_STORAGE_ADAPTERS: dict[str, type[BaseStorageAdapter]] = {
|
||||||
+1
-1
@@ -7,7 +7,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
from app.core.exceptions import CustomException
|
from app.core.exceptions import CustomException
|
||||||
from app.core.logger import logger
|
from app.core.logger import logger
|
||||||
from app.modules.workflow.core.base import BaseStorageAdapter, FtpAdvancedConfig, StorageObject, StorageProtocol
|
from app.modules.task.storage.core.base import BaseStorageAdapter, FtpAdvancedConfig, StorageObject, StorageProtocol
|
||||||
|
|
||||||
|
|
||||||
class _ImplicitFTP_TLS(ftplib.FTP_TLS):
|
class _ImplicitFTP_TLS(ftplib.FTP_TLS):
|
||||||
+1
-1
@@ -4,7 +4,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
from app.core.exceptions import CustomException
|
from app.core.exceptions import CustomException
|
||||||
from app.core.logger import logger
|
from app.core.logger import logger
|
||||||
from app.modules.workflow.core.base import BaseStorageAdapter, StorageObject, StorageProtocol
|
from app.modules.task.storage.core.base import BaseStorageAdapter, StorageObject, StorageProtocol
|
||||||
|
|
||||||
|
|
||||||
class LocalStorageAdapter(BaseStorageAdapter):
|
class LocalStorageAdapter(BaseStorageAdapter):
|
||||||
+1
-1
@@ -5,7 +5,7 @@ from obs import ObsClient
|
|||||||
|
|
||||||
from app.core.exceptions import CustomException
|
from app.core.exceptions import CustomException
|
||||||
from app.core.logger import logger
|
from app.core.logger import logger
|
||||||
from app.modules.workflow.core.base import (
|
from app.modules.task.storage.core.base import (
|
||||||
BaseStorageAdapter,
|
BaseStorageAdapter,
|
||||||
ObsAdvancedConfig,
|
ObsAdvancedConfig,
|
||||||
StorageObject,
|
StorageObject,
|
||||||
+1
-1
@@ -4,7 +4,7 @@ import alibabacloud_oss_v2 as oss
|
|||||||
|
|
||||||
from app.core.exceptions import CustomException
|
from app.core.exceptions import CustomException
|
||||||
from app.core.logger import logger
|
from app.core.logger import logger
|
||||||
from app.modules.workflow.core.base import (
|
from app.modules.task.storage.core.base import (
|
||||||
BaseStorageAdapter,
|
BaseStorageAdapter,
|
||||||
OssAdvancedConfig,
|
OssAdvancedConfig,
|
||||||
StorageObject,
|
StorageObject,
|
||||||
+1
-1
@@ -7,7 +7,7 @@ from botocore.exceptions import ClientError
|
|||||||
|
|
||||||
from app.core.exceptions import CustomException
|
from app.core.exceptions import CustomException
|
||||||
from app.core.logger import logger
|
from app.core.logger import logger
|
||||||
from app.modules.workflow.core.base import (
|
from app.modules.task.storage.core.base import (
|
||||||
BaseStorageAdapter,
|
BaseStorageAdapter,
|
||||||
S3AdvancedConfig,
|
S3AdvancedConfig,
|
||||||
StorageObject,
|
StorageObject,
|
||||||
+1
-1
@@ -13,7 +13,7 @@ import paramiko
|
|||||||
|
|
||||||
from app.core.exceptions import CustomException
|
from app.core.exceptions import CustomException
|
||||||
from app.core.logger import logger
|
from app.core.logger import logger
|
||||||
from app.modules.workflow.core.base import BaseStorageAdapter, SftpAdvancedConfig, StorageObject, StorageProtocol
|
from app.modules.task.storage.core.base import BaseStorageAdapter, SftpAdvancedConfig, StorageObject, StorageProtocol
|
||||||
|
|
||||||
|
|
||||||
class SftpStorageAdapter(BaseStorageAdapter):
|
class SftpStorageAdapter(BaseStorageAdapter):
|
||||||
+37
-37
@@ -8,7 +8,7 @@ from app.common.response import ResponseSchema, SuccessResponse
|
|||||||
from app.core.base_schema import AuthSchema, PageResultSchema, PaginationQueryParam
|
from app.core.base_schema import AuthSchema, PageResultSchema, PaginationQueryParam
|
||||||
from app.core.dependencies import AuthPermission, db_getter
|
from app.core.dependencies import AuthPermission, db_getter
|
||||||
from app.core.router_class import OperationLogRoute
|
from app.core.router_class import OperationLogRoute
|
||||||
from app.modules.workflow.core.base import (
|
from app.modules.task.storage.core.base import (
|
||||||
ADVANCED_FIELD_DEFS,
|
ADVANCED_FIELD_DEFS,
|
||||||
DEFAULT_PORTS,
|
DEFAULT_PORTS,
|
||||||
AdvancedFieldDefSchema,
|
AdvancedFieldDefSchema,
|
||||||
@@ -16,15 +16,15 @@ from app.modules.workflow.core.base import (
|
|||||||
StorageProtocolDefSchema,
|
StorageProtocolDefSchema,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .schema import StorageSourceCreateSchema, StorageSourceOutSchema, StorageSourceQueryParam, StorageSourceTestSchema, StorageSourceUpdateSchema
|
from .schema import StorageNodeCreateSchema, StorageNodeOutSchema, StorageNodeQueryParam, StorageNodeTestSchema, StorageNodeUpdateSchema
|
||||||
from .service import StorageSourceService
|
from .service import StorageNodeService
|
||||||
|
|
||||||
StorageSourceRouter = APIRouter(route_class=OperationLogRoute, prefix="/source", tags=["存储源管理"])
|
StorageNodeRouter = APIRouter(route_class=OperationLogRoute, prefix="/storage/node", tags=["节点管理"])
|
||||||
|
|
||||||
|
|
||||||
@StorageSourceRouter.get("/protocols", summary="查询支持的存储协议", response_model=ResponseSchema[list[StorageProtocolDefSchema]])
|
@StorageNodeRouter.get("/protocols", summary="查询支持的存储协议", response_model=ResponseSchema[list[StorageProtocolDefSchema]])
|
||||||
async def get_storage_protocols_controller(
|
async def get_storage_protocols_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:node:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:node:query"]))],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result: list[StorageProtocolDefSchema] = [
|
result: list[StorageProtocolDefSchema] = [
|
||||||
StorageProtocolDefSchema(protocol=p.value, name=p.name, default_port=DEFAULT_PORTS[p])
|
StorageProtocolDefSchema(protocol=p.value, name=p.name, default_port=DEFAULT_PORTS[p])
|
||||||
@@ -33,22 +33,22 @@ async def get_storage_protocols_controller(
|
|||||||
return SuccessResponse(data=result, msg="查询存储协议成功")
|
return SuccessResponse(data=result, msg="查询存储协议成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageSourceRouter.get("/advanced-fields", summary="查询存储 SDK 高级配置字段定义", response_model=ResponseSchema[dict[str, list[AdvancedFieldDefSchema]]])
|
@StorageNodeRouter.get("/advanced-fields", summary="查询存储 SDK 高级配置字段定义", response_model=ResponseSchema[dict[str, list[AdvancedFieldDefSchema]]])
|
||||||
async def get_advanced_fields_controller(
|
async def get_advanced_fields_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:node:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:node:query"]))],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
"""返回按协议分组的 SDK 高级配置字段元数据,供前端「高级设置」面板按协议动态渲染。"""
|
"""返回按协议分组的 SDK 高级配置字段元数据,供前端「高级设置」面板按协议动态渲染。"""
|
||||||
return SuccessResponse(data=ADVANCED_FIELD_DEFS, msg="查询高级配置字段成功")
|
return SuccessResponse(data=ADVANCED_FIELD_DEFS, msg="查询高级配置字段成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageSourceRouter.get("/page", summary="分页查询存储源", response_model=ResponseSchema[PageResultSchema[StorageSourceOutSchema]])
|
@StorageNodeRouter.get("/page", summary="分页查询存储源", response_model=ResponseSchema[PageResultSchema[StorageNodeOutSchema]])
|
||||||
async def get_storage_source_page_controller(
|
async def get_storage_source_page_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:node:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:node:query"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
page: Annotated[PaginationQueryParam, Depends()],
|
page: Annotated[PaginationQueryParam, Depends()],
|
||||||
search: Annotated[StorageSourceQueryParam, Query()],
|
search: Annotated[StorageNodeQueryParam, Query()],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result: PageResultSchema[StorageSourceOutSchema] = await StorageSourceService(auth, db).page(
|
result: PageResultSchema[StorageNodeOutSchema] = await StorageNodeService(auth, db).page(
|
||||||
search=search,
|
search=search,
|
||||||
page_no=page.page_no,
|
page_no=page.page_no,
|
||||||
page_size=page.page_size,
|
page_size=page.page_size,
|
||||||
@@ -57,72 +57,72 @@ async def get_storage_source_page_controller(
|
|||||||
return SuccessResponse(data=result, msg="查询存储源分页成功")
|
return SuccessResponse(data=result, msg="查询存储源分页成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageSourceRouter.get("/list", summary="查询存储源列表", response_model=ResponseSchema[list[StorageSourceOutSchema]])
|
@StorageNodeRouter.get("/list", summary="查询存储源列表", response_model=ResponseSchema[list[StorageNodeOutSchema]])
|
||||||
async def get_storage_source_list_controller(
|
async def get_storage_source_list_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:node:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:node:query"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
search: Annotated[StorageSourceQueryParam, Query()],
|
search: Annotated[StorageNodeQueryParam, Query()],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result: list[StorageSourceOutSchema] = await StorageSourceService(auth, db).get_list(search=search)
|
result: list[StorageNodeOutSchema] = await StorageNodeService(auth, db).get_list(search=search)
|
||||||
return SuccessResponse(data=result, msg="查询存储源列表成功")
|
return SuccessResponse(data=result, msg="查询存储源列表成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageSourceRouter.get("/detail/{id}", summary="查询存储源详情", response_model=ResponseSchema[StorageSourceOutSchema])
|
@StorageNodeRouter.get("/detail/{id}", summary="查询存储源详情", response_model=ResponseSchema[StorageNodeOutSchema])
|
||||||
async def get_storage_source_detail_controller(
|
async def get_storage_source_detail_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:node:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:node:query"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
id: Annotated[int, Path(description="存储源ID", ge=1)],
|
id: Annotated[int, Path(description="存储源ID", ge=1)],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result: StorageSourceOutSchema = await StorageSourceService(auth, db).detail(id=id)
|
result: StorageNodeOutSchema = await StorageNodeService(auth, db).detail(id=id)
|
||||||
return SuccessResponse(data=result, msg="查询存储源详情成功")
|
return SuccessResponse(data=result, msg="查询存储源详情成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageSourceRouter.post("/create", status_code=status.HTTP_201_CREATED, summary="创建存储源", response_model=ResponseSchema[StorageSourceOutSchema])
|
@StorageNodeRouter.post("/create", status_code=status.HTTP_201_CREATED, summary="创建存储源", response_model=ResponseSchema[StorageNodeOutSchema])
|
||||||
async def create_storage_source_controller(
|
async def create_storage_source_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:node:create"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:node:create"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
data: Annotated[StorageSourceCreateSchema, Body(description="存储源创建参数")],
|
data: Annotated[StorageNodeCreateSchema, Body(description="存储源创建参数")],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result: StorageSourceOutSchema = await StorageSourceService(auth, db).create(data=data)
|
result: StorageNodeOutSchema = await StorageNodeService(auth, db).create(data=data)
|
||||||
return SuccessResponse(data=result, msg="创建存储源成功")
|
return SuccessResponse(data=result, msg="创建存储源成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageSourceRouter.put("/update/{id}", summary="修改存储源", response_model=ResponseSchema[StorageSourceOutSchema])
|
@StorageNodeRouter.put("/update/{id}", summary="修改存储源", response_model=ResponseSchema[StorageNodeOutSchema])
|
||||||
async def update_storage_source_controller(
|
async def update_storage_source_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:node:update"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:node:update"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
id: Annotated[int, Path(description="存储源ID", ge=1)],
|
id: Annotated[int, Path(description="存储源ID", ge=1)],
|
||||||
data: Annotated[StorageSourceUpdateSchema, Body(description="存储源修改参数")],
|
data: Annotated[StorageNodeUpdateSchema, Body(description="存储源修改参数")],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result: StorageSourceOutSchema = await StorageSourceService(auth, db).update(id=id, data=data)
|
result: StorageNodeOutSchema = await StorageNodeService(auth, db).update(id=id, data=data)
|
||||||
return SuccessResponse(data=result, msg="修改存储源成功")
|
return SuccessResponse(data=result, msg="修改存储源成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageSourceRouter.delete("/delete", summary="删除存储源", response_model=ResponseSchema[None])
|
@StorageNodeRouter.delete("/delete", summary="删除存储源", response_model=ResponseSchema[None])
|
||||||
async def delete_storage_source_controller(
|
async def delete_storage_source_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:node:delete"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:node:delete"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
ids: Annotated[list[int], Body(description="存储源ID列表")],
|
ids: Annotated[list[int], Body(description="存储源ID列表")],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
await StorageSourceService(auth, db).delete(ids=ids)
|
await StorageNodeService(auth, db).delete(ids=ids)
|
||||||
return SuccessResponse(msg="删除存储源成功")
|
return SuccessResponse(msg="删除存储源成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageSourceRouter.post("/test/{id}", summary="测试存储源连接", response_model=ResponseSchema[bool])
|
@StorageNodeRouter.post("/test/{id}", summary="测试存储源连接", response_model=ResponseSchema[bool])
|
||||||
async def test_storage_source_controller(
|
async def test_storage_source_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:node:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:node:query"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
id: Annotated[int, Path(description="存储源ID", ge=1)],
|
id: Annotated[int, Path(description="存储源ID", ge=1)],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result: bool = await StorageSourceService(auth, db).test_connection(id=id)
|
result: bool = await StorageNodeService(auth, db).test_connection(id=id)
|
||||||
return SuccessResponse(data=result, msg="连接成功")
|
return SuccessResponse(data=result, msg="连接成功")
|
||||||
|
|
||||||
|
|
||||||
@StorageSourceRouter.post("/test", summary="测试存储源连接(配置)", response_model=ResponseSchema[bool])
|
@StorageNodeRouter.post("/test", summary="测试存储源连接(配置)", response_model=ResponseSchema[bool])
|
||||||
async def test_storage_source_config_controller(
|
async def test_storage_source_config_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:node:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:node:query"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
data: Annotated[StorageSourceTestSchema, Body(description="存储源连接配置")],
|
data: Annotated[StorageNodeTestSchema, Body(description="存储源连接配置")],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result: bool = await StorageSourceService(auth, db).test_config(data=data)
|
result: bool = await StorageNodeService(auth, db).test_config(data=data)
|
||||||
return SuccessResponse(data=result, msg="连接成功")
|
return SuccessResponse(data=result, msg="连接成功")
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from app.core.base_crud import CRUDBase
|
||||||
|
from app.core.base_schema import AuthSchema
|
||||||
|
|
||||||
|
from .model import StorageNodeModel
|
||||||
|
from .schema import StorageNodeCreateSchema, StorageNodeUpdateSchema
|
||||||
|
|
||||||
|
|
||||||
|
class StorageNodeCRUD(CRUDBase[StorageNodeModel, StorageNodeCreateSchema, StorageNodeUpdateSchema]):
|
||||||
|
"""存储源模块数据层"""
|
||||||
|
|
||||||
|
def __init__(self, auth: AuthSchema, db: AsyncSession) -> None:
|
||||||
|
super().__init__(model=StorageNodeModel, auth=auth, db=db)
|
||||||
+2
-2
@@ -4,10 +4,10 @@ from sqlalchemy.orm import Mapped, mapped_column
|
|||||||
from app.core.base_model import ModelMixin, UserMixin
|
from app.core.base_model import ModelMixin, UserMixin
|
||||||
|
|
||||||
|
|
||||||
class StorageSourceModel(ModelMixin, UserMixin):
|
class StorageNodeModel(ModelMixin, UserMixin):
|
||||||
"""存储源配置模型"""
|
"""存储源配置模型"""
|
||||||
|
|
||||||
__tablename__: str = "task_workflow_storage_source"
|
__tablename__: str = "task_storage_node"
|
||||||
__table_args__: dict[str, str] = {"comment": "存储源配置表"}
|
__table_args__: dict[str, str] = {"comment": "存储源配置表"}
|
||||||
|
|
||||||
name: Mapped[str] = mapped_column(String(64), unique=True, nullable=False, index=True, comment="存储源名称")
|
name: Mapped[str] = mapped_column(String(64), unique=True, nullable=False, index=True, comment="存储源名称")
|
||||||
+8
-8
@@ -3,10 +3,10 @@ import codecs
|
|||||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||||
|
|
||||||
from app.core.base_schema import BaseQueryParam, BaseSchema, UserByQueryParam, UserBySchema
|
from app.core.base_schema import BaseQueryParam, BaseSchema, UserByQueryParam, UserBySchema
|
||||||
from app.modules.workflow.core.base import DEFAULT_PORTS, StorageProtocol
|
from app.modules.task.storage.core.base import DEFAULT_PORTS, StorageProtocol
|
||||||
|
|
||||||
|
|
||||||
class StorageSourceConfigSchema(BaseModel):
|
class StorageNodeConfigSchema(BaseModel):
|
||||||
"""存储源连接配置模型(创建/测试共用)"""
|
"""存储源连接配置模型(创建/测试共用)"""
|
||||||
|
|
||||||
protocol: StorageProtocol = Field(..., description="协议(ftp/ftps/sftp/s3/obs/oss/cos/local)")
|
protocol: StorageProtocol = Field(..., description="协议(ftp/ftps/sftp/s3/obs/oss/cos/local)")
|
||||||
@@ -108,7 +108,7 @@ class StorageSourceConfigSchema(BaseModel):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class StorageSourceCreateSchema(StorageSourceConfigSchema):
|
class StorageNodeCreateSchema(StorageNodeConfigSchema):
|
||||||
"""存储源创建模型"""
|
"""存储源创建模型"""
|
||||||
|
|
||||||
name: str = Field(..., min_length=1, max_length=64, description="存储源名称")
|
name: str = Field(..., min_length=1, max_length=64, description="存储源名称")
|
||||||
@@ -125,17 +125,17 @@ class StorageSourceCreateSchema(StorageSourceConfigSchema):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class StorageSourceUpdateSchema(StorageSourceCreateSchema):
|
class StorageNodeUpdateSchema(StorageNodeCreateSchema):
|
||||||
"""存储源更新模型(password 为空表示不修改原密码)"""
|
"""存储源更新模型(password 为空表示不修改原密码)"""
|
||||||
|
|
||||||
|
|
||||||
class StorageSourceTestSchema(StorageSourceConfigSchema):
|
class StorageNodeTestSchema(StorageNodeConfigSchema):
|
||||||
"""存储源连接测试模型(仅校验连接配置,不落库;密码留空且传 source_id 时回退已保存密码)"""
|
"""存储源连接测试模型(仅校验连接配置,不落库;密码留空且传 source_id 时回退已保存密码)"""
|
||||||
|
|
||||||
source_id: int | None = Field(default=None, ge=1, description="已保存的存储源ID(编辑态测试时使用)")
|
node_id: int | None = Field(default=None, ge=1, description="已保存的存储源ID(编辑态测试时使用)")
|
||||||
|
|
||||||
|
|
||||||
class StorageSourceOutSchema(StorageSourceCreateSchema, BaseSchema, UserBySchema):
|
class StorageNodeOutSchema(StorageNodeCreateSchema, BaseSchema, UserBySchema):
|
||||||
"""存储源详情响应模型(密码永不明文返回)"""
|
"""存储源详情响应模型(密码永不明文返回)"""
|
||||||
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
@@ -149,7 +149,7 @@ class StorageSourceOutSchema(StorageSourceCreateSchema, BaseSchema, UserBySchema
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class StorageSourceQueryParam(BaseQueryParam, UserByQueryParam):
|
class StorageNodeQueryParam(BaseQueryParam, UserByQueryParam):
|
||||||
"""存储源管理查询参数"""
|
"""存储源管理查询参数"""
|
||||||
|
|
||||||
name: str | None = Field(None, description="存储源名称", json_schema_extra={"q": "like"})
|
name: str | None = Field(None, description="存储源名称", json_schema_extra={"q": "like"})
|
||||||
+27
-27
@@ -5,22 +5,22 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
|
|
||||||
from app.core.base_schema import AuthSchema, PageResultSchema
|
from app.core.base_schema import AuthSchema, PageResultSchema
|
||||||
from app.core.exceptions import CustomException
|
from app.core.exceptions import CustomException
|
||||||
from app.modules.workflow.core.base import (
|
from app.modules.task.storage.core.base import (
|
||||||
DEFAULT_PORTS,
|
DEFAULT_PORTS,
|
||||||
StorageAdapterConfig,
|
StorageAdapterConfig,
|
||||||
StorageProtocol,
|
StorageProtocol,
|
||||||
decrypt_password,
|
decrypt_password,
|
||||||
encrypt_password,
|
encrypt_password,
|
||||||
)
|
)
|
||||||
from app.modules.workflow.core.factory import StorageAdapterFactory
|
from app.modules.task.storage.core.factory import StorageAdapterFactory
|
||||||
from app.utils.common_util import search_to_dict
|
from app.utils.common_util import search_to_dict
|
||||||
|
|
||||||
from .crud import StorageSourceCRUD
|
from .crud import StorageNodeCRUD
|
||||||
from .model import StorageSourceModel
|
from .model import StorageNodeModel
|
||||||
from .schema import StorageSourceCreateSchema, StorageSourceOutSchema, StorageSourceQueryParam, StorageSourceTestSchema, StorageSourceUpdateSchema
|
from .schema import StorageNodeCreateSchema, StorageNodeOutSchema, StorageNodeQueryParam, StorageNodeTestSchema, StorageNodeUpdateSchema
|
||||||
|
|
||||||
|
|
||||||
class StorageSourceService:
|
class StorageNodeService:
|
||||||
"""存储源管理服务"""
|
"""存储源管理服务"""
|
||||||
|
|
||||||
def __init__(self, auth: AuthSchema, db: AsyncSession) -> None:
|
def __init__(self, auth: AuthSchema, db: AsyncSession) -> None:
|
||||||
@@ -29,21 +29,21 @@ class StorageSourceService:
|
|||||||
|
|
||||||
# ── 内部工具 ────────────────────────────────────────────────────
|
# ── 内部工具 ────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _crud(self) -> StorageSourceCRUD:
|
def _crud(self) -> StorageNodeCRUD:
|
||||||
return StorageSourceCRUD(self.auth, self.db)
|
return StorageNodeCRUD(self.auth, self.db)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _to_out(obj: StorageSourceModel) -> StorageSourceOutSchema:
|
def _to_out(obj: StorageNodeModel) -> StorageNodeOutSchema:
|
||||||
out = StorageSourceOutSchema.model_validate(obj)
|
out = StorageNodeOutSchema.model_validate(obj)
|
||||||
out.has_password = bool(obj.password)
|
out.has_password = bool(obj.password)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
async def _clear_other_default(self, keep_id: int | None = None) -> None:
|
async def _clear_other_default(self, keep_id: int | None = None) -> None:
|
||||||
"""取消其他存储源的默认标记,保证同时只有一个默认源。"""
|
"""取消其他存储源的默认标记,保证同时只有一个默认源。"""
|
||||||
conditions: list[ColumnElement[bool]] = [StorageSourceModel.is_default.is_(True)]
|
conditions: list[ColumnElement[bool]] = [StorageNodeModel.is_default.is_(True)]
|
||||||
if keep_id is not None:
|
if keep_id is not None:
|
||||||
conditions.append(StorageSourceModel.id != keep_id)
|
conditions.append(StorageNodeModel.id != keep_id)
|
||||||
await self.db.execute(update(StorageSourceModel).where(*conditions).values(is_default=False))
|
await self.db.execute(update(StorageNodeModel).where(*conditions).values(is_default=False))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _build_config_from(source: Any, password: str) -> StorageAdapterConfig:
|
def _build_config_from(source: Any, password: str) -> StorageAdapterConfig:
|
||||||
@@ -71,29 +71,29 @@ class StorageSourceService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _build_config(obj: StorageSourceModel) -> StorageAdapterConfig:
|
def _build_config(obj: StorageNodeModel) -> StorageAdapterConfig:
|
||||||
return StorageSourceService._build_config_from(obj, decrypt_password(obj.password))
|
return StorageNodeService._build_config_from(obj, decrypt_password(obj.password))
|
||||||
|
|
||||||
# ── 查询 ────────────────────────────────────────────────────────
|
# ── 查询 ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
async def detail(self, id: int) -> StorageSourceOutSchema:
|
async def detail(self, id: int) -> StorageNodeOutSchema:
|
||||||
obj = await self._crud().get_or_404(id=id)
|
obj = await self._crud().get_or_404(id=id)
|
||||||
return self._to_out(obj)
|
return self._to_out(obj)
|
||||||
|
|
||||||
async def page(
|
async def page(
|
||||||
self,
|
self,
|
||||||
search: StorageSourceQueryParam | None,
|
search: StorageNodeQueryParam | None,
|
||||||
page_no: int,
|
page_no: int,
|
||||||
page_size: int,
|
page_size: int,
|
||||||
order_by: list[dict] | None = None,
|
order_by: list[dict] | None = None,
|
||||||
) -> PageResultSchema[StorageSourceOutSchema]:
|
) -> PageResultSchema[StorageNodeOutSchema]:
|
||||||
result = await self._crud().page(
|
result = await self._crud().page(
|
||||||
offset=(page_no - 1) * page_size,
|
offset=(page_no - 1) * page_size,
|
||||||
limit=page_size,
|
limit=page_size,
|
||||||
order_by=order_by or [{"id": "asc"}],
|
order_by=order_by or [{"id": "asc"}],
|
||||||
search=search_to_dict(search),
|
search=search_to_dict(search),
|
||||||
)
|
)
|
||||||
return PageResultSchema[StorageSourceOutSchema](
|
return PageResultSchema[StorageNodeOutSchema](
|
||||||
page_no=result.page_no,
|
page_no=result.page_no,
|
||||||
page_size=result.page_size,
|
page_size=result.page_size,
|
||||||
total=result.total,
|
total=result.total,
|
||||||
@@ -101,13 +101,13 @@ class StorageSourceService:
|
|||||||
items=[self._to_out(obj) for obj in result.items],
|
items=[self._to_out(obj) for obj in result.items],
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_list(self, search: StorageSourceQueryParam | None = None) -> list[StorageSourceOutSchema]:
|
async def get_list(self, search: StorageNodeQueryParam | None = None) -> list[StorageNodeOutSchema]:
|
||||||
objs = await self._crud().get_list(search=search_to_dict(search), order_by=[{"id": "asc"}])
|
objs = await self._crud().get_list(search=search_to_dict(search), order_by=[{"id": "asc"}])
|
||||||
return [self._to_out(obj) for obj in objs]
|
return [self._to_out(obj) for obj in objs]
|
||||||
|
|
||||||
# ── 写入 ────────────────────────────────────────────────────────
|
# ── 写入 ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
async def create(self, data: StorageSourceCreateSchema) -> StorageSourceOutSchema:
|
async def create(self, data: StorageNodeCreateSchema) -> StorageNodeOutSchema:
|
||||||
exist = await self._crud().get(name=data.name)
|
exist = await self._crud().get(name=data.name)
|
||||||
if exist:
|
if exist:
|
||||||
raise CustomException(msg="创建失败,存储源名称已存在")
|
raise CustomException(msg="创建失败,存储源名称已存在")
|
||||||
@@ -121,7 +121,7 @@ class StorageSourceService:
|
|||||||
await self._clear_other_default(keep_id=obj.id)
|
await self._clear_other_default(keep_id=obj.id)
|
||||||
return self._to_out(obj)
|
return self._to_out(obj)
|
||||||
|
|
||||||
async def update(self, id: int, data: StorageSourceUpdateSchema) -> StorageSourceOutSchema:
|
async def update(self, id: int, data: StorageNodeUpdateSchema) -> StorageNodeOutSchema:
|
||||||
await self._crud().get_or_404(id=id, msg="更新失败,该存储源不存在")
|
await self._crud().get_or_404(id=id, msg="更新失败,该存储源不存在")
|
||||||
exist = await self._crud().get(name=data.name)
|
exist = await self._crud().get(name=data.name)
|
||||||
if exist and exist.id != id:
|
if exist and exist.id != id:
|
||||||
@@ -158,11 +158,11 @@ class StorageSourceService:
|
|||||||
raise CustomException(msg="连接失败,请检查存储源配置")
|
raise CustomException(msg="连接失败,请检查存储源配置")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def test_config(self, data: StorageSourceTestSchema) -> bool:
|
async def test_config(self, data: StorageNodeTestSchema) -> bool:
|
||||||
"""使用表单提交的配置直接测试连接(不落库),密码留空且传 source_id 时回退已保存密码。"""
|
"""使用表单提交的配置直接测试连接(不落库),密码留空且传 source_id 时回退已保存密码。"""
|
||||||
password = data.password or ""
|
password = data.password or ""
|
||||||
if not password and data.source_id:
|
if not password and data.node_id:
|
||||||
obj = await self._crud().get_or_404(id=data.source_id, msg="该存储源不存在")
|
obj = await self._crud().get_or_404(id=data.node_id, msg="该存储源不存在")
|
||||||
password = decrypt_password(obj.password)
|
password = decrypt_password(obj.password)
|
||||||
adapter = StorageAdapterFactory.create(self._build_config_from(data, password))
|
adapter = StorageAdapterFactory.create(self._build_config_from(data, password))
|
||||||
try:
|
try:
|
||||||
@@ -175,7 +175,7 @@ class StorageSourceService:
|
|||||||
|
|
||||||
# ── 供文件模块复用 ──────────────────────────────────────────────
|
# ── 供文件模块复用 ──────────────────────────────────────────────
|
||||||
|
|
||||||
async def get_active_source(self, source_id: int | None = None) -> StorageSourceModel:
|
async def get_active_source(self, source_id: int | None = None) -> StorageNodeModel:
|
||||||
"""获取可用存储源:优先指定 id;否则默认源;再退化为任一启用源。"""
|
"""获取可用存储源:优先指定 id;否则默认源;再退化为任一启用源。"""
|
||||||
if source_id:
|
if source_id:
|
||||||
source = await self._crud().get_or_404(id=source_id)
|
source = await self._crud().get_or_404(id=source_id)
|
||||||
@@ -190,7 +190,7 @@ class StorageSourceService:
|
|||||||
return source
|
return source
|
||||||
raise CustomException(msg="未配置可用的存储源,请先在存储源管理中创建")
|
raise CustomException(msg="未配置可用的存储源,请先在存储源管理中创建")
|
||||||
|
|
||||||
async def get_active_sources(self, ids: list[int]) -> list[StorageSourceModel]:
|
async def get_active_sources(self, ids: list[int]) -> list[StorageNodeModel]:
|
||||||
"""批量获取可用存储源:全部存在且启用,任一无效即抛错(一次查询,避免 N+1)。"""
|
"""批量获取可用存储源:全部存在且启用,任一无效即抛错(一次查询,避免 N+1)。"""
|
||||||
if not ids:
|
if not ids:
|
||||||
return []
|
return []
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# 见 docs/PLUGIN_ARCHITECTURE.md
|
||||||
|
|
||||||
|
name = "storage"
|
||||||
|
title = "存储传输"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "存储源管理、对象存储适配(OSS/COS/OBS/S3/SFTP/FTP)、文件浏览、传输任务与传输流程编排。"
|
||||||
|
optional = true
|
||||||
|
tags = ["storage", "transfer", "workflow"]
|
||||||
+10
-10
@@ -15,22 +15,22 @@ from app.core.exceptions import CustomException
|
|||||||
from app.core.logger import logger
|
from app.core.logger import logger
|
||||||
from app.core.router_class import OperationLogRoute
|
from app.core.router_class import OperationLogRoute
|
||||||
from app.core.sse_manager import SSE_QUEUE_MAX_SIZE
|
from app.core.sse_manager import SSE_QUEUE_MAX_SIZE
|
||||||
from app.modules.workflow.transfer.schema import (
|
from app.modules.task.storage.transfer.schema import (
|
||||||
TransferTaskCreateResultSchema,
|
TransferTaskCreateResultSchema,
|
||||||
TransferTaskCreateSchema,
|
TransferTaskCreateSchema,
|
||||||
TransferTaskOutSchema,
|
TransferTaskOutSchema,
|
||||||
TransferTaskQueryParam,
|
TransferTaskQueryParam,
|
||||||
TransferTaskType,
|
TransferTaskType,
|
||||||
)
|
)
|
||||||
from app.modules.workflow.transfer.service import StorageTransferService
|
from app.modules.task.storage.transfer.service import StorageTransferService
|
||||||
from app.modules.workflow.transfer.sse_manager import transfer_stream_manager
|
from app.modules.task.storage.transfer.sse_manager import transfer_stream_manager
|
||||||
|
|
||||||
StorageTransferRouter = APIRouter(route_class=OperationLogRoute, prefix="/transfer", tags=["文件传输"])
|
StorageTransferRouter = APIRouter(route_class=OperationLogRoute, prefix="/storage/transfer", tags=["文件传输"])
|
||||||
|
|
||||||
|
|
||||||
@StorageTransferRouter.post("/task", summary="创建传输任务(远端源)", response_model=ResponseSchema[TransferTaskCreateResultSchema])
|
@StorageTransferRouter.post("/task", summary="创建传输任务(远端源)", response_model=ResponseSchema[TransferTaskCreateResultSchema])
|
||||||
async def create_transfer_task_controller(
|
async def create_transfer_task_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:transfer:create"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:transfer:create"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
data: Annotated[TransferTaskCreateSchema, Body(description="任务参数(远端源)")],
|
data: Annotated[TransferTaskCreateSchema, Body(description="任务参数(远端源)")],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
@@ -40,7 +40,7 @@ async def create_transfer_task_controller(
|
|||||||
|
|
||||||
@StorageTransferRouter.post("/task/upload", summary="创建传输任务(本地上传源)", response_model=ResponseSchema[TransferTaskCreateResultSchema])
|
@StorageTransferRouter.post("/task/upload", summary="创建传输任务(本地上传源)", response_model=ResponseSchema[TransferTaskCreateResultSchema])
|
||||||
async def create_local_transfer_task_controller(
|
async def create_local_transfer_task_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:transfer:create"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:transfer:create"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
file: Annotated[UploadFile, File(description="本地源文件")],
|
file: Annotated[UploadFile, File(description="本地源文件")],
|
||||||
name: Annotated[str, Form(description="任务名称")],
|
name: Annotated[str, Form(description="任务名称")],
|
||||||
@@ -58,7 +58,7 @@ async def create_local_transfer_task_controller(
|
|||||||
|
|
||||||
@StorageTransferRouter.get("/task/page", summary="分页查询传输任务", response_model=ResponseSchema[PageResultSchema[TransferTaskOutSchema]])
|
@StorageTransferRouter.get("/task/page", summary="分页查询传输任务", response_model=ResponseSchema[PageResultSchema[TransferTaskOutSchema]])
|
||||||
async def get_transfer_task_page_controller(
|
async def get_transfer_task_page_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:transfer:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:transfer:query"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
page: Annotated[PaginationQueryParam, Depends()],
|
page: Annotated[PaginationQueryParam, Depends()],
|
||||||
search: Annotated[TransferTaskQueryParam, Query()],
|
search: Annotated[TransferTaskQueryParam, Query()],
|
||||||
@@ -74,7 +74,7 @@ async def get_transfer_task_page_controller(
|
|||||||
|
|
||||||
@StorageTransferRouter.get("/task/{id}", summary="查询传输任务详情", response_model=ResponseSchema[TransferTaskOutSchema])
|
@StorageTransferRouter.get("/task/{id}", summary="查询传输任务详情", response_model=ResponseSchema[TransferTaskOutSchema])
|
||||||
async def get_transfer_task_detail_controller(
|
async def get_transfer_task_detail_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:transfer:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:transfer:query"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
id: Annotated[int, Path(description="任务ID", ge=1)],
|
id: Annotated[int, Path(description="任务ID", ge=1)],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
@@ -84,7 +84,7 @@ async def get_transfer_task_detail_controller(
|
|||||||
|
|
||||||
@StorageTransferRouter.post("/task/{id}/cancel", summary="取消传输任务", response_model=ResponseSchema[None])
|
@StorageTransferRouter.post("/task/{id}/cancel", summary="取消传输任务", response_model=ResponseSchema[None])
|
||||||
async def cancel_transfer_task_controller(
|
async def cancel_transfer_task_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:transfer:update"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:transfer:update"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
id: Annotated[int, Path(description="任务ID", ge=1)],
|
id: Annotated[int, Path(description="任务ID", ge=1)],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
@@ -94,7 +94,7 @@ async def cancel_transfer_task_controller(
|
|||||||
|
|
||||||
@StorageTransferRouter.delete("/task", summary="删除传输任务", response_model=ResponseSchema[None])
|
@StorageTransferRouter.delete("/task", summary="删除传输任务", response_model=ResponseSchema[None])
|
||||||
async def delete_transfer_task_controller(
|
async def delete_transfer_task_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:transfer:delete"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:transfer:delete"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
ids: Annotated[list[int], Body(description="任务ID列表")],
|
ids: Annotated[list[int], Body(description="任务ID列表")],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
+8
-8
@@ -19,12 +19,12 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
|
|
||||||
from app.core.database import async_db_session
|
from app.core.database import async_db_session
|
||||||
from app.core.logger import logger
|
from app.core.logger import logger
|
||||||
from app.modules.workflow.core.base import StorageAdapterConfig
|
from app.modules.task.storage.core.base import StorageAdapterConfig
|
||||||
from app.modules.workflow.core.factory import StorageAdapterFactory
|
from app.modules.task.storage.core.factory import StorageAdapterFactory
|
||||||
from app.modules.workflow.source.model import StorageSourceModel
|
from app.modules.task.storage.node.model import StorageNodeModel
|
||||||
from app.modules.workflow.source.service import StorageSourceService
|
from app.modules.task.storage.node.service import StorageNodeService
|
||||||
from app.modules.workflow.transfer.registry import transfer_task_registry
|
from app.modules.task.storage.transfer.registry import transfer_task_registry
|
||||||
from app.modules.workflow.transfer.sse_manager import transfer_stream_manager
|
from app.modules.task.storage.transfer.sse_manager import transfer_stream_manager
|
||||||
|
|
||||||
from .model import StorageTransferStepModel, StorageTransferTaskModel
|
from .model import StorageTransferStepModel, StorageTransferTaskModel
|
||||||
from .schema import TransferMode, TransferStepOutSchema, TransferTaskOutSchema
|
from .schema import TransferMode, TransferStepOutSchema, TransferTaskOutSchema
|
||||||
@@ -56,10 +56,10 @@ async def _broadcast(task: StorageTransferTaskModel, steps: list[StorageTransfer
|
|||||||
|
|
||||||
async def _build_config(db: AsyncSession, source_id: int) -> StorageAdapterConfig | None:
|
async def _build_config(db: AsyncSession, source_id: int) -> StorageAdapterConfig | None:
|
||||||
"""获取存储源并构造适配器配置(复用存储源服务,含 SDK 高级配置);不存在或停用返回 None。"""
|
"""获取存储源并构造适配器配置(复用存储源服务,含 SDK 高级配置);不存在或停用返回 None。"""
|
||||||
source = await db.get(StorageSourceModel, source_id)
|
source = await db.get(StorageNodeModel, source_id)
|
||||||
if source is None or source.status == 1:
|
if source is None or source.status == 1:
|
||||||
return None
|
return None
|
||||||
return StorageSourceService._build_config(source)
|
return StorageNodeService._build_config(source)
|
||||||
|
|
||||||
|
|
||||||
def _remove_local_temp(task: StorageTransferTaskModel) -> None:
|
def _remove_local_temp(task: StorageTransferTaskModel) -> None:
|
||||||
+3
-3
@@ -9,7 +9,7 @@ from app.core.base_model import ModelMixin, UserMixin
|
|||||||
class StorageTransferTaskModel(ModelMixin, UserMixin):
|
class StorageTransferTaskModel(ModelMixin, UserMixin):
|
||||||
"""文件传输任务模型(多目标 / 链式)"""
|
"""文件传输任务模型(多目标 / 链式)"""
|
||||||
|
|
||||||
__tablename__: str = "task_workflow_transfer_task"
|
__tablename__: str = "task_storage_transfer"
|
||||||
__table_args__: dict[str, str] = {"comment": "文件传输任务表"}
|
__table_args__: dict[str, str] = {"comment": "文件传输任务表"}
|
||||||
|
|
||||||
name: Mapped[str] = mapped_column(String(128), nullable=False, comment="任务名称")
|
name: Mapped[str] = mapped_column(String(128), nullable=False, comment="任务名称")
|
||||||
@@ -32,11 +32,11 @@ class StorageTransferTaskModel(ModelMixin, UserMixin):
|
|||||||
class StorageTransferStepModel(ModelMixin):
|
class StorageTransferStepModel(ModelMixin):
|
||||||
"""文件传输步骤模型(一个任务展开为多个步骤)"""
|
"""文件传输步骤模型(一个任务展开为多个步骤)"""
|
||||||
|
|
||||||
__tablename__: str = "task_workflow_transfer_step"
|
__tablename__: str = "task_storage_transfer_step"
|
||||||
__table_args__: dict[str, str] = {"comment": "文件传输步骤表"}
|
__table_args__: dict[str, str] = {"comment": "文件传输步骤表"}
|
||||||
|
|
||||||
task_id: Mapped[int] = mapped_column(
|
task_id: Mapped[int] = mapped_column(
|
||||||
ForeignKey("task_workflow_transfer_task.id", ondelete="CASCADE"),
|
ForeignKey("task_storage_transfer.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
index=True,
|
index=True,
|
||||||
comment="任务ID",
|
comment="任务ID",
|
||||||
+5
-5
@@ -10,9 +10,9 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
|
|
||||||
from app.core.base_schema import AuthSchema, PageResultSchema
|
from app.core.base_schema import AuthSchema, PageResultSchema
|
||||||
from app.core.exceptions import CustomException
|
from app.core.exceptions import CustomException
|
||||||
from app.modules.workflow.source.service import StorageSourceService
|
from app.modules.task.storage.node.service import StorageNodeService
|
||||||
from app.modules.workflow.transfer.engine import _broadcast, execute_transfer_task
|
from app.modules.task.storage.transfer.engine import _broadcast, execute_transfer_task
|
||||||
from app.modules.workflow.transfer.registry import transfer_task_registry
|
from app.modules.task.storage.transfer.registry import transfer_task_registry
|
||||||
from app.utils.common_util import search_to_dict
|
from app.utils.common_util import search_to_dict
|
||||||
|
|
||||||
from .crud import StorageTransferTaskCRUD
|
from .crud import StorageTransferTaskCRUD
|
||||||
@@ -47,7 +47,7 @@ class StorageTransferService:
|
|||||||
|
|
||||||
async def _validate_targets(self, targets: list[TransferTargetSchema]) -> None:
|
async def _validate_targets(self, targets: list[TransferTargetSchema]) -> None:
|
||||||
"""校验目标存储源均存在且启用(批量一次查询)。"""
|
"""校验目标存储源均存在且启用(批量一次查询)。"""
|
||||||
source_service = StorageSourceService(self.auth, self.db)
|
source_service = StorageNodeService(self.auth, self.db)
|
||||||
await source_service.get_active_sources([t.target_id for t in targets])
|
await source_service.get_active_sources([t.target_id for t in targets])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -118,7 +118,7 @@ class StorageTransferService:
|
|||||||
|
|
||||||
async def create(self, data: TransferTaskCreateSchema) -> int:
|
async def create(self, data: TransferTaskCreateSchema) -> int:
|
||||||
"""创建远端源传输任务。"""
|
"""创建远端源传输任务。"""
|
||||||
source_service = StorageSourceService(self.auth, self.db)
|
source_service = StorageNodeService(self.auth, self.db)
|
||||||
if data.source_type == "remote":
|
if data.source_type == "remote":
|
||||||
await source_service.get_active_source(data.source_id)
|
await source_service.get_active_source(data.source_id)
|
||||||
await self._validate_targets(data.targets)
|
await self._validate_targets(data.targets)
|
||||||
+29
-29
@@ -8,20 +8,20 @@ from app.common.response import ResponseSchema, SuccessResponse
|
|||||||
from app.core.base_schema import AuthSchema, PageResultSchema, PaginationQueryParam
|
from app.core.base_schema import AuthSchema, PageResultSchema, PaginationQueryParam
|
||||||
from app.core.dependencies import AuthPermission, db_getter
|
from app.core.dependencies import AuthPermission, db_getter
|
||||||
from app.core.router_class import OperationLogRoute
|
from app.core.router_class import OperationLogRoute
|
||||||
from app.modules.workflow.flow.schema import WorkflowFlowCreateSchema, WorkflowFlowExecuteSchema, WorkflowFlowOutSchema, WorkflowFlowQueryParam, WorkflowFlowUpdateSchema
|
from app.modules.task.storage.workflow.schema import WorkflowCreateSchema, WorkflowExecuteSchema, WorkflowOutSchema, WorkflowQueryParam, WorkflowUpdateSchema
|
||||||
from app.modules.workflow.flow.service import WorkflowFlowService
|
from app.modules.task.storage.workflow.service import WorkflowService
|
||||||
|
|
||||||
WorkflowFlowRouter = APIRouter(route_class=OperationLogRoute, prefix="/flow", tags=["传输流程"])
|
StorageWorkflowRouter = APIRouter(route_class=OperationLogRoute, prefix="/storage/workflow", tags=["传输流程"])
|
||||||
|
|
||||||
|
|
||||||
@WorkflowFlowRouter.get("/page", summary="分页查询传输流程", response_model=ResponseSchema[PageResultSchema[WorkflowFlowOutSchema]])
|
@StorageWorkflowRouter.get("/page", summary="分页查询传输流程", response_model=ResponseSchema[PageResultSchema[WorkflowOutSchema]])
|
||||||
async def get_flow_page_controller(
|
async def get_flow_page_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:flow:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:flow:query"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
page: Annotated[PaginationQueryParam, Depends()],
|
page: Annotated[PaginationQueryParam, Depends()],
|
||||||
search: Annotated[WorkflowFlowQueryParam, Query()],
|
search: Annotated[WorkflowQueryParam, Query()],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result: PageResultSchema[WorkflowFlowOutSchema] = await WorkflowFlowService(auth, db).page(
|
result: PageResultSchema[WorkflowOutSchema] = await WorkflowService(auth, db).page(
|
||||||
search=search,
|
search=search,
|
||||||
page_no=page.page_no,
|
page_no=page.page_no,
|
||||||
page_size=page.page_size,
|
page_size=page.page_size,
|
||||||
@@ -30,65 +30,65 @@ async def get_flow_page_controller(
|
|||||||
return SuccessResponse(data=result, msg="查询传输流程分页成功")
|
return SuccessResponse(data=result, msg="查询传输流程分页成功")
|
||||||
|
|
||||||
|
|
||||||
@WorkflowFlowRouter.get("/list", summary="查询传输流程列表", response_model=ResponseSchema[list[WorkflowFlowOutSchema]])
|
@StorageWorkflowRouter.get("/list", summary="查询传输流程列表", response_model=ResponseSchema[list[WorkflowOutSchema]])
|
||||||
async def get_flow_list_controller(
|
async def get_flow_list_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:flow:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:flow:query"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
search: Annotated[WorkflowFlowQueryParam, Query()],
|
search: Annotated[WorkflowQueryParam, Query()],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result: list[WorkflowFlowOutSchema] = await WorkflowFlowService(auth, db).get_list(search=search)
|
result: list[WorkflowOutSchema] = await WorkflowService(auth, db).get_list(search=search)
|
||||||
return SuccessResponse(data=result, msg="查询传输流程列表成功")
|
return SuccessResponse(data=result, msg="查询传输流程列表成功")
|
||||||
|
|
||||||
|
|
||||||
@WorkflowFlowRouter.get("/detail/{id}", summary="查询传输流程详情", response_model=ResponseSchema[WorkflowFlowOutSchema])
|
@StorageWorkflowRouter.get("/detail/{id}", summary="查询传输流程详情", response_model=ResponseSchema[WorkflowOutSchema])
|
||||||
async def get_flow_detail_controller(
|
async def get_flow_detail_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:flow:query"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:flow:query"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
id: Annotated[int, Path(description="流程ID", ge=1)],
|
id: Annotated[int, Path(description="流程ID", ge=1)],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result: WorkflowFlowOutSchema = await WorkflowFlowService(auth, db).detail(id=id)
|
result: WorkflowOutSchema = await WorkflowService(auth, db).detail(id=id)
|
||||||
return SuccessResponse(data=result, msg="查询传输流程详情成功")
|
return SuccessResponse(data=result, msg="查询传输流程详情成功")
|
||||||
|
|
||||||
|
|
||||||
@WorkflowFlowRouter.post("/create", status_code=status.HTTP_201_CREATED, summary="创建传输流程", response_model=ResponseSchema[WorkflowFlowOutSchema])
|
@StorageWorkflowRouter.post("/create", status_code=status.HTTP_201_CREATED, summary="创建传输流程", response_model=ResponseSchema[WorkflowOutSchema])
|
||||||
async def create_flow_controller(
|
async def create_flow_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:flow:create"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:flow:create"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
data: Annotated[WorkflowFlowCreateSchema, Body(description="流程创建参数")],
|
data: Annotated[WorkflowCreateSchema, Body(description="流程创建参数")],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result: WorkflowFlowOutSchema = await WorkflowFlowService(auth, db).create(data=data)
|
result: WorkflowOutSchema = await WorkflowService(auth, db).create(data=data)
|
||||||
return SuccessResponse(data=result, msg="创建传输流程成功")
|
return SuccessResponse(data=result, msg="创建传输流程成功")
|
||||||
|
|
||||||
|
|
||||||
@WorkflowFlowRouter.put("/update/{id}", summary="修改传输流程", response_model=ResponseSchema[WorkflowFlowOutSchema])
|
@StorageWorkflowRouter.put("/update/{id}", summary="修改传输流程", response_model=ResponseSchema[WorkflowOutSchema])
|
||||||
async def update_flow_controller(
|
async def update_flow_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:flow:update"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:flow:update"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
id: Annotated[int, Path(description="流程ID", ge=1)],
|
id: Annotated[int, Path(description="流程ID", ge=1)],
|
||||||
data: Annotated[WorkflowFlowUpdateSchema, Body(description="流程修改参数")],
|
data: Annotated[WorkflowUpdateSchema, Body(description="流程修改参数")],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result: WorkflowFlowOutSchema = await WorkflowFlowService(auth, db).update(id=id, data=data)
|
result: WorkflowOutSchema = await WorkflowService(auth, db).update(id=id, data=data)
|
||||||
return SuccessResponse(data=result, msg="修改传输流程成功")
|
return SuccessResponse(data=result, msg="修改传输流程成功")
|
||||||
|
|
||||||
|
|
||||||
@WorkflowFlowRouter.delete("/delete", summary="删除传输流程", response_model=ResponseSchema[None])
|
@StorageWorkflowRouter.delete("/delete", summary="删除传输流程", response_model=ResponseSchema[None])
|
||||||
async def delete_flow_controller(
|
async def delete_flow_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:flow:delete"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:flow:delete"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
ids: Annotated[list[int], Body(description="流程ID列表")],
|
ids: Annotated[list[int], Body(description="流程ID列表")],
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
await WorkflowFlowService(auth, db).delete(ids=ids)
|
await WorkflowService(auth, db).delete(ids=ids)
|
||||||
return SuccessResponse(msg="删除传输流程成功")
|
return SuccessResponse(msg="删除传输流程成功")
|
||||||
|
|
||||||
|
|
||||||
@WorkflowFlowRouter.post("/execute/{id}", summary="执行传输流程", response_model=ResponseSchema[list[int]])
|
@StorageWorkflowRouter.post("/execute/{id}", summary="执行传输流程", response_model=ResponseSchema[list[int]])
|
||||||
async def execute_flow_controller(
|
async def execute_flow_controller(
|
||||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:transfer:create"]))],
|
auth: Annotated[AuthSchema, Security(AuthPermission(["module_storage:workflow:transfer:create"]))],
|
||||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||||
id: Annotated[int, Path(description="流程ID", ge=1)],
|
id: Annotated[int, Path(description="流程ID", ge=1)],
|
||||||
data: Annotated[WorkflowFlowExecuteSchema | None, Body(description="执行参数(源文件/目录路径映射,可选)")] = None,
|
data: Annotated[WorkflowExecuteSchema | None, Body(description="执行参数(源文件/目录路径映射,可选)")] = None,
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
task_ids: list[int] = await WorkflowFlowService(auth, db).execute(
|
task_ids: list[int] = await WorkflowService(auth, db).execute(
|
||||||
id=id, source_paths=data.source_paths if data else None
|
id=id, source_paths=data.source_paths if data else None
|
||||||
)
|
)
|
||||||
return SuccessResponse(data=task_ids, msg=f"执行传输流程成功,已生成 {len(task_ids)} 个传输任务")
|
return SuccessResponse(data=task_ids, msg=f"执行传输流程成功,已生成 {len(task_ids)} 个传输任务")
|
||||||
+11
-11
@@ -6,18 +6,18 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
from app.core.base_crud import CRUDBase
|
from app.core.base_crud import CRUDBase
|
||||||
from app.core.base_schema import AuthSchema
|
from app.core.base_schema import AuthSchema
|
||||||
|
|
||||||
from .model import WorkflowFlowEdgeModel, WorkflowFlowModel, WorkflowFlowNodeModel
|
from .model import WorkflowEdgeModel, WorkflowModel, WorkflowNodeModel
|
||||||
from .schema import WorkflowFlowCreateSchema, WorkflowFlowUpdateSchema
|
from .schema import WorkflowCreateSchema, WorkflowUpdateSchema
|
||||||
|
|
||||||
|
|
||||||
class WorkflowFlowCRUD(CRUDBase[WorkflowFlowModel, WorkflowFlowCreateSchema, WorkflowFlowUpdateSchema]):
|
class WorkflowCRUD(CRUDBase[WorkflowModel, WorkflowCreateSchema, WorkflowUpdateSchema]):
|
||||||
"""传输流程数据层"""
|
"""传输流程数据层"""
|
||||||
|
|
||||||
def __init__(self, auth: AuthSchema, db: AsyncSession) -> None:
|
def __init__(self, auth: AuthSchema, db: AsyncSession) -> None:
|
||||||
super().__init__(model=WorkflowFlowModel, auth=auth, db=db)
|
super().__init__(model=WorkflowModel, auth=auth, db=db)
|
||||||
|
|
||||||
|
|
||||||
class WorkflowFlowNodeCRUD(CRUDBase[WorkflowFlowNodeModel, object, object]):
|
class WorkflowNodeCRUD(CRUDBase[WorkflowNodeModel, object, object]):
|
||||||
"""流程节点明细数据层
|
"""流程节点明细数据层
|
||||||
|
|
||||||
明细是 flow 的派生从属数据:随父流程全量覆写/删除,无独立数据权限主体,
|
明细是 flow 的派生从属数据:随父流程全量覆写/删除,无独立数据权限主体,
|
||||||
@@ -26,23 +26,23 @@ class WorkflowFlowNodeCRUD(CRUDBase[WorkflowFlowNodeModel, object, object]):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, auth: AuthSchema, db: AsyncSession) -> None:
|
def __init__(self, auth: AuthSchema, db: AsyncSession) -> None:
|
||||||
super().__init__(model=WorkflowFlowNodeModel, auth=auth, db=db)
|
super().__init__(model=WorkflowNodeModel, auth=auth, db=db)
|
||||||
|
|
||||||
async def hard_delete_by_flow_ids(self, flow_ids: Sequence[int]) -> None:
|
async def hard_delete_by_flow_ids(self, flow_ids: Sequence[int]) -> None:
|
||||||
"""按 flow 物理删除明细(父流程行已过数据权限校验)。"""
|
"""按 flow 物理删除明细(父流程行已过数据权限校验)。"""
|
||||||
if not flow_ids:
|
if not flow_ids:
|
||||||
return
|
return
|
||||||
_ = await self.db.execute(delete(WorkflowFlowNodeModel).where(WorkflowFlowNodeModel.flow_id.in_(flow_ids)))
|
_ = await self.db.execute(delete(WorkflowNodeModel).where(WorkflowNodeModel.flow_id.in_(flow_ids)))
|
||||||
|
|
||||||
|
|
||||||
class WorkflowFlowEdgeCRUD(CRUDBase[WorkflowFlowEdgeModel, object, object]):
|
class WorkflowEdgeCRUD(CRUDBase[WorkflowEdgeModel, object, object]):
|
||||||
"""流程连线明细数据层,删除语义同 WorkflowFlowNodeCRUD"""
|
"""流程连线明细数据层,删除语义同 WorkflowNodeCRUD"""
|
||||||
|
|
||||||
def __init__(self, auth: AuthSchema, db: AsyncSession) -> None:
|
def __init__(self, auth: AuthSchema, db: AsyncSession) -> None:
|
||||||
super().__init__(model=WorkflowFlowEdgeModel, auth=auth, db=db)
|
super().__init__(model=WorkflowEdgeModel, auth=auth, db=db)
|
||||||
|
|
||||||
async def hard_delete_by_flow_ids(self, flow_ids: Sequence[int]) -> None:
|
async def hard_delete_by_flow_ids(self, flow_ids: Sequence[int]) -> None:
|
||||||
"""按 flow 物理删除明细(父流程行已过数据权限校验)。"""
|
"""按 flow 物理删除明细(父流程行已过数据权限校验)。"""
|
||||||
if not flow_ids:
|
if not flow_ids:
|
||||||
return
|
return
|
||||||
_ = await self.db.execute(delete(WorkflowFlowEdgeModel).where(WorkflowFlowEdgeModel.flow_id.in_(flow_ids)))
|
_ = await self.db.execute(delete(WorkflowEdgeModel).where(WorkflowEdgeModel.flow_id.in_(flow_ids)))
|
||||||
+6
-6
@@ -4,14 +4,14 @@ from sqlalchemy.orm import Mapped, mapped_column
|
|||||||
from app.core.base_model import ModelMixin, UserMixin
|
from app.core.base_model import ModelMixin, UserMixin
|
||||||
|
|
||||||
|
|
||||||
class WorkflowFlowModel(ModelMixin, UserMixin):
|
class WorkflowModel(ModelMixin, UserMixin):
|
||||||
"""传输流程:定义源节点 → 目标节点列表(parallel 多目标 / chain 链式)
|
"""传输流程:定义源节点 → 目标节点列表(parallel 多目标 / chain 链式)
|
||||||
|
|
||||||
graph 仅存画布布局与展示字段(节点位置、连线样式),业务配置落于
|
graph 仅存画布布局与展示字段(节点位置、连线样式),业务配置落于
|
||||||
flow_node / flow_edge 表,回显时由 service 组装,避免双份数据不一致。
|
flow_node / flow_edge 表,回显时由 service 组装,避免双份数据不一致。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__: str = "task_workflow_flow"
|
__tablename__: str = "task_storage_workflow"
|
||||||
__table_args__: dict[str, str] = {"comment": "传输流程定义表"}
|
__table_args__: dict[str, str] = {"comment": "传输流程定义表"}
|
||||||
|
|
||||||
name: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment="流程名称")
|
name: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment="流程名称")
|
||||||
@@ -21,13 +21,13 @@ class WorkflowFlowModel(ModelMixin, UserMixin):
|
|||||||
description: Mapped[str | None] = mapped_column(Text, default=None, nullable=True, comment="备注")
|
description: Mapped[str | None] = mapped_column(Text, default=None, nullable=True, comment="备注")
|
||||||
|
|
||||||
|
|
||||||
class WorkflowFlowNodeModel(ModelMixin, UserMixin):
|
class WorkflowNodeModel(ModelMixin, UserMixin):
|
||||||
"""流程画布节点(业务配置):节点关联的存储源与默认源目录。
|
"""流程画布节点(业务配置):节点关联的存储源与默认源目录。
|
||||||
|
|
||||||
节点在画布上的位置/名称等布局字段存 flow.graph 的 nodes 项(key=node_key)。
|
节点在画布上的位置/名称等布局字段存 flow.graph 的 nodes 项(key=node_key)。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__: str = "task_workflow_flow_node"
|
__tablename__: str = "task_storage_workflow_node"
|
||||||
__table_args__: dict[str, str] = {"comment": "流程画布节点表"}
|
__table_args__: dict[str, str] = {"comment": "流程画布节点表"}
|
||||||
|
|
||||||
flow_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True, comment="流程ID")
|
flow_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True, comment="流程ID")
|
||||||
@@ -36,14 +36,14 @@ class WorkflowFlowNodeModel(ModelMixin, UserMixin):
|
|||||||
source_path: Mapped[str | None] = mapped_column(String(1024), default=None, nullable=True, comment="默认源目录")
|
source_path: Mapped[str | None] = mapped_column(String(1024), default=None, nullable=True, comment="默认源目录")
|
||||||
|
|
||||||
|
|
||||||
class WorkflowFlowEdgeModel(ModelMixin, UserMixin):
|
class WorkflowEdgeModel(ModelMixin, UserMixin):
|
||||||
"""流程画布连线(业务配置):传输方式与分片参数。
|
"""流程画布连线(业务配置):传输方式与分片参数。
|
||||||
|
|
||||||
连线的目标目录由目标节点的默认源目录决定(节点 source_path),连线不再配置路径。
|
连线的目标目录由目标节点的默认源目录决定(节点 source_path),连线不再配置路径。
|
||||||
连线的样式/动画等展示字段存 flow.graph 的 edges 项(key=edge_key)。
|
连线的样式/动画等展示字段存 flow.graph 的 edges 项(key=edge_key)。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__: str = "task_workflow_flow_edge"
|
__tablename__: str = "task_storage_workflow_edge"
|
||||||
__table_args__: dict[str, str] = {"comment": "流程画布连线表"}
|
__table_args__: dict[str, str] = {"comment": "流程画布连线表"}
|
||||||
|
|
||||||
flow_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True, comment="流程ID")
|
flow_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True, comment="流程ID")
|
||||||
+25
-25
@@ -4,24 +4,24 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_valida
|
|||||||
|
|
||||||
from app.core.base_schema import BaseQueryParam, BaseSchema, UserByQueryParam, UserBySchema
|
from app.core.base_schema import BaseQueryParam, BaseSchema, UserByQueryParam, UserBySchema
|
||||||
|
|
||||||
FlowTaskType = Literal["parallel", "chain"]
|
WorkflowTaskType = Literal["parallel", "chain"]
|
||||||
|
|
||||||
|
|
||||||
class FlowTargetSchema(BaseModel):
|
class WorkflowTargetSchema(BaseModel):
|
||||||
"""流程目标节点配置(目标目录由目标节点默认源目录决定)"""
|
"""流程目标节点配置(目标目录由目标节点默认源目录决定)"""
|
||||||
|
|
||||||
target_id: int = Field(..., ge=1, description="目标节点ID(存储源)")
|
target_id: int = Field(..., ge=1, description="目标节点ID(存储源)")
|
||||||
target_path: str = Field(..., max_length=1024, description="目标路径")
|
target_path: str = Field(..., max_length=1024, description="目标路径")
|
||||||
|
|
||||||
|
|
||||||
class FlowSourceSchema(BaseModel):
|
class WorkflowSourceSchema(BaseModel):
|
||||||
"""流程源节点概览(由画布连线实时派生)"""
|
"""流程源节点概览(由画布连线实时派生)"""
|
||||||
|
|
||||||
source_id: int = Field(..., ge=1, description="源节点ID(存储源)")
|
source_id: int = Field(..., ge=1, description="源节点ID(存储源)")
|
||||||
source_name: str | None = Field(default=None, description="源存储源名称")
|
source_name: str | None = Field(default=None, description="源存储源名称")
|
||||||
|
|
||||||
|
|
||||||
class FlowNodeSchema(BaseModel):
|
class WorkflowNodeSchema(BaseModel):
|
||||||
"""画布节点业务配置(由画布拆分,落 flow_node 表)"""
|
"""画布节点业务配置(由画布拆分,落 flow_node 表)"""
|
||||||
|
|
||||||
node_key: str = Field(..., description="画布节点ID")
|
node_key: str = Field(..., description="画布节点ID")
|
||||||
@@ -29,7 +29,7 @@ class FlowNodeSchema(BaseModel):
|
|||||||
source_path: str | None = Field(default=None, max_length=1024, description="默认源目录")
|
source_path: str | None = Field(default=None, max_length=1024, description="默认源目录")
|
||||||
|
|
||||||
|
|
||||||
class FlowEdgeSchema(BaseModel):
|
class WorkflowEdgeSchema(BaseModel):
|
||||||
"""画布连线业务配置(由画布拆分,落 flow_edge 表)
|
"""画布连线业务配置(由画布拆分,落 flow_edge 表)
|
||||||
|
|
||||||
连线只定义传输方式;目标目录由目标节点的默认源目录(source_path)决定。
|
连线只定义传输方式;目标目录由目标节点的默认源目录(source_path)决定。
|
||||||
@@ -44,7 +44,7 @@ class FlowEdgeSchema(BaseModel):
|
|||||||
multipart_concurrency: int | None = Field(default=None, ge=1, description="分片并发数")
|
multipart_concurrency: int | None = Field(default=None, ge=1, description="分片并发数")
|
||||||
|
|
||||||
|
|
||||||
class FlowLayoutNodeSchema(BaseModel):
|
class WorkflowLayoutNodeSchema(BaseModel):
|
||||||
"""画布节点布局展示字段(存 flow.graph,业务配置在 flow_node 表)"""
|
"""画布节点布局展示字段(存 flow.graph,业务配置在 flow_node 表)"""
|
||||||
|
|
||||||
id: str = Field(..., description="画布节点ID")
|
id: str = Field(..., description="画布节点ID")
|
||||||
@@ -54,7 +54,7 @@ class FlowLayoutNodeSchema(BaseModel):
|
|||||||
style: dict | None = Field(default=None, description="节点样式")
|
style: dict | None = Field(default=None, description="节点样式")
|
||||||
|
|
||||||
|
|
||||||
class FlowLayoutEdgeSchema(BaseModel):
|
class WorkflowLayoutEdgeSchema(BaseModel):
|
||||||
"""画布连线布局展示字段(存 flow.graph,业务配置在 flow_edge 表)"""
|
"""画布连线布局展示字段(存 flow.graph,业务配置在 flow_edge 表)"""
|
||||||
|
|
||||||
id: str = Field(..., description="画布连线ID")
|
id: str = Field(..., description="画布连线ID")
|
||||||
@@ -66,7 +66,7 @@ class FlowLayoutEdgeSchema(BaseModel):
|
|||||||
label: str | None = Field(default=None, description="连线显示名")
|
label: str | None = Field(default=None, description="连线显示名")
|
||||||
|
|
||||||
|
|
||||||
class FlowGraphNodeDataSchema(BaseModel):
|
class WorkflowGraphNodeDataSchema(BaseModel):
|
||||||
"""画布节点回显数据(组装完整画布时写入 node.data)"""
|
"""画布节点回显数据(组装完整画布时写入 node.data)"""
|
||||||
|
|
||||||
source_id: int = Field(..., ge=1, description="存储源ID")
|
source_id: int = Field(..., ge=1, description="存储源ID")
|
||||||
@@ -80,7 +80,7 @@ class FlowGraphNodeDataSchema(BaseModel):
|
|||||||
path_prefix: str | None = Field(default=None, description="路径前缀")
|
path_prefix: str | None = Field(default=None, description="路径前缀")
|
||||||
|
|
||||||
|
|
||||||
class FlowGraphEdgeDataSchema(BaseModel):
|
class WorkflowGraphEdgeDataSchema(BaseModel):
|
||||||
"""画布连线回显数据(组装完整画布时写入 edge.data)"""
|
"""画布连线回显数据(组装完整画布时写入 edge.data)"""
|
||||||
|
|
||||||
enabled: bool = Field(default=True, description="是否启用(禁用则不执行)")
|
enabled: bool = Field(default=True, description="是否启用(禁用则不执行)")
|
||||||
@@ -95,17 +95,17 @@ class FlowGraphEdgeDataSchema(BaseModel):
|
|||||||
target_storage_id: int | None = Field(default=None, description="目标存储源ID")
|
target_storage_id: int | None = Field(default=None, description="目标存储源ID")
|
||||||
|
|
||||||
|
|
||||||
class FlowSplitResultSchema(BaseModel):
|
class WorkflowSplitResultSchema(BaseModel):
|
||||||
"""画布拆分结果:业务明细(node/edge)+ 精简布局 + 派生概览"""
|
"""画布拆分结果:业务明细(node/edge)+ 精简布局 + 派生概览"""
|
||||||
|
|
||||||
layout: dict = Field(..., description="精简后的画布布局 {nodes:[{id,type,position,label}],edges:[{id,source,target,type,animated,style,label}]}")
|
layout: dict = Field(..., description="精简后的画布布局 {nodes:[{id,type,position,label}],edges:[{id,source,target,type,animated,style,label}]}")
|
||||||
nodes: list[FlowNodeSchema] = Field(default_factory=list, description="节点业务明细")
|
nodes: list[WorkflowNodeSchema] = Field(default_factory=list, description="节点业务明细")
|
||||||
edges: list[FlowEdgeSchema] = Field(default_factory=list, description="连线业务明细")
|
edges: list[WorkflowEdgeSchema] = Field(default_factory=list, description="连线业务明细")
|
||||||
sources: list[FlowSourceSchema] = Field(default_factory=list, description="源节点列表(去重)")
|
sources: list[WorkflowSourceSchema] = Field(default_factory=list, description="源节点列表(去重)")
|
||||||
targets: list[FlowTargetSchema] = Field(default_factory=list, description="目标列表(去重)")
|
targets: list[WorkflowTargetSchema] = Field(default_factory=list, description="目标列表(去重)")
|
||||||
|
|
||||||
|
|
||||||
class FlowTransferPlanSchema(BaseModel):
|
class WorkflowTransferPlanSchema(BaseModel):
|
||||||
"""执行计划:单条连线生成的传输任务参数"""
|
"""执行计划:单条连线生成的传输任务参数"""
|
||||||
|
|
||||||
src_id: int = Field(..., ge=1, description="源存储源ID")
|
src_id: int = Field(..., ge=1, description="源存储源ID")
|
||||||
@@ -117,11 +117,11 @@ class FlowTransferPlanSchema(BaseModel):
|
|||||||
multipart_concurrency: int | None = Field(default=None, description="分片并发数")
|
multipart_concurrency: int | None = Field(default=None, description="分片并发数")
|
||||||
|
|
||||||
|
|
||||||
class WorkflowFlowCreateSchema(BaseModel):
|
class WorkflowCreateSchema(BaseModel):
|
||||||
"""创建传输流程(画布驱动:业务配置解析后落 flow_node/flow_edge 表)"""
|
"""创建传输流程(画布驱动:业务配置解析后落 flow_node/flow_edge 表)"""
|
||||||
|
|
||||||
name: str = Field(..., min_length=1, max_length=64, description="流程名称")
|
name: str = Field(..., min_length=1, max_length=64, description="流程名称")
|
||||||
task_type: FlowTaskType = Field("parallel", description="类型(parallel:多目标 chain:链式)")
|
task_type: WorkflowTaskType = Field("parallel", description="类型(parallel:多目标 chain:链式)")
|
||||||
graph: dict = Field(..., description="VueFlow画布数据 {nodes:[{id,type,position,data}],edges:[{id,source,target,data}]}")
|
graph: dict = Field(..., description="VueFlow画布数据 {nodes:[{id,type,position,data}],edges:[{id,source,target,data}]}")
|
||||||
status: int = Field(default=0, ge=0, le=1, description="状态(0:启用 1:停用)")
|
status: int = Field(default=0, ge=0, le=1, description="状态(0:启用 1:停用)")
|
||||||
description: str | None = Field(default=None, max_length=255, description="备注")
|
description: str | None = Field(default=None, max_length=255, description="备注")
|
||||||
@@ -142,11 +142,11 @@ class WorkflowFlowCreateSchema(BaseModel):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class WorkflowFlowUpdateSchema(WorkflowFlowCreateSchema):
|
class WorkflowUpdateSchema(WorkflowCreateSchema):
|
||||||
"""更新传输流程"""
|
"""更新传输流程"""
|
||||||
|
|
||||||
|
|
||||||
class WorkflowFlowExecuteSchema(BaseModel):
|
class WorkflowExecuteSchema(BaseModel):
|
||||||
"""执行传输流程参数"""
|
"""执行传输流程参数"""
|
||||||
|
|
||||||
source_paths: dict[str, str] | None = Field(
|
source_paths: dict[str, str] | None = Field(
|
||||||
@@ -155,24 +155,24 @@ class WorkflowFlowExecuteSchema(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WorkflowFlowOutSchema(BaseSchema, UserBySchema):
|
class WorkflowOutSchema(BaseSchema, UserBySchema):
|
||||||
"""传输流程详情响应模型(sources/targets 由 service 从明细表派生)"""
|
"""传输流程详情响应模型(sources/targets 由 service 从明细表派生)"""
|
||||||
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
name: str | None = None
|
name: str | None = None
|
||||||
task_type: FlowTaskType = "parallel"
|
task_type: WorkflowTaskType = "parallel"
|
||||||
sources: list[FlowSourceSchema] = Field(default_factory=list, description="源节点列表(由画布派生)")
|
sources: list[WorkflowSourceSchema] = Field(default_factory=list, description="源节点列表(由画布派生)")
|
||||||
targets: list[FlowTargetSchema] = Field(default_factory=list)
|
targets: list[WorkflowTargetSchema] = Field(default_factory=list)
|
||||||
graph: dict | None = None
|
graph: dict | None = None
|
||||||
graph_stats: dict | None = None
|
graph_stats: dict | None = None
|
||||||
status: int = 0
|
status: int = 0
|
||||||
description: str | None = None
|
description: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class WorkflowFlowQueryParam(BaseQueryParam, UserByQueryParam):
|
class WorkflowQueryParam(BaseQueryParam, UserByQueryParam):
|
||||||
"""传输流程查询参数"""
|
"""传输流程查询参数"""
|
||||||
|
|
||||||
name: str | None = Field(None, description="流程名称", json_schema_extra={"q": "like"})
|
name: str | None = Field(None, description="流程名称", json_schema_extra={"q": "like"})
|
||||||
task_type: FlowTaskType | None = Field(None, description="类型(parallel/chain)", json_schema_extra={"q": "eq"})
|
task_type: WorkflowTaskType | None = Field(None, description="类型(parallel/chain)", json_schema_extra={"q": "eq"})
|
||||||
status: int | None = Field(None, ge=0, le=1, description="状态(0:启用 1:停用)", json_schema_extra={"q": "eq"})
|
status: int | None = Field(None, ge=0, le=1, description="状态(0:启用 1:停用)", json_schema_extra={"q": "eq"})
|
||||||
+88
-88
@@ -7,34 +7,34 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
|
|
||||||
from app.core.base_schema import AuthSchema, PageResultSchema
|
from app.core.base_schema import AuthSchema, PageResultSchema
|
||||||
from app.core.exceptions import CustomException
|
from app.core.exceptions import CustomException
|
||||||
from app.modules.workflow.source.crud import StorageSourceCRUD
|
from app.modules.task.storage.node.crud import StorageNodeCRUD
|
||||||
from app.modules.workflow.source.model import StorageSourceModel
|
from app.modules.task.storage.node.model import StorageNodeModel
|
||||||
from app.modules.workflow.source.service import StorageSourceService
|
from app.modules.task.storage.node.service import StorageNodeService
|
||||||
from app.modules.workflow.transfer.schema import TransferMode, TransferTargetSchema, TransferTaskCreateSchema
|
from app.modules.task.storage.transfer.schema import TransferMode, TransferTargetSchema, TransferTaskCreateSchema
|
||||||
from app.modules.workflow.transfer.service import StorageTransferService
|
from app.modules.task.storage.transfer.service import StorageTransferService
|
||||||
from app.utils.common_util import search_to_dict
|
from app.utils.common_util import search_to_dict
|
||||||
|
|
||||||
from .crud import WorkflowFlowCRUD, WorkflowFlowEdgeCRUD, WorkflowFlowNodeCRUD
|
from .crud import WorkflowCRUD, WorkflowEdgeCRUD, WorkflowNodeCRUD
|
||||||
from .model import WorkflowFlowEdgeModel, WorkflowFlowModel, WorkflowFlowNodeModel
|
from .model import WorkflowEdgeModel, WorkflowModel, WorkflowNodeModel
|
||||||
from .schema import (
|
from .schema import (
|
||||||
FlowEdgeSchema,
|
WorkflowCreateSchema,
|
||||||
FlowGraphEdgeDataSchema,
|
WorkflowEdgeSchema,
|
||||||
FlowGraphNodeDataSchema,
|
WorkflowGraphEdgeDataSchema,
|
||||||
FlowLayoutEdgeSchema,
|
WorkflowGraphNodeDataSchema,
|
||||||
FlowLayoutNodeSchema,
|
WorkflowLayoutEdgeSchema,
|
||||||
FlowNodeSchema,
|
WorkflowLayoutNodeSchema,
|
||||||
FlowSourceSchema,
|
WorkflowNodeSchema,
|
||||||
FlowSplitResultSchema,
|
WorkflowOutSchema,
|
||||||
FlowTargetSchema,
|
WorkflowQueryParam,
|
||||||
FlowTransferPlanSchema,
|
WorkflowSourceSchema,
|
||||||
WorkflowFlowCreateSchema,
|
WorkflowSplitResultSchema,
|
||||||
WorkflowFlowOutSchema,
|
WorkflowTargetSchema,
|
||||||
WorkflowFlowQueryParam,
|
WorkflowTransferPlanSchema,
|
||||||
WorkflowFlowUpdateSchema,
|
WorkflowUpdateSchema,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WorkflowFlowService:
|
class WorkflowService:
|
||||||
"""传输流程服务(源节点 → 目标节点,支持 1对多 / 多对1)
|
"""传输流程服务(源节点 → 目标节点,支持 1对多 / 多对1)
|
||||||
|
|
||||||
数据职责划分:
|
数据职责划分:
|
||||||
@@ -51,24 +51,24 @@ class WorkflowFlowService:
|
|||||||
|
|
||||||
# ── 内部工具 ────────────────────────────────────────────────────
|
# ── 内部工具 ────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _crud(self) -> WorkflowFlowCRUD:
|
def _crud(self) -> WorkflowCRUD:
|
||||||
return WorkflowFlowCRUD(self.auth, self.db)
|
return WorkflowCRUD(self.auth, self.db)
|
||||||
|
|
||||||
async def _validate_nodes(
|
async def _validate_nodes(
|
||||||
self, sources: list[FlowSourceSchema], targets: list[FlowTargetSchema]
|
self, sources: list[WorkflowSourceSchema], targets: list[WorkflowTargetSchema]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""校验源节点与目标节点均存在且启用。"""
|
"""校验源节点与目标节点均存在且启用。"""
|
||||||
node_service = StorageSourceService(self.auth, self.db)
|
node_service = StorageNodeService(self.auth, self.db)
|
||||||
ids = [s.source_id for s in sources] + [t.target_id for t in targets]
|
ids = [s.source_id for s in sources] + [t.target_id for t in targets]
|
||||||
if ids:
|
if ids:
|
||||||
await node_service.get_active_sources(ids)
|
await node_service.get_active_sources(ids)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _sources_from(
|
def _sources_from(
|
||||||
edge_rows: Sequence[WorkflowFlowEdgeModel], node_map: dict[str, WorkflowFlowNodeModel]
|
edge_rows: Sequence[WorkflowEdgeModel], node_map: dict[str, WorkflowNodeModel]
|
||||||
) -> list[FlowSourceSchema]:
|
) -> list[WorkflowSourceSchema]:
|
||||||
"""由连线明细派生源节点列表(去重)。"""
|
"""由连线明细派生源节点列表(去重)。"""
|
||||||
out: list[FlowSourceSchema] = []
|
out: list[WorkflowSourceSchema] = []
|
||||||
seen: set[int] = set()
|
seen: set[int] = set()
|
||||||
for e in edge_rows:
|
for e in edge_rows:
|
||||||
src_node = node_map.get(e.source_node_key)
|
src_node = node_map.get(e.source_node_key)
|
||||||
@@ -77,15 +77,15 @@ class WorkflowFlowService:
|
|||||||
if src_node.source_id in seen:
|
if src_node.source_id in seen:
|
||||||
continue
|
continue
|
||||||
seen.add(src_node.source_id)
|
seen.add(src_node.source_id)
|
||||||
out.append(FlowSourceSchema(source_id=src_node.source_id))
|
out.append(WorkflowSourceSchema(source_id=src_node.source_id))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _targets_from(
|
def _targets_from(
|
||||||
edge_rows: Sequence[WorkflowFlowEdgeModel], node_map: dict[str, WorkflowFlowNodeModel]
|
edge_rows: Sequence[WorkflowEdgeModel], node_map: dict[str, WorkflowNodeModel]
|
||||||
) -> list[FlowTargetSchema]:
|
) -> list[WorkflowTargetSchema]:
|
||||||
"""由连线明细派生目标列表(去重),目标目录取目标节点的默认源目录。"""
|
"""由连线明细派生目标列表(去重),目标目录取目标节点的默认源目录。"""
|
||||||
out: list[FlowTargetSchema] = []
|
out: list[WorkflowTargetSchema] = []
|
||||||
seen: set[int] = set()
|
seen: set[int] = set()
|
||||||
for e in edge_rows:
|
for e in edge_rows:
|
||||||
tgt_node = node_map.get(e.target_node_key)
|
tgt_node = node_map.get(e.target_node_key)
|
||||||
@@ -95,14 +95,14 @@ class WorkflowFlowService:
|
|||||||
continue
|
continue
|
||||||
seen.add(tgt_node.source_id)
|
seen.add(tgt_node.source_id)
|
||||||
out.append(
|
out.append(
|
||||||
FlowTargetSchema(target_id=tgt_node.source_id, target_path=tgt_node.source_path or "")
|
WorkflowTargetSchema(target_id=tgt_node.source_id, target_path=tgt_node.source_path or "")
|
||||||
)
|
)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
# ── 画布拆分/组装 ───────────────────────────────────────────────
|
# ── 画布拆分/组装 ───────────────────────────────────────────────
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _split_graph(graph: dict) -> FlowSplitResultSchema:
|
def _split_graph(graph: dict) -> WorkflowSplitResultSchema:
|
||||||
"""校验并拆分提交的画布。
|
"""校验并拆分提交的画布。
|
||||||
|
|
||||||
- 业务配置(节点 source_id/source_path、连线启用与传输方式)→ FlowNode/FlowEdge 明细
|
- 业务配置(节点 source_id/source_path、连线启用与传输方式)→ FlowNode/FlowEdge 明细
|
||||||
@@ -115,11 +115,11 @@ class WorkflowFlowService:
|
|||||||
raise CustomException(msg="画布中未找到有效的传输连线(源节点 → 目标节点)")
|
raise CustomException(msg="画布中未找到有效的传输连线(源节点 → 目标节点)")
|
||||||
|
|
||||||
layout_nodes: list[dict] = []
|
layout_nodes: list[dict] = []
|
||||||
node_items: list[FlowNodeSchema] = []
|
node_items: list[WorkflowNodeSchema] = []
|
||||||
for nid, n in nodes.items():
|
for nid, n in nodes.items():
|
||||||
d = n.get("data") or {}
|
d = n.get("data") or {}
|
||||||
layout_nodes.append(
|
layout_nodes.append(
|
||||||
FlowLayoutNodeSchema(
|
WorkflowLayoutNodeSchema(
|
||||||
id=nid,
|
id=nid,
|
||||||
type=n.get("type") or "storage",
|
type=n.get("type") or "storage",
|
||||||
position=n.get("position") or {"x": 0, "y": 0},
|
position=n.get("position") or {"x": 0, "y": 0},
|
||||||
@@ -129,13 +129,13 @@ class WorkflowFlowService:
|
|||||||
)
|
)
|
||||||
if d.get("source_id") is not None:
|
if d.get("source_id") is not None:
|
||||||
node_items.append(
|
node_items.append(
|
||||||
FlowNodeSchema(node_key=nid, source_id=d["source_id"], source_path=d.get("source_path"))
|
WorkflowNodeSchema(node_key=nid, source_id=d["source_id"], source_path=d.get("source_path"))
|
||||||
)
|
)
|
||||||
node_map = {it.node_key: it for it in node_items}
|
node_map = {it.node_key: it for it in node_items}
|
||||||
|
|
||||||
sources: list[FlowSourceSchema] = []
|
sources: list[WorkflowSourceSchema] = []
|
||||||
targets: list[FlowTargetSchema] = []
|
targets: list[WorkflowTargetSchema] = []
|
||||||
edge_items: list[FlowEdgeSchema] = []
|
edge_items: list[WorkflowEdgeSchema] = []
|
||||||
layout_edges: list[dict] = []
|
layout_edges: list[dict] = []
|
||||||
seen_edges: set[tuple[int, int]] = set()
|
seen_edges: set[tuple[int, int]] = set()
|
||||||
seen_sources: set[int] = set()
|
seen_sources: set[int] = set()
|
||||||
@@ -156,14 +156,14 @@ class WorkflowFlowService:
|
|||||||
seen_edges.add(pair)
|
seen_edges.add(pair)
|
||||||
if sid not in seen_sources:
|
if sid not in seen_sources:
|
||||||
seen_sources.add(sid)
|
seen_sources.add(sid)
|
||||||
sources.append(FlowSourceSchema(source_id=sid))
|
sources.append(WorkflowSourceSchema(source_id=sid))
|
||||||
if tid not in seen_targets:
|
if tid not in seen_targets:
|
||||||
seen_targets.add(tid)
|
seen_targets.add(tid)
|
||||||
# 目标目录由目标节点默认源目录决定
|
# 目标目录由目标节点默认源目录决定
|
||||||
targets.append(FlowTargetSchema(target_id=tid, target_path=tgt_n.source_path or ""))
|
targets.append(WorkflowTargetSchema(target_id=tid, target_path=tgt_n.source_path or ""))
|
||||||
ed = e.get("data") or {}
|
ed = e.get("data") or {}
|
||||||
edge_items.append(
|
edge_items.append(
|
||||||
FlowEdgeSchema(
|
WorkflowEdgeSchema(
|
||||||
edge_key=e["id"],
|
edge_key=e["id"],
|
||||||
source_node_key=e.get("source"),
|
source_node_key=e.get("source"),
|
||||||
target_node_key=e.get("target"),
|
target_node_key=e.get("target"),
|
||||||
@@ -174,7 +174,7 @@ class WorkflowFlowService:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
layout_edges.append(
|
layout_edges.append(
|
||||||
FlowLayoutEdgeSchema(
|
WorkflowLayoutEdgeSchema(
|
||||||
id=e["id"],
|
id=e["id"],
|
||||||
source=e.get("source"),
|
source=e.get("source"),
|
||||||
target=e.get("target"),
|
target=e.get("target"),
|
||||||
@@ -187,7 +187,7 @@ class WorkflowFlowService:
|
|||||||
|
|
||||||
if not edge_items:
|
if not edge_items:
|
||||||
raise CustomException(msg="画布中未找到有效的传输连线(源节点 → 目标节点)")
|
raise CustomException(msg="画布中未找到有效的传输连线(源节点 → 目标节点)")
|
||||||
return FlowSplitResultSchema(
|
return WorkflowSplitResultSchema(
|
||||||
layout={"nodes": layout_nodes, "edges": layout_edges},
|
layout={"nodes": layout_nodes, "edges": layout_edges},
|
||||||
nodes=node_items,
|
nodes=node_items,
|
||||||
edges=edge_items,
|
edges=edge_items,
|
||||||
@@ -195,41 +195,41 @@ class WorkflowFlowService:
|
|||||||
targets=targets,
|
targets=targets,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _save_graph(self, flow_id: int, nodes: list[FlowNodeSchema], edges: list[FlowEdgeSchema]) -> None:
|
async def _save_graph(self, flow_id: int, nodes: list[WorkflowNodeSchema], edges: list[WorkflowEdgeSchema]) -> None:
|
||||||
"""覆写流程的业务明细(附属表物理删除后重建,不做逻辑删除)。
|
"""覆写流程的业务明细(附属表物理删除后重建,不做逻辑删除)。
|
||||||
|
|
||||||
父流程行已在调用方经 WorkflowFlowCRUD 做过数据权限校验,明细随父全量覆写,
|
父流程行已在调用方经 WorkflowCRUD 做过数据权限校验,明细随父全量覆写,
|
||||||
删除统一走子表 CRUD 的物理清理方法(基类软删 delete 不适用于逐次覆写场景)。
|
删除统一走子表 CRUD 的物理清理方法(基类软删 delete 不适用于逐次覆写场景)。
|
||||||
"""
|
"""
|
||||||
await WorkflowFlowNodeCRUD(self.auth, self.db).hard_delete_by_flow_ids([flow_id])
|
await WorkflowNodeCRUD(self.auth, self.db).hard_delete_by_flow_ids([flow_id])
|
||||||
await WorkflowFlowEdgeCRUD(self.auth, self.db).hard_delete_by_flow_ids([flow_id])
|
await WorkflowEdgeCRUD(self.auth, self.db).hard_delete_by_flow_ids([flow_id])
|
||||||
user_id = self.auth.user.id
|
user_id = self.auth.user.id
|
||||||
for it in nodes:
|
for it in nodes:
|
||||||
self.db.add(WorkflowFlowNodeModel(flow_id=flow_id, created_id=user_id, updated_id=user_id, **it.model_dump()))
|
self.db.add(WorkflowNodeModel(flow_id=flow_id, created_id=user_id, updated_id=user_id, **it.model_dump()))
|
||||||
for it in edges:
|
for it in edges:
|
||||||
self.db.add(WorkflowFlowEdgeModel(flow_id=flow_id, created_id=user_id, updated_id=user_id, **it.model_dump()))
|
self.db.add(WorkflowEdgeModel(flow_id=flow_id, created_id=user_id, updated_id=user_id, **it.model_dump()))
|
||||||
await self.db.flush()
|
await self.db.flush()
|
||||||
|
|
||||||
async def _load_flow_graph(self, flow_id: int) -> tuple[list[WorkflowFlowNodeModel], list[WorkflowFlowEdgeModel]]:
|
async def _load_flow_graph(self, flow_id: int) -> tuple[list[WorkflowNodeModel], list[WorkflowEdgeModel]]:
|
||||||
result = await self.db.execute(
|
result = await self.db.execute(
|
||||||
select(WorkflowFlowNodeModel)
|
select(WorkflowNodeModel)
|
||||||
.where(WorkflowFlowNodeModel.flow_id == flow_id)
|
.where(WorkflowNodeModel.flow_id == flow_id)
|
||||||
.order_by(WorkflowFlowNodeModel.id)
|
.order_by(WorkflowNodeModel.id)
|
||||||
)
|
)
|
||||||
node_rows = list(result.scalars().all())
|
node_rows = list(result.scalars().all())
|
||||||
result = await self.db.execute(
|
result = await self.db.execute(
|
||||||
select(WorkflowFlowEdgeModel)
|
select(WorkflowEdgeModel)
|
||||||
.where(WorkflowFlowEdgeModel.flow_id == flow_id)
|
.where(WorkflowEdgeModel.flow_id == flow_id)
|
||||||
.order_by(WorkflowFlowEdgeModel.id)
|
.order_by(WorkflowEdgeModel.id)
|
||||||
)
|
)
|
||||||
edge_rows = list(result.scalars().all())
|
edge_rows = list(result.scalars().all())
|
||||||
return node_rows, edge_rows
|
return node_rows, edge_rows
|
||||||
|
|
||||||
async def _build_graph(
|
async def _build_graph(
|
||||||
self,
|
self,
|
||||||
flow: WorkflowFlowModel,
|
flow: WorkflowModel,
|
||||||
node_rows: Sequence[WorkflowFlowNodeModel],
|
node_rows: Sequence[WorkflowNodeModel],
|
||||||
edge_rows: Sequence[WorkflowFlowEdgeModel],
|
edge_rows: Sequence[WorkflowEdgeModel],
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""由布局(flow.graph)+ 业务明细(node/edge 表)+ 存储源组装完整画布,供前端直接回显。"""
|
"""由布局(flow.graph)+ 业务明细(node/edge 表)+ 存储源组装完整画布,供前端直接回显。"""
|
||||||
layout = flow.graph or {}
|
layout = flow.graph or {}
|
||||||
@@ -239,9 +239,9 @@ class WorkflowFlowService:
|
|||||||
edges_by_key = {e.edge_key: e for e in edge_rows}
|
edges_by_key = {e.edge_key: e for e in edge_rows}
|
||||||
|
|
||||||
ids = {n.source_id for n in node_rows}
|
ids = {n.source_id for n in node_rows}
|
||||||
src_map: dict[int, StorageSourceModel] = {}
|
src_map: dict[int, StorageNodeModel] = {}
|
||||||
if ids:
|
if ids:
|
||||||
sources = await StorageSourceCRUD(self.auth, self.db).get_list(search={"id": ("in", sorted(ids))})
|
sources = await StorageNodeCRUD(self.auth, self.db).get_list(search={"id": ("in", sorted(ids))})
|
||||||
src_map = {s.id: s for s in sources}
|
src_map = {s.id: s for s in sources}
|
||||||
|
|
||||||
out_nodes: list[dict] = []
|
out_nodes: list[dict] = []
|
||||||
@@ -251,7 +251,7 @@ class WorkflowFlowService:
|
|||||||
continue
|
continue
|
||||||
src = src_map.get(node_row.source_id)
|
src = src_map.get(node_row.source_id)
|
||||||
node = dict(ln)
|
node = dict(ln)
|
||||||
node["data"] = FlowGraphNodeDataSchema(
|
node["data"] = WorkflowGraphNodeDataSchema(
|
||||||
source_id=node_row.source_id,
|
source_id=node_row.source_id,
|
||||||
source_path=node_row.source_path,
|
source_path=node_row.source_path,
|
||||||
label=ln.get("label") or (src.name if src else None),
|
label=ln.get("label") or (src.name if src else None),
|
||||||
@@ -274,7 +274,7 @@ class WorkflowFlowService:
|
|||||||
src = src_map.get(src_node.source_id) if src_node else None
|
src = src_map.get(src_node.source_id) if src_node else None
|
||||||
tgt = src_map.get(tgt_node.source_id) if tgt_node else None
|
tgt = src_map.get(tgt_node.source_id) if tgt_node else None
|
||||||
edge = dict(le)
|
edge = dict(le)
|
||||||
edge["data"] = FlowGraphEdgeDataSchema(
|
edge["data"] = WorkflowGraphEdgeDataSchema(
|
||||||
enabled=edge_row.enabled,
|
enabled=edge_row.enabled,
|
||||||
transfer_mode=edge_row.transfer_mode,
|
transfer_mode=edge_row.transfer_mode,
|
||||||
multipart_part_size=edge_row.multipart_part_size,
|
multipart_part_size=edge_row.multipart_part_size,
|
||||||
@@ -292,8 +292,8 @@ class WorkflowFlowService:
|
|||||||
|
|
||||||
# ── 查询 ────────────────────────────────────────────────────────
|
# ── 查询 ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
async def _to_out(self, obj: WorkflowFlowModel) -> WorkflowFlowOutSchema:
|
async def _to_out(self, obj: WorkflowModel) -> WorkflowOutSchema:
|
||||||
out = WorkflowFlowOutSchema.model_validate(obj)
|
out = WorkflowOutSchema.model_validate(obj)
|
||||||
node_rows, edge_rows = await self._load_flow_graph(obj.id)
|
node_rows, edge_rows = await self._load_flow_graph(obj.id)
|
||||||
out.graph_stats = {"node_count": len(node_rows), "edge_count": len(edge_rows)}
|
out.graph_stats = {"node_count": len(node_rows), "edge_count": len(edge_rows)}
|
||||||
if edge_rows:
|
if edge_rows:
|
||||||
@@ -304,24 +304,24 @@ class WorkflowFlowService:
|
|||||||
out.graph = await self._build_graph(obj, node_rows, edge_rows)
|
out.graph = await self._build_graph(obj, node_rows, edge_rows)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
async def _to_out_list(self, objs: Sequence[WorkflowFlowModel]) -> list[WorkflowFlowOutSchema]:
|
async def _to_out_list(self, objs: Sequence[WorkflowModel]) -> list[WorkflowOutSchema]:
|
||||||
"""批量组装列表概览:源/目标列表、画布统计,一次查询避免 N+1。"""
|
"""批量组装列表概览:源/目标列表、画布统计,一次查询避免 N+1。"""
|
||||||
flow_ids = [o.id for o in objs]
|
flow_ids = [o.id for o in objs]
|
||||||
nodes_by_flow: dict[int, list[WorkflowFlowNodeModel]] = defaultdict(list)
|
nodes_by_flow: dict[int, list[WorkflowNodeModel]] = defaultdict(list)
|
||||||
edges_by_flow: dict[int, list[WorkflowFlowEdgeModel]] = defaultdict(list)
|
edges_by_flow: dict[int, list[WorkflowEdgeModel]] = defaultdict(list)
|
||||||
if flow_ids:
|
if flow_ids:
|
||||||
result = await self.db.execute(
|
result = await self.db.execute(
|
||||||
select(WorkflowFlowNodeModel).where(WorkflowFlowNodeModel.flow_id.in_(flow_ids))
|
select(WorkflowNodeModel).where(WorkflowNodeModel.flow_id.in_(flow_ids))
|
||||||
)
|
)
|
||||||
for n in result.scalars().all():
|
for n in result.scalars().all():
|
||||||
nodes_by_flow[n.flow_id].append(n)
|
nodes_by_flow[n.flow_id].append(n)
|
||||||
result = await self.db.execute(
|
result = await self.db.execute(
|
||||||
select(WorkflowFlowEdgeModel).where(WorkflowFlowEdgeModel.flow_id.in_(flow_ids))
|
select(WorkflowEdgeModel).where(WorkflowEdgeModel.flow_id.in_(flow_ids))
|
||||||
)
|
)
|
||||||
for e in result.scalars().all():
|
for e in result.scalars().all():
|
||||||
edges_by_flow[e.flow_id].append(e)
|
edges_by_flow[e.flow_id].append(e)
|
||||||
|
|
||||||
outs = [WorkflowFlowOutSchema.model_validate(o) for o in objs]
|
outs = [WorkflowOutSchema.model_validate(o) for o in objs]
|
||||||
for obj, out in zip(objs, outs, strict=False):
|
for obj, out in zip(objs, outs, strict=False):
|
||||||
out.graph = None
|
out.graph = None
|
||||||
ns = nodes_by_flow.get(obj.id, [])
|
ns = nodes_by_flow.get(obj.id, [])
|
||||||
@@ -333,17 +333,17 @@ class WorkflowFlowService:
|
|||||||
out.targets = self._targets_from(es, node_map)
|
out.targets = self._targets_from(es, node_map)
|
||||||
return outs
|
return outs
|
||||||
|
|
||||||
async def detail(self, id: int) -> WorkflowFlowOutSchema:
|
async def detail(self, id: int) -> WorkflowOutSchema:
|
||||||
obj = await self._crud().get_or_404(id=id)
|
obj = await self._crud().get_or_404(id=id)
|
||||||
return await self._to_out(obj)
|
return await self._to_out(obj)
|
||||||
|
|
||||||
async def page(
|
async def page(
|
||||||
self,
|
self,
|
||||||
search: WorkflowFlowQueryParam | None,
|
search: WorkflowQueryParam | None,
|
||||||
page_no: int,
|
page_no: int,
|
||||||
page_size: int,
|
page_size: int,
|
||||||
order_by: list[dict] | None = None,
|
order_by: list[dict] | None = None,
|
||||||
) -> PageResultSchema[WorkflowFlowOutSchema]:
|
) -> PageResultSchema[WorkflowOutSchema]:
|
||||||
result = await self._crud().page(
|
result = await self._crud().page(
|
||||||
offset=(page_no - 1) * page_size,
|
offset=(page_no - 1) * page_size,
|
||||||
limit=page_size,
|
limit=page_size,
|
||||||
@@ -351,7 +351,7 @@ class WorkflowFlowService:
|
|||||||
search=search_to_dict(search),
|
search=search_to_dict(search),
|
||||||
)
|
)
|
||||||
items = await self._to_out_list(result.items)
|
items = await self._to_out_list(result.items)
|
||||||
return PageResultSchema[WorkflowFlowOutSchema](
|
return PageResultSchema[WorkflowOutSchema](
|
||||||
page_no=result.page_no,
|
page_no=result.page_no,
|
||||||
page_size=result.page_size,
|
page_size=result.page_size,
|
||||||
total=result.total,
|
total=result.total,
|
||||||
@@ -359,15 +359,15 @@ class WorkflowFlowService:
|
|||||||
items=items,
|
items=items,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_list(self, search: WorkflowFlowQueryParam | None = None) -> list[WorkflowFlowOutSchema]:
|
async def get_list(self, search: WorkflowQueryParam | None = None) -> list[WorkflowOutSchema]:
|
||||||
objs = await self._crud().get_list(search=search_to_dict(search), order_by=[{"id": "asc"}])
|
objs = await self._crud().get_list(search=search_to_dict(search), order_by=[{"id": "asc"}])
|
||||||
return await self._to_out_list(objs)
|
return await self._to_out_list(objs)
|
||||||
|
|
||||||
# ── 写入 ────────────────────────────────────────────────────────
|
# ── 写入 ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
async def _prepare_and_save(
|
async def _prepare_and_save(
|
||||||
self, flow_id: int | None, data: WorkflowFlowCreateSchema | WorkflowFlowUpdateSchema
|
self, flow_id: int | None, data: WorkflowCreateSchema | WorkflowUpdateSchema
|
||||||
) -> tuple[dict, list[FlowNodeSchema], list[FlowEdgeSchema]]:
|
) -> tuple[dict, list[WorkflowNodeSchema], list[WorkflowEdgeSchema]]:
|
||||||
"""拆分画布:业务配置写入明细表,布局存入 flow.graph。"""
|
"""拆分画布:业务配置写入明细表,布局存入 flow.graph。"""
|
||||||
data_dict = data.model_dump(exclude_unset=True, exclude_none=True)
|
data_dict = data.model_dump(exclude_unset=True, exclude_none=True)
|
||||||
graph = data_dict.pop("graph", None)
|
graph = data_dict.pop("graph", None)
|
||||||
@@ -380,7 +380,7 @@ class WorkflowFlowService:
|
|||||||
await self._save_graph(flow_id, split.nodes, split.edges)
|
await self._save_graph(flow_id, split.nodes, split.edges)
|
||||||
return data_dict, split.nodes, split.edges
|
return data_dict, split.nodes, split.edges
|
||||||
|
|
||||||
async def create(self, data: WorkflowFlowCreateSchema) -> WorkflowFlowOutSchema:
|
async def create(self, data: WorkflowCreateSchema) -> WorkflowOutSchema:
|
||||||
exist = await self._crud().get(name=data.name)
|
exist = await self._crud().get(name=data.name)
|
||||||
if exist:
|
if exist:
|
||||||
raise CustomException(msg="创建失败,流程名称已存在")
|
raise CustomException(msg="创建失败,流程名称已存在")
|
||||||
@@ -389,7 +389,7 @@ class WorkflowFlowService:
|
|||||||
await self._save_graph(obj.id, node_items, edge_items)
|
await self._save_graph(obj.id, node_items, edge_items)
|
||||||
return await self._to_out(obj)
|
return await self._to_out(obj)
|
||||||
|
|
||||||
async def update(self, id: int, data: WorkflowFlowUpdateSchema) -> WorkflowFlowOutSchema:
|
async def update(self, id: int, data: WorkflowUpdateSchema) -> WorkflowOutSchema:
|
||||||
await self._crud().get_or_404(id=id, msg="更新失败,该流程不存在")
|
await self._crud().get_or_404(id=id, msg="更新失败,该流程不存在")
|
||||||
exist = await self._crud().get(name=data.name)
|
exist = await self._crud().get(name=data.name)
|
||||||
if exist and exist.id != id:
|
if exist and exist.id != id:
|
||||||
@@ -405,8 +405,8 @@ class WorkflowFlowService:
|
|||||||
raise CustomException(msg="删除失败,删除对象不能为空")
|
raise CustomException(msg="删除失败,删除对象不能为空")
|
||||||
await self._crud().delete(ids=ids)
|
await self._crud().delete(ids=ids)
|
||||||
# 子表随父流程一并物理清理(无软删消费场景)
|
# 子表随父流程一并物理清理(无软删消费场景)
|
||||||
await WorkflowFlowNodeCRUD(self.auth, self.db).hard_delete_by_flow_ids(ids)
|
await WorkflowNodeCRUD(self.auth, self.db).hard_delete_by_flow_ids(ids)
|
||||||
await WorkflowFlowEdgeCRUD(self.auth, self.db).hard_delete_by_flow_ids(ids)
|
await WorkflowEdgeCRUD(self.auth, self.db).hard_delete_by_flow_ids(ids)
|
||||||
|
|
||||||
# ── 执行 ────────────────────────────────────────────────────────
|
# ── 执行 ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -427,7 +427,7 @@ class WorkflowFlowService:
|
|||||||
source_paths = source_paths or {}
|
source_paths = source_paths or {}
|
||||||
|
|
||||||
# 阶段一:解析并校验每条连线
|
# 阶段一:解析并校验每条连线
|
||||||
plans: list[FlowTransferPlanSchema] = []
|
plans: list[WorkflowTransferPlanSchema] = []
|
||||||
for e in enabled_edges:
|
for e in enabled_edges:
|
||||||
src_node = nodes_by_key.get(e.source_node_key)
|
src_node = nodes_by_key.get(e.source_node_key)
|
||||||
tgt_node = nodes_by_key.get(e.target_node_key)
|
tgt_node = nodes_by_key.get(e.target_node_key)
|
||||||
@@ -446,7 +446,7 @@ class WorkflowFlowService:
|
|||||||
missing = "未选择源文件/目录" if not src_path else "目标节点未配置默认目录"
|
missing = "未选择源文件/目录" if not src_path else "目标节点未配置默认目录"
|
||||||
raise CustomException(msg=f"执行失败,连线 {edge_label} {missing}")
|
raise CustomException(msg=f"执行失败,连线 {edge_label} {missing}")
|
||||||
plans.append(
|
plans.append(
|
||||||
FlowTransferPlanSchema(
|
WorkflowTransferPlanSchema(
|
||||||
src_id=src_id,
|
src_id=src_id,
|
||||||
tgt_id=tgt_id,
|
tgt_id=tgt_id,
|
||||||
src_path=src_path,
|
src_path=src_path,
|
||||||
@@ -458,7 +458,7 @@ class WorkflowFlowService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 阶段二:统一校验所有涉及的存储源可用,再逐个生成传输任务
|
# 阶段二:统一校验所有涉及的存储源可用,再逐个生成传输任务
|
||||||
node_service = StorageSourceService(self.auth, self.db)
|
node_service = StorageNodeService(self.auth, self.db)
|
||||||
sources = await node_service.get_active_sources(
|
sources = await node_service.get_active_sources(
|
||||||
[p.src_id for p in plans] + [p.tgt_id for p in plans]
|
[p.src_id for p in plans] + [p.tgt_id for p in plans]
|
||||||
)
|
)
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
"""工作流模块(存储与文件流转):
|
|
||||||
|
|
||||||
- ``source``: 存储源管理(OSS/COS/OBS/S3/SFTP/FTP 等连接配置)
|
|
||||||
- ``core``: 对象存储协议适配器与工厂
|
|
||||||
- ``storage``: 存储文件浏览
|
|
||||||
- ``transfer``: 传输任务引擎
|
|
||||||
- ``flow``: 工作流定义(画布 CRUD、发布、执行 API)
|
|
||||||
|
|
||||||
路由统一挂在 ``/workflow`` 下(见 ``app/api/v1/workflow.py``)。
|
|
||||||
"""
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
# 见 docs/PLUGIN_ARCHITECTURE.md
|
|
||||||
|
|
||||||
name = "workflow"
|
|
||||||
title = "工作流"
|
|
||||||
version = "1.0.0"
|
|
||||||
description = "存储源管理、对象存储适配(OSS/COS/OBS/S3/SFTP/FTP)、文件浏览、传输任务与流程编排。"
|
|
||||||
optional = true
|
|
||||||
tags = ["workflow", "storage", "transfer"]
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from app.core.base_crud import CRUDBase
|
|
||||||
from app.core.base_schema import AuthSchema
|
|
||||||
|
|
||||||
from .model import StorageSourceModel
|
|
||||||
from .schema import StorageSourceCreateSchema, StorageSourceUpdateSchema
|
|
||||||
|
|
||||||
|
|
||||||
class StorageSourceCRUD(CRUDBase[StorageSourceModel, StorageSourceCreateSchema, StorageSourceUpdateSchema]):
|
|
||||||
"""存储源模块数据层"""
|
|
||||||
|
|
||||||
def __init__(self, auth: AuthSchema, db: AsyncSession) -> None:
|
|
||||||
super().__init__(model=StorageSourceModel, auth=auth, db=db)
|
|
||||||
+15
-20
@@ -5,26 +5,24 @@ import typer
|
|||||||
import uvicorn
|
import uvicorn
|
||||||
from alembic import command
|
from alembic import command
|
||||||
from alembic.config import Config
|
from alembic.config import Config
|
||||||
from fastapi import FastAPI
|
|
||||||
from typer.main import Typer
|
from typer.main import Typer
|
||||||
|
|
||||||
from app.common.enums import EnvironmentEnum
|
from app.common.enums import EnvironmentEnum
|
||||||
from app.config.setting import settings
|
|
||||||
from app.init_app import create_app
|
from app.init_app import create_app
|
||||||
from app.utils.banner import worship
|
|
||||||
|
|
||||||
|
app = create_app()
|
||||||
fastapiadmin_cli: Typer = typer.Typer()
|
fastapiadmin_cli: Typer = typer.Typer()
|
||||||
alembic_cfg: Config = Config(file_="alembic.ini")
|
alembic_cfg: Config = Config(file_="alembic.ini")
|
||||||
app: FastAPI = create_app()
|
|
||||||
|
|
||||||
@fastapiadmin_cli.command(
|
@fastapiadmin_cli.command(
|
||||||
name="run",
|
name="run",
|
||||||
help="启动 FastapiAdmin 服务, 运行 uv run main.py run --env=dev 不加参数默认 dev 环境",
|
help="启动 FastapiAdmin 服务, 运行 python(或uv run) main.py run --env=dev 不加参数默认 dev 环境",
|
||||||
)
|
)
|
||||||
def run(
|
def run(
|
||||||
env: Annotated[EnvironmentEnum, typer.Option("--env", help="运行环境 (dev, prod)")] = EnvironmentEnum.DEV,
|
env: Annotated[EnvironmentEnum, typer.Option("--env", help="运行环境 (dev, prod)")] = EnvironmentEnum.DEV,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""按指定环境加载配置并启动 Uvicorn(开发环境开启 reload)。
|
"""按指定环境加载配置并启动 Uvicorn(dev 环境 DEBUG=True 自动开启 reload)。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
- env (EnvironmentEnum): 运行环境,对应 `--env`。
|
- env (EnvironmentEnum): 运行环境,对应 `--env`。
|
||||||
@@ -32,24 +30,21 @@ def run(
|
|||||||
返回:
|
返回:
|
||||||
- None
|
- None
|
||||||
"""
|
"""
|
||||||
# 设置环境变量:本进程的 settings 已在模块导入时固化,此处由 uvicorn 子进程继承
|
|
||||||
# ENVIRONMENT 后重新 import main,使 --env 真正生效(revision/upgrade 另用 cache_clear 处理)
|
|
||||||
os.environ["ENVIRONMENT"] = env.value
|
os.environ["ENVIRONMENT"] = env.value
|
||||||
|
|
||||||
typer.secho(
|
from app.utils.banner import worship
|
||||||
message=f"{worship()}",
|
typer.secho(message=f"{worship()}", fg=typer.colors.GREEN)
|
||||||
fg=typer.colors.GREEN,
|
|
||||||
)
|
from app.config.setting import get_settings, settings
|
||||||
|
|
||||||
|
get_settings.cache_clear()
|
||||||
|
|
||||||
# 启动uvicorn服务(传 import string 而非实例:reload/多 worker 模式要求子进程重新 import,
|
|
||||||
# 且 os.environ 的环境设置由子进程继承后,settings 才能按 --env 正确加载)
|
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
app=app,
|
app="main:app",
|
||||||
host=settings.SERVER_HOST,
|
host=settings.SERVER_HOST,
|
||||||
port=settings.SERVER_PORT,
|
port=settings.SERVER_PORT,
|
||||||
reload=env.value == EnvironmentEnum.DEV.value,
|
reload=settings.DEBUG,
|
||||||
workers=settings.WORKERS if env.value == EnvironmentEnum.PROD.value else 1,
|
workers=settings.WORKERS,
|
||||||
factory=True,
|
|
||||||
log_config=None,
|
log_config=None,
|
||||||
timeout_graceful_shutdown=5,
|
timeout_graceful_shutdown=5,
|
||||||
)
|
)
|
||||||
@@ -57,7 +52,7 @@ def run(
|
|||||||
|
|
||||||
@fastapiadmin_cli.command(
|
@fastapiadmin_cli.command(
|
||||||
name="revision",
|
name="revision",
|
||||||
help="生成新的 Alembic 迁移脚本, 运行 python main.py revision --env=dev",
|
help="生成新的 Alembic 迁移脚本, 运行 python(或uv run) main.py revision --env=dev",
|
||||||
)
|
)
|
||||||
def revision(
|
def revision(
|
||||||
env: Annotated[EnvironmentEnum, typer.Option("--env", help="运行环境 (dev, prod)")] = EnvironmentEnum.DEV,
|
env: Annotated[EnvironmentEnum, typer.Option("--env", help="运行环境 (dev, prod)")] = EnvironmentEnum.DEV,
|
||||||
@@ -80,7 +75,7 @@ def revision(
|
|||||||
|
|
||||||
@fastapiadmin_cli.command(
|
@fastapiadmin_cli.command(
|
||||||
name="upgrade",
|
name="upgrade",
|
||||||
help="应用最新的 Alembic 迁移, 运行 python main.py upgrade --env=dev",
|
help="应用最新的 Alembic 迁移, 运行 python(或uv run) main.py upgrade --env=dev",
|
||||||
)
|
)
|
||||||
def upgrade(
|
def upgrade(
|
||||||
env: Annotated[EnvironmentEnum, typer.Option("--env", help="运行环境 (dev, prod)")] = EnvironmentEnum.DEV,
|
env: Annotated[EnvironmentEnum, typer.Option("--env", help="运行环境 (dev, prod)")] = EnvironmentEnum.DEV,
|
||||||
|
|||||||
+3938
-3911
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { request } from "@utils";
|
import { request } from "@utils";
|
||||||
|
|
||||||
const API_PATH = "/workflow/storage";
|
const API_PATH = "/task/storage/browse";
|
||||||
|
|
||||||
const StorageAPI = {
|
const StorageAPI = {
|
||||||
/** 查询文件/目录列表。传 page_no/page_size 时分页返回(PageResult),否则返回全量数组。 */
|
/** 查询文件/目录列表。传 page_no/page_size 时分页返回(PageResult),否则返回全量数组。 */
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { request } from "@utils";
|
import { request } from "@utils";
|
||||||
|
|
||||||
const API_PATH = "/workflow/source";
|
const API_PATH = "/task/storage/node";
|
||||||
|
|
||||||
const NodeAPI = {
|
const NodeAPI = {
|
||||||
pageNode(query?: TablePageQuery) {
|
pageNode(query?: TablePageQuery) {
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import { request } from "@utils";
|
import { request } from "@utils";
|
||||||
import { createSSEClient, httpEndpoint, type SSEClient } from "@utils/sse";
|
import { createSSEClient, httpEndpoint, type SSEClient } from "@utils/sse";
|
||||||
|
|
||||||
const API_PATH = "/workflow/transfer";
|
const API_PATH = "/task/storage/transfer";
|
||||||
|
|
||||||
const TransferAPI = {
|
const TransferAPI = {
|
||||||
/** 创建传输任务(远端源,JSON) */
|
/** 创建传输任务(远端源,JSON) */
|
||||||
@@ -152,7 +152,7 @@ export class TransferStream {
|
|||||||
connect() {
|
connect() {
|
||||||
if (this.client) return;
|
if (this.client) return;
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
"/api/v1/task/workflow/transfer/stream",
|
"/api/v1/task/storage/transfer/stream",
|
||||||
httpEndpoint(import.meta.env.VITE_APP_WS_ENDPOINT)
|
httpEndpoint(import.meta.env.VITE_APP_WS_ENDPOINT)
|
||||||
);
|
);
|
||||||
this.client = createSSEClient({
|
this.client = createSSEClient({
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { request } from "@utils";
|
import { request } from "@utils";
|
||||||
|
|
||||||
const API_PATH = "/workflow/flow";
|
const API_PATH = "/task/storage/workflow";
|
||||||
|
|
||||||
const FlowAPI = {
|
const FlowAPI = {
|
||||||
pageFlow(query?: FlowPageQuery) {
|
pageFlow(query?: FlowPageQuery) {
|
||||||
@@ -182,30 +182,30 @@ export const MANUAL_MODULES_AFTER_SYSTEM: ManualModuleSection[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
anchor: "page-workflow",
|
anchor: "page-workflow",
|
||||||
title: "传输流程",
|
title: "流程管理",
|
||||||
path: "module_task/workflow/flow/index.vue",
|
path: "module_storage/workflow/index.vue",
|
||||||
notes: [
|
notes: [
|
||||||
"检索:FaSearchBar(可展开)。",
|
"检索:FaSearchBar(可展开)。",
|
||||||
"工具栏:新增、批量删除、刷新、列配置。",
|
"工具栏:新增、批量删除、刷新、列配置。",
|
||||||
"流程定义:源节点 + 目标节点列表(parallel 多目标 / chain 链式),目标路径随行配置。",
|
"流程定义:源节点 + 目标节点列表(parallel 多目标 / chain 链式),目标路径随行配置。",
|
||||||
"权限:`module_task:workflow:flow:create|delete|update` + 行级操作。",
|
"权限:`module_storage:workflow:flow:create|delete|update` + 行级操作。",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
anchor: "page-workflow-node",
|
anchor: "page-workflow-node",
|
||||||
title: "节点管理",
|
title: "节点管理",
|
||||||
path: "module_task/workflow/node/index.vue",
|
path: "module_storage/node/index.vue",
|
||||||
notes: [
|
notes: [
|
||||||
"检索:FaSearchBar(可展开)。",
|
"检索:FaSearchBar(可展开)。",
|
||||||
"节点即传输地址(存储源):FTP/FTPS/SFTP/S3/OBS/OSS/COS/本地目录。",
|
"节点即传输地址(存储源):FTP/FTPS/SFTP/S3/OBS/OSS/COS/本地目录。",
|
||||||
"行操作:打开(进入存储管理)、测试连接、详情、编辑、删除。",
|
"行操作:打开(进入存储管理)、测试连接、详情、编辑、删除。",
|
||||||
"权限:`module_task:workflow:node:create|delete|update` + 行级操作。",
|
"权限:`module_storage:workflow:node:create|delete|update` + 行级操作。",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
anchor: "page-workflow-storage",
|
anchor: "page-workflow-storage",
|
||||||
title: "存储管理",
|
title: "文件浏览",
|
||||||
path: "module_task/workflow/storage/index.vue",
|
path: "module_storage/browse/index.vue",
|
||||||
notes: [
|
notes: [
|
||||||
"多节点标签浏览各节点内部文件(类似 IDE 标签栏)。",
|
"多节点标签浏览各节点内部文件(类似 IDE 标签栏)。",
|
||||||
"文件操作:上传、新建目录、下载、复制/移动、重命名、分享、删除。",
|
"文件操作:上传、新建目录、下载、复制/移动、重命名、分享、删除。",
|
||||||
@@ -215,7 +215,7 @@ export const MANUAL_MODULES_AFTER_SYSTEM: ManualModuleSection[] = [
|
|||||||
{
|
{
|
||||||
anchor: "page-workflow-transfer",
|
anchor: "page-workflow-transfer",
|
||||||
title: "传输任务",
|
title: "传输任务",
|
||||||
path: "module_task/workflow/transfer/index.vue",
|
path: "module_storage/transfer/index.vue",
|
||||||
notes: [
|
notes: [
|
||||||
"创建传输任务:可选「从流程自动填充」源节点与多目标,或手动指定。",
|
"创建传输任务:可选「从流程自动填充」源节点与多目标,或手动指定。",
|
||||||
"实时进度:WebSocket 推送任务/步骤状态与速度。",
|
"实时进度:WebSocket 推送任务/步骤状态与速度。",
|
||||||
|
|||||||
+5
-5
@@ -1,4 +1,4 @@
|
|||||||
<!-- 文件浏览:多节点标签 + 上传/下载/新建目录/复制/移动/重命名/分享/删除(源项目风格,对接 /task/workflow/storage) -->
|
<!-- 文件浏览:多节点标签 + 上传/下载/新建目录/复制/移动/重命名/分享/删除(源项目风格,对接 /storage/browse) -->
|
||||||
<template>
|
<template>
|
||||||
<div class="flex h-full">
|
<div class="flex h-full">
|
||||||
<el-card shadow="never" class="browse-card flex min-h-0 flex-1 flex-col">
|
<el-card shadow="never" class="browse-card flex min-h-0 flex-1 flex-col">
|
||||||
@@ -605,14 +605,14 @@ import {
|
|||||||
Share,
|
Share,
|
||||||
Upload,
|
Upload,
|
||||||
} from "@element-plus/icons-vue";
|
} from "@element-plus/icons-vue";
|
||||||
import StorageAPI, { type StorageObject } from "@/api/module_task/workflow/storage";
|
import StorageAPI, { type StorageObject } from "@/api/module_storage/browse";
|
||||||
import NodeAPI, { type SourceTable } from "@/api/module_task/workflow/node";
|
import NodeAPI, { type SourceTable } from "@/api/module_storage/node";
|
||||||
import TransferAPI from "@/api/module_task/workflow/transfer";
|
import TransferAPI from "@/api/module_storage/transfer";
|
||||||
import type { ColumnOption } from "@/types/component";
|
import type { ColumnOption } from "@/types/component";
|
||||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "WorkflowStorage",
|
name: "WorkflowBrowse",
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
+8
-8
@@ -27,8 +27,8 @@
|
|||||||
<template #left>
|
<template #left>
|
||||||
<FaTableHeaderLeft
|
<FaTableHeaderLeft
|
||||||
:remove-ids="selectedIds"
|
:remove-ids="selectedIds"
|
||||||
:perm-create="['module_task:workflow:node:create']"
|
:perm-create="['module_storage:workflow:node:create']"
|
||||||
:perm-delete="['module_task:workflow:node:delete']"
|
:perm-delete="['module_storage:workflow:node:delete']"
|
||||||
:delete-loading="batchDeleting"
|
:delete-loading="batchDeleting"
|
||||||
:create-loading="createLoading"
|
:create-loading="createLoading"
|
||||||
@add="handleAdd"
|
@add="handleAdd"
|
||||||
@@ -194,7 +194,7 @@ import NodeAPI, {
|
|||||||
type AdvancedFieldDef,
|
type AdvancedFieldDef,
|
||||||
type SourceForm,
|
type SourceForm,
|
||||||
type SourceTable,
|
type SourceTable,
|
||||||
} from "@/api/module_task/workflow/node";
|
} from "@/api/module_storage/node";
|
||||||
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
||||||
import type FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
|
import type FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
|
||||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
@@ -347,14 +347,14 @@ function buildSourceRowActions(
|
|||||||
artType: "view",
|
artType: "view",
|
||||||
icon: "ri:link",
|
icon: "ri:link",
|
||||||
iconColor: "var(--el-color-primary)",
|
iconColor: "var(--el-color-primary)",
|
||||||
perm: "module_task:workflow:node:query",
|
perm: "module_storage:workflow:node:query",
|
||||||
run: () => ctx.onTest(row.id!),
|
run: () => ctx.onTest(row.id!),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "detail",
|
key: "detail",
|
||||||
label: "详情",
|
label: "详情",
|
||||||
artType: "view",
|
artType: "view",
|
||||||
perm: "module_task:workflow:node:query",
|
perm: "module_storage:workflow:node:query",
|
||||||
run: () => ctx.onDetail(row.id!),
|
run: () => ctx.onDetail(row.id!),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -362,7 +362,7 @@ function buildSourceRowActions(
|
|||||||
label: "编辑",
|
label: "编辑",
|
||||||
artType: "edit",
|
artType: "edit",
|
||||||
icon: "ri:edit-2-line",
|
icon: "ri:edit-2-line",
|
||||||
perm: "module_task:workflow:node:update",
|
perm: "module_storage:workflow:node:update",
|
||||||
run: () => ctx.onEdit(row.id!),
|
run: () => ctx.onEdit(row.id!),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -370,7 +370,7 @@ function buildSourceRowActions(
|
|||||||
label: "删除",
|
label: "删除",
|
||||||
artType: "delete",
|
artType: "delete",
|
||||||
icon: "ri:delete-bin-4-line",
|
icon: "ri:delete-bin-4-line",
|
||||||
perm: "module_task:workflow:node:delete",
|
perm: "module_storage:workflow:node:delete",
|
||||||
run: () => ctx.onDelete(row.id!, row.name ?? ""),
|
run: () => ctx.onDelete(row.id!, row.name ?? ""),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -458,7 +458,7 @@ async function handleTest(id: number) {
|
|||||||
// 打开浏览:跳转到文件管理并携带存储源信息
|
// 打开浏览:跳转到文件管理并携带存储源信息
|
||||||
function handleOpen(row: SourceTable) {
|
function handleOpen(row: SourceTable) {
|
||||||
router.push({
|
router.push({
|
||||||
path: "/task/workflow/storage",
|
path: "/task/storage/workflow/browse",
|
||||||
query: { source_id: row.id, name: row.name, protocol: row.protocol },
|
query: { source_id: row.id, name: row.name, protocol: row.protocol },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
+3
-3
@@ -367,9 +367,9 @@ import TransferAPI, {
|
|||||||
type TransferStatus,
|
type TransferStatus,
|
||||||
type TransferTaskItem,
|
type TransferTaskItem,
|
||||||
type TransferTaskType,
|
type TransferTaskType,
|
||||||
} from "@/api/module_task/workflow/transfer";
|
} from "@/api/module_storage/transfer";
|
||||||
import NodeAPI, { type SourceTable } from "@/api/module_task/workflow/node";
|
import NodeAPI, { type SourceTable } from "@/api/module_storage/node";
|
||||||
import FlowAPI, { type FlowTable } from "@/api/module_task/workflow/flow";
|
import FlowAPI, { type FlowTable } from "@/api/module_storage/workflow";
|
||||||
import FaDescriptions, {
|
import FaDescriptions, {
|
||||||
type DescriptionsItem,
|
type DescriptionsItem,
|
||||||
} from "@/components/display/fa-descriptions/index.vue";
|
} from "@/components/display/fa-descriptions/index.vue";
|
||||||
+1
-1
@@ -83,7 +83,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Document, Folder, Refresh } from "@element-plus/icons-vue";
|
import { Document, Folder, Refresh } from "@element-plus/icons-vue";
|
||||||
import StorageAPI, { type StorageObject } from "@/api/module_task/workflow/storage";
|
import StorageAPI, { type StorageObject } from "@/api/module_storage/browse";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
+2
-2
@@ -84,12 +84,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import { Close, Folder, FolderOpened } from "@element-plus/icons-vue";
|
import { Close, Folder, FolderOpened } from "@element-plus/icons-vue";
|
||||||
import NodeAPI from "@/api/module_task/workflow/node";
|
import NodeAPI from "@/api/module_storage/node.ts";
|
||||||
import {
|
import {
|
||||||
protocolColor,
|
protocolColor,
|
||||||
protocolLabel as getProtocolLabel,
|
protocolLabel as getProtocolLabel,
|
||||||
protocolText as getProtocolText,
|
protocolText as getProtocolText,
|
||||||
} from "./protocol";
|
} from "./protocol.ts";
|
||||||
import FaFileBrowserDialog from "./FaFileBrowserDialog.vue";
|
import FaFileBrowserDialog from "./FaFileBrowserDialog.vue";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
+3
-3
@@ -256,11 +256,11 @@ import FaDynamicEdge from "./FaDynamicEdge.vue";
|
|||||||
import FaStorageNodePanel from "./FaStorageNodePanel.vue";
|
import FaStorageNodePanel from "./FaStorageNodePanel.vue";
|
||||||
import FaEdgeConfigPanel from "./FaEdgeConfigPanel.vue";
|
import FaEdgeConfigPanel from "./FaEdgeConfigPanel.vue";
|
||||||
import FaFileBrowserDialog from "./FaFileBrowserDialog.vue";
|
import FaFileBrowserDialog from "./FaFileBrowserDialog.vue";
|
||||||
import { protocolColor, protocolText } from "./protocol";
|
import { protocolColor, protocolText } from "./protocol.ts";
|
||||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
import type FaForm from "@/components/forms/fa-form/index.vue";
|
import type FaForm from "@/components/forms/fa-form/index.vue";
|
||||||
import FlowAPI, { type FlowForm, type FlowTable } from "@/api/module_task/workflow/flow";
|
import FlowAPI, { type FlowForm, type FlowTable } from "@/api/module_storage/workflow.ts";
|
||||||
import NodeAPI, { type SourceTable } from "@/api/module_task/workflow/node";
|
import NodeAPI, { type SourceTable } from "@/api/module_storage/node.ts";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "WorkflowFlowDesignDrawer",
|
name: "WorkflowFlowDesignDrawer",
|
||||||
+11
-11
@@ -27,8 +27,8 @@
|
|||||||
<template #left>
|
<template #left>
|
||||||
<FaTableHeaderLeft
|
<FaTableHeaderLeft
|
||||||
:remove-ids="selectedIds"
|
:remove-ids="selectedIds"
|
||||||
:perm-create="['module_task:workflow:flow:create']"
|
:perm-create="['module_storage:workflow:flow:create']"
|
||||||
:perm-delete="['module_task:workflow:flow:delete']"
|
:perm-delete="['module_storage:workflow:flow:delete']"
|
||||||
:delete-loading="batchDeleting"
|
:delete-loading="batchDeleting"
|
||||||
:create-loading="createLoading"
|
:create-loading="createLoading"
|
||||||
@add="handleAdd"
|
@add="handleAdd"
|
||||||
@@ -174,17 +174,17 @@
|
|||||||
import { ElButton, ElTag, ElTooltip } from "element-plus";
|
import { ElButton, ElTag, ElTooltip } from "element-plus";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { resolveStatusColumns, renderTableOperationCell, type TableOperationAction } from "@utils";
|
import { resolveStatusColumns, renderTableOperationCell, type TableOperationAction } from "@utils";
|
||||||
import FlowAPI, { type FlowTable } from "@/api/module_task/workflow/flow";
|
import FlowAPI, { type FlowTable } from "@/api/module_storage/workflow.ts";
|
||||||
import NodeAPI, { type SourceTable } from "@/api/module_task/workflow/node";
|
import NodeAPI, { type SourceTable } from "@/api/module_storage/node.ts";
|
||||||
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
||||||
import type FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
|
import type FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
|
||||||
import FaTableHeader from "@/components/tables/fa-table-header/index.vue";
|
import FaTableHeader from "@/components/tables/fa-table-header/index.vue";
|
||||||
import FaWorkflowDesignDrawer from "./components/FaWorkflowDesignDrawer.vue";
|
import FaWorkflowDesignDrawer from "./components/FaWorkflowDesignDrawer.vue";
|
||||||
import FaFileBrowserDialog from "./components/FaFileBrowserDialog.vue";
|
import FaFileBrowserDialog from "./components/FaFileBrowserDialog.vue";
|
||||||
import { protocolColor, protocolLabel } from "./components/protocol";
|
import { protocolColor, protocolLabel } from "./components/protocol.ts";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "WorkflowFlow",
|
name: "Workflow",
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -358,7 +358,7 @@ function buildFlowRowActions(
|
|||||||
key: "detail",
|
key: "detail",
|
||||||
label: "查看",
|
label: "查看",
|
||||||
artType: "view",
|
artType: "view",
|
||||||
perm: "module_task:workflow:flow:query",
|
perm: "module_storage:workflow:flow:query",
|
||||||
run: () => ctx.onDetail(row.id!),
|
run: () => ctx.onDetail(row.id!),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -366,7 +366,7 @@ function buildFlowRowActions(
|
|||||||
label: "设计",
|
label: "设计",
|
||||||
artType: "edit",
|
artType: "edit",
|
||||||
icon: "ri:pen-nib-line",
|
icon: "ri:pen-nib-line",
|
||||||
perm: "module_task:workflow:flow:update",
|
perm: "module_storage:workflow:flow:update",
|
||||||
run: () => ctx.onEdit(row.id!),
|
run: () => ctx.onEdit(row.id!),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -375,7 +375,7 @@ function buildFlowRowActions(
|
|||||||
artType: "view",
|
artType: "view",
|
||||||
icon: "ri:play-line",
|
icon: "ri:play-line",
|
||||||
iconColor: "var(--el-color-success)",
|
iconColor: "var(--el-color-success)",
|
||||||
perm: "module_task:workflow:transfer:create",
|
perm: "module_storage:workflow:transfer:create",
|
||||||
run: () => ctx.onExecute(row),
|
run: () => ctx.onExecute(row),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -383,7 +383,7 @@ function buildFlowRowActions(
|
|||||||
label: "删除",
|
label: "删除",
|
||||||
artType: "delete",
|
artType: "delete",
|
||||||
icon: "ri:delete-bin-4-line",
|
icon: "ri:delete-bin-4-line",
|
||||||
perm: "module_task:workflow:flow:delete",
|
perm: "module_storage:workflow:flow:delete",
|
||||||
run: () => ctx.onDelete(row.id!, row.name ?? ""),
|
run: () => ctx.onDelete(row.id!, row.name ?? ""),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -490,7 +490,7 @@ async function handleExecuteConfirm() {
|
|||||||
type: "primary",
|
type: "primary",
|
||||||
size: "small",
|
size: "small",
|
||||||
text: true,
|
text: true,
|
||||||
onClick: () => void router.push("/task/workflow/transfer"),
|
onClick: () => void router.push("/task/storage/workflow/transfer"),
|
||||||
},
|
},
|
||||||
() => "查看任务"
|
() => "查看任务"
|
||||||
),
|
),
|
||||||
Reference in New Issue
Block a user