Data Factory API 提供了与 n8n 工作流引擎的集成接口,支持工作流的查询、状态监控、执行记录查看和工作流触发等功能。
版本: v1.0
基础URL: /api/datafactory
内容类型: application/json
字符编码: UTF-8
所有API接口都遵循统一的响应格式:
{
"code": 200, // HTTP状态码
"message": "操作成功", // 操作结果消息
"data": {} // 返回的具体数据
}
| 状态码 | 含义 | 说明 |
|---|---|---|
| 200 | 成功 | 请求成功执行 |
| 400 | 请求错误 | 请求参数错误或缺失 |
| 401 | 认证失败 | n8n API Key 无效 |
| 403 | 权限不足 | 无权访问该资源 |
| 404 | 未找到 | 请求的资源不存在 |
| 500 | 服务器错误 | 服务器内部错误 |
| 503 | 服务不可用 | n8n 服务连接失败 |
接口描述: 获取 n8n 系统中的工作流列表,支持分页、搜索和状态过滤。
GET/workflowsContent-Type: application/json| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| page | int | 否 | 1 | 页码,从1开始 |
| page_size | int | 否 | 20 | 每页数量 |
| active | string | 否 | - | 过滤活跃状态:true/false |
| search | string | 否 | - | 按名称搜索关键词 |
| tags | string | 否 | - | 按标签过滤,多个标签用逗号分隔 |
{
"code": 200,
"message": "获取工作流列表成功",
"data": {
"items": [
{
"id": "1PCBqwesRXiFcfJ1",
"name": "Simple RAG",
"active": false,
"tags": ["AI", "RAG"],
"created_at": "2025-12-01 10:00:00",
"updated_at": "2025-12-03 03:14:49"
},
{
"id": "9w5VhCRlRrjFqDpX",
"name": "My workflow 2",
"active": true,
"tags": [],
"created_at": "2025-10-28 11:00:00",
"updated_at": "2025-10-28 12:06:12"
}
],
"total": 3,
"page": 1,
"page_size": 20,
"total_pages": 1
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
| items | array | 工作流列表 |
| items[].id | string | 工作流唯一标识 |
| items[].name | string | 工作流名称 |
| items[].active | boolean | 是否激活状态 |
| items[].tags | array | 标签列表 |
| items[].created_at | string | 创建时间 |
| items[].updated_at | string | 更新时间 |
| total | int | 总记录数 |
| page | int | 当前页码 |
| page_size | int | 每页数量 |
| total_pages | int | 总页数 |
<template>
<div class="workflow-list">
<!-- 搜索栏 -->
<div class="search-bar">
<el-input
v-model="searchKeyword"
placeholder="搜索工作流名称"
clearable
@clear="fetchWorkflows"
@keyup.enter="fetchWorkflows"
>
<template #append>
<el-button @click="fetchWorkflows">
<el-icon><Search /></el-icon>
</el-button>
</template>
</el-input>
<el-select v-model="activeFilter" placeholder="状态筛选" clearable @change="fetchWorkflows">
<el-option label="全部" value="" />
<el-option label="活跃" value="true" />
<el-option label="停用" value="false" />
</el-select>
</div>
<!-- 工作流表格 -->
<el-table :data="workflows" v-loading="loading" stripe>
<el-table-column prop="id" label="ID" width="180" />
<el-table-column prop="name" label="工作流名称" />
<el-table-column prop="active" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.active ? 'success' : 'info'">
{{ row.active ? '活跃' : '停用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="tags" label="标签" width="200">
<template #default="{ row }">
<el-tag v-for="tag in row.tags" :key="tag" size="small" class="tag-item">
{{ tag }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="updated_at" label="更新时间" width="180" />
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="viewDetail(row.id)">详情</el-button>
<el-button type="primary" link @click="viewStatus(row.id)">状态</el-button>
<el-button type="primary" link @click="viewExecutions(row.id)">执行记录</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="fetchWorkflows"
@current-change="fetchWorkflows"
/>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { Search } from '@element-plus/icons-vue'
import axios from 'axios'
const loading = ref(false)
const workflows = ref([])
const searchKeyword = ref('')
const activeFilter = ref('')
const pagination = reactive({
page: 1,
pageSize: 20,
total: 0
})
const fetchWorkflows = async () => {
loading.value = true
try {
const params = {
page: pagination.page,
page_size: pagination.pageSize
}
if (searchKeyword.value) {
params.search = searchKeyword.value
}
if (activeFilter.value) {
params.active = activeFilter.value
}
const response = await axios.get('/api/datafactory/workflows', { params })
if (response.data.code === 200) {
workflows.value = response.data.data.items
pagination.total = response.data.data.total
} else {
ElMessage.error(response.data.message)
}
} catch (error) {
ElMessage.error('获取工作流列表失败')
console.error(error)
} finally {
loading.value = false
}
}
const viewDetail = (id) => {
// 跳转到详情页
router.push(`/datafactory/workflow/${id}`)
}
const viewStatus = (id) => {
// 跳转到状态页
router.push(`/datafactory/workflow/${id}/status`)
}
const viewExecutions = (id) => {
// 跳转到执行记录页
router.push(`/datafactory/workflow/${id}/executions`)
}
onMounted(() => {
fetchWorkflows()
})
</script>
<style scoped>
.workflow-list {
padding: 20px;
}
.search-bar {
display: flex;
gap: 16px;
margin-bottom: 20px;
}
.search-bar .el-input {
width: 300px;
}
.tag-item {
margin-right: 4px;
}
.el-pagination {
margin-top: 20px;
justify-content: flex-end;
}
</style>
接口描述: 根据工作流ID获取详细信息,包括节点列表和配置。
GET/workflows/{workflow_id}Content-Type: application/json| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| workflow_id | string | 是 | 工作流ID |
{
"code": 200,
"message": "获取工作流详情成功",
"data": {
"id": "1PCBqwesRXiFcfJ1",
"name": "Simple RAG",
"active": false,
"tags": ["AI", "RAG"],
"created_at": "2025-12-01 10:00:00",
"updated_at": "2025-12-03 03:14:49",
"nodes_count": 5,
"nodes": [
{
"id": "node-1",
"name": "Start",
"type": "n8n-nodes-base.manualTrigger",
"type_version": 1,
"position": [250, 300],
"disabled": false
},
{
"id": "node-2",
"name": "HTTP Request",
"type": "n8n-nodes-base.httpRequest",
"type_version": 4,
"position": [450, 300],
"disabled": false
}
],
"settings": {
"executionOrder": "v1"
}
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 工作流ID |
| name | string | 工作流名称 |
| active | boolean | 是否激活 |
| tags | array | 标签列表 |
| created_at | string | 创建时间 |
| updated_at | string | 更新时间 |
| nodes_count | int | 节点数量 |
| nodes | array | 节点列表 |
| nodes[].id | string | 节点ID |
| nodes[].name | string | 节点名称 |
| nodes[].type | string | 节点类型 |
| nodes[].type_version | int | 节点版本 |
| nodes[].position | array | 节点位置 [x, y] |
| nodes[].disabled | boolean | 是否禁用 |
| settings | object | 工作流设置 |
<template>
<div class="workflow-detail" v-loading="loading">
<el-page-header @back="goBack" :title="workflow.name">
<template #content>
<div class="header-content">
<el-tag :type="workflow.active ? 'success' : 'info'" size="large">
{{ workflow.active ? '活跃' : '停用' }}
</el-tag>
</div>
</template>
<template #extra>
<el-button-group>
<el-button
v-if="!workflow.active"
type="success"
@click="activateWorkflow"
>
激活
</el-button>
<el-button
v-else
type="warning"
@click="deactivateWorkflow"
>
停用
</el-button>
</el-button-group>
</template>
</el-page-header>
<el-descriptions :column="2" border class="detail-info">
<el-descriptions-item label="工作流ID">{{ workflow.id }}</el-descriptions-item>
<el-descriptions-item label="节点数量">{{ workflow.nodes_count }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ workflow.created_at }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ workflow.updated_at }}</el-descriptions-item>
<el-descriptions-item label="标签" :span="2">
<el-tag v-for="tag in workflow.tags" :key="tag" class="tag-item">
{{ tag }}
</el-tag>
<span v-if="!workflow.tags?.length">无</span>
</el-descriptions-item>
</el-descriptions>
<el-card class="nodes-card">
<template #header>
<span>节点列表</span>
</template>
<el-table :data="workflow.nodes" stripe>
<el-table-column prop="name" label="节点名称" />
<el-table-column prop="type" label="节点类型" />
<el-table-column prop="type_version" label="版本" width="80" />
<el-table-column prop="disabled" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.disabled ? 'danger' : 'success'" size="small">
{{ row.disabled ? '禁用' : '启用' }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import axios from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'
const route = useRoute()
const router = useRouter()
const loading = ref(false)
const workflow = ref({})
const workflowId = route.params.id
const fetchWorkflowDetail = async () => {
loading.value = true
try {
const response = await axios.get(`/api/datafactory/workflows/${workflowId}`)
if (response.data.code === 200) {
workflow.value = response.data.data
} else {
ElMessage.error(response.data.message)
}
} catch (error) {
ElMessage.error('获取工作流详情失败')
console.error(error)
} finally {
loading.value = false
}
}
const activateWorkflow = async () => {
try {
await ElMessageBox.confirm('确定要激活该工作流吗?', '提示', {
type: 'warning'
})
const response = await axios.post(`/api/datafactory/workflows/${workflowId}/activate`)
if (response.data.code === 200) {
ElMessage.success('工作流已激活')
fetchWorkflowDetail()
} else {
ElMessage.error(response.data.message)
}
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('激活失败')
}
}
}
const deactivateWorkflow = async () => {
try {
await ElMessageBox.confirm('确定要停用该工作流吗?', '提示', {
type: 'warning'
})
const response = await axios.post(`/api/datafactory/workflows/${workflowId}/deactivate`)
if (response.data.code === 200) {
ElMessage.success('工作流已停用')
fetchWorkflowDetail()
} else {
ElMessage.error(response.data.message)
}
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('停用失败')
}
}
}
const goBack = () => {
router.back()
}
onMounted(() => {
fetchWorkflowDetail()
})
</script>
<style scoped>
.workflow-detail {
padding: 20px;
}
.header-content {
display: flex;
align-items: center;
}
.detail-info {
margin: 20px 0;
}
.nodes-card {
margin-top: 20px;
}
.tag-item {
margin-right: 8px;
}
</style>
接口描述: 获取工作流的运行状态和最近执行情况统计。
GET/workflows/{workflow_id}/statusContent-Type: application/json| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| workflow_id | string | 是 | 工作流ID |
{
"code": 200,
"message": "获取工作流状态成功",
"data": {
"workflow_id": "9w5VhCRlRrjFqDpX",
"name": "My workflow 2",
"active": true,
"status": "active",
"status_label": "运行中",
"recent_executions": {
"total": 5,
"success": 4,
"error": 1
},
"last_execution": {
"id": "12345",
"workflow_id": "9w5VhCRlRrjFqDpX",
"workflow_name": "My workflow 2",
"status": "success",
"status_label": "成功",
"mode": "trigger",
"started_at": "2025-12-24 10:30:00",
"finished_at": "2025-12-24 10:30:05",
"retry_of": null,
"retry_success_id": null
},
"updated_at": "2025-10-28 12:06:12"
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
| workflow_id | string | 工作流ID |
| name | string | 工作流名称 |
| active | boolean | 是否激活 |
| status | string | 状态码:active/inactive |
| status_label | string | 状态显示文本 |
| recent_executions | object | 最近执行统计 |
| recent_executions.total | int | 最近执行总数 |
| recent_executions.success | int | 成功次数 |
| recent_executions.error | int | 失败次数 |
| last_execution | object | 最后一次执行信息(可能为null) |
| updated_at | string | 更新时间 |
<template>
<div class="workflow-status" v-loading="loading">
<el-card class="status-card">
<template #header>
<div class="card-header">
<span>{{ status.name }}</span>
<el-tag :type="status.active ? 'success' : 'info'" size="large">
{{ status.status_label }}
</el-tag>
</div>
</template>
<el-row :gutter="20">
<el-col :span="8">
<el-statistic title="最近执行总数" :value="status.recent_executions?.total || 0" />
</el-col>
<el-col :span="8">
<el-statistic title="成功次数" :value="status.recent_executions?.success || 0">
<template #suffix>
<el-icon color="#67C23A"><SuccessFilled /></el-icon>
</template>
</el-statistic>
</el-col>
<el-col :span="8">
<el-statistic title="失败次数" :value="status.recent_executions?.error || 0">
<template #suffix>
<el-icon color="#F56C6C"><CircleCloseFilled /></el-icon>
</template>
</el-statistic>
</el-col>
</el-row>
</el-card>
<el-card class="last-execution-card" v-if="status.last_execution">
<template #header>
<span>最后一次执行</span>
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="执行ID">
{{ status.last_execution.id }}
</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="getStatusType(status.last_execution.status)">
{{ status.last_execution.status_label }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="开始时间">
{{ status.last_execution.started_at }}
</el-descriptions-item>
<el-descriptions-item label="结束时间">
{{ status.last_execution.finished_at }}
</el-descriptions-item>
<el-descriptions-item label="执行模式">
{{ status.last_execution.mode }}
</el-descriptions-item>
</el-descriptions>
</el-card>
<el-empty v-else description="暂无执行记录" />
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import { SuccessFilled, CircleCloseFilled } from '@element-plus/icons-vue'
import axios from 'axios'
const route = useRoute()
const loading = ref(false)
const status = ref({})
let refreshTimer = null
const workflowId = route.params.id
const fetchStatus = async () => {
loading.value = true
try {
const response = await axios.get(`/api/datafactory/workflows/${workflowId}/status`)
if (response.data.code === 200) {
status.value = response.data.data
}
} catch (error) {
console.error('获取状态失败', error)
} finally {
loading.value = false
}
}
const getStatusType = (statusCode) => {
const types = {
success: 'success',
error: 'danger',
waiting: 'warning',
running: 'primary'
}
return types[statusCode] || 'info'
}
onMounted(() => {
fetchStatus()
// 每30秒自动刷新状态
refreshTimer = setInterval(fetchStatus, 30000)
})
onUnmounted(() => {
if (refreshTimer) {
clearInterval(refreshTimer)
}
})
</script>
<style scoped>
.workflow-status {
padding: 20px;
}
.status-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.last-execution-card {
margin-top: 20px;
}
</style>
接口描述: 激活指定的工作流。
POST/workflows/{workflow_id}/activateContent-Type: application/json| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| workflow_id | string | 是 | 工作流ID |
{
"code": 200,
"message": "工作流激活成功",
"data": {
"workflow_id": "9w5VhCRlRrjFqDpX",
"active": true,
"message": "工作流已激活"
}
}
接口描述: 停用指定的工作流。
POST/workflows/{workflow_id}/deactivateContent-Type: application/json| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| workflow_id | string | 是 | 工作流ID |
{
"code": 200,
"message": "工作流停用成功",
"data": {
"workflow_id": "9w5VhCRlRrjFqDpX",
"active": false,
"message": "工作流已停用"
}
}
接口描述: 获取指定工作流的执行记录列表。
GET/workflows/{workflow_id}/executionsContent-Type: application/json| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| workflow_id | string | 是 | 工作流ID |
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| page | int | 否 | 1 | 页码 |
| page_size | int | 否 | 20 | 每页数量 |
| status | string | 否 | - | 状态过滤:success/error/waiting |
{
"code": 200,
"message": "获取执行记录列表成功",
"data": {
"items": [
{
"id": "12345",
"workflow_id": "9w5VhCRlRrjFqDpX",
"workflow_name": "My workflow 2",
"status": "success",
"status_label": "成功",
"mode": "trigger",
"started_at": "2025-12-24 10:30:00",
"finished_at": "2025-12-24 10:30:05",
"retry_of": null,
"retry_success_id": null
},
{
"id": "12344",
"workflow_id": "9w5VhCRlRrjFqDpX",
"workflow_name": "My workflow 2",
"status": "error",
"status_label": "失败",
"mode": "manual",
"started_at": "2025-12-24 09:15:00",
"finished_at": "2025-12-24 09:15:03",
"retry_of": null,
"retry_success_id": null
}
],
"total": 25,
"page": 1,
"page_size": 20,
"total_pages": 2
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
| items[].id | string | 执行记录ID |
| items[].workflow_id | string | 工作流ID |
| items[].workflow_name | string | 工作流名称 |
| items[].status | string | 状态码:success/error/waiting/running |
| items[].status_label | string | 状态显示文本 |
| items[].mode | string | 执行模式:trigger/manual/webhook等 |
| items[].started_at | string | 开始时间 |
| items[].finished_at | string | 结束时间 |
| items[].retry_of | string | 重试来源执行ID(可能为null) |
| items[].retry_success_id | string | 重试成功执行ID(可能为null) |
<template>
<div class="execution-list">
<!-- 筛选栏 -->
<div class="filter-bar">
<el-select v-model="statusFilter" placeholder="状态筛选" clearable @change="fetchExecutions">
<el-option label="全部" value="" />
<el-option label="成功" value="success" />
<el-option label="失败" value="error" />
<el-option label="等待中" value="waiting" />
</el-select>
<el-button type="primary" @click="fetchExecutions">
<el-icon><Refresh /></el-icon>
刷新
</el-button>
</div>
<!-- 执行记录表格 -->
<el-table :data="executions" v-loading="loading" stripe>
<el-table-column prop="id" label="执行ID" width="120" />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)">
{{ row.status_label }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="mode" label="执行模式" width="100" />
<el-table-column prop="started_at" label="开始时间" width="180" />
<el-table-column prop="finished_at" label="结束时间" width="180" />
<el-table-column label="耗时" width="100">
<template #default="{ row }">
{{ calculateDuration(row.started_at, row.finished_at) }}
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="viewExecutionDetail(row.id)">
详情
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next"
@size-change="fetchExecutions"
@current-change="fetchExecutions"
/>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Refresh } from '@element-plus/icons-vue'
import axios from 'axios'
import dayjs from 'dayjs'
const route = useRoute()
const router = useRouter()
const loading = ref(false)
const executions = ref([])
const statusFilter = ref('')
const workflowId = route.params.id
const pagination = reactive({
page: 1,
pageSize: 20,
total: 0
})
const fetchExecutions = async () => {
loading.value = true
try {
const params = {
page: pagination.page,
page_size: pagination.pageSize
}
if (statusFilter.value) {
params.status = statusFilter.value
}
const response = await axios.get(
`/api/datafactory/workflows/${workflowId}/executions`,
{ params }
)
if (response.data.code === 200) {
executions.value = response.data.data.items
pagination.total = response.data.data.total
}
} catch (error) {
console.error('获取执行记录失败', error)
} finally {
loading.value = false
}
}
const getStatusType = (status) => {
const types = {
success: 'success',
error: 'danger',
waiting: 'warning',
running: 'primary'
}
return types[status] || 'info'
}
const calculateDuration = (start, end) => {
if (!start || !end) return '-'
const duration = dayjs(end).diff(dayjs(start), 'second')
if (duration < 60) return `${duration}秒`
if (duration < 3600) return `${Math.floor(duration / 60)}分${duration % 60}秒`
return `${Math.floor(duration / 3600)}时${Math.floor((duration % 3600) / 60)}分`
}
const viewExecutionDetail = (executionId) => {
router.push(`/datafactory/executions/${executionId}`)
}
onMounted(() => {
fetchExecutions()
})
</script>
<style scoped>
.execution-list {
padding: 20px;
}
.filter-bar {
display: flex;
gap: 16px;
margin-bottom: 20px;
}
.el-pagination {
margin-top: 20px;
justify-content: flex-end;
}
</style>
接口描述: 获取所有工作流的执行记录列表(不限定工作流)。
GET/executionsContent-Type: application/json| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| page | int | 否 | 1 | 页码 |
| page_size | int | 否 | 20 | 每页数量 |
| workflow_id | string | 否 | - | 按工作流ID过滤 |
| status | string | 否 | - | 状态过滤:success/error/waiting |
响应格式与 /workflows/{workflow_id}/executions 相同。
接口描述: 获取单次执行的详细信息,包括各节点的执行结果。
GET/executions/{execution_id}Content-Type: application/json| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| execution_id | string | 是 | 执行ID |
{
"code": 200,
"message": "获取执行详情成功",
"data": {
"id": "12345",
"workflow_id": "9w5VhCRlRrjFqDpX",
"workflow_name": "My workflow 2",
"status": "success",
"status_label": "成功",
"mode": "trigger",
"started_at": "2025-12-24 10:30:00",
"finished_at": "2025-12-24 10:30:05",
"retry_of": null,
"retry_success_id": null,
"node_results": [
{
"node_name": "Start",
"start_time": "2025-12-24 10:30:00",
"execution_time": 10,
"source": [],
"data": {
"main": [[{"json": {"started": true}}]]
}
},
{
"node_name": "HTTP Request",
"start_time": "2025-12-24 10:30:01",
"execution_time": 3500,
"source": [{"previousNode": "Start"}],
"data": {
"main": [[{"json": {"response": "OK"}}]]
}
}
],
"error": null
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
| node_results | array | 各节点执行结果列表 |
| node_results[].node_name | string | 节点名称 |
| node_results[].start_time | string | 节点开始执行时间 |
| node_results[].execution_time | int | 执行耗时(毫秒) |
| node_results[].source | array | 数据来源节点 |
| node_results[].data | object | 节点输出数据 |
| error | object | 错误信息(成功时为null) |
<template>
<div class="execution-detail" v-loading="loading">
<el-page-header @back="goBack" title="执行详情">
<template #content>
<el-tag :type="getStatusType(execution.status)" size="large">
{{ execution.status_label }}
</el-tag>
</template>
</el-page-header>
<el-descriptions :column="2" border class="execution-info">
<el-descriptions-item label="执行ID">{{ execution.id }}</el-descriptions-item>
<el-descriptions-item label="工作流">{{ execution.workflow_name }}</el-descriptions-item>
<el-descriptions-item label="执行模式">{{ execution.mode }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="getStatusType(execution.status)">
{{ execution.status_label }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ execution.started_at }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ execution.finished_at }}</el-descriptions-item>
</el-descriptions>
<!-- 错误信息 -->
<el-alert
v-if="execution.error"
:title="execution.error.message || '执行出错'"
type="error"
:description="JSON.stringify(execution.error, null, 2)"
show-icon
class="error-alert"
/>
<!-- 节点执行结果 -->
<el-card class="node-results-card">
<template #header>
<span>节点执行结果</span>
</template>
<el-timeline>
<el-timeline-item
v-for="(node, index) in execution.node_results"
:key="index"
:type="getNodeStatusType(node)"
:timestamp="node.start_time"
>
<el-card shadow="hover">
<div class="node-header">
<span class="node-name">{{ node.node_name }}</span>
<el-tag size="small">{{ node.execution_time }}ms</el-tag>
</div>
<div class="node-data">
<el-collapse>
<el-collapse-item title="输出数据">
<pre>{{ JSON.stringify(node.data, null, 2) }}</pre>
</el-collapse-item>
</el-collapse>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import axios from 'axios'
const route = useRoute()
const router = useRouter()
const loading = ref(false)
const execution = ref({})
const executionId = route.params.id
const fetchExecutionDetail = async () => {
loading.value = true
try {
const response = await axios.get(`/api/datafactory/executions/${executionId}`)
if (response.data.code === 200) {
execution.value = response.data.data
}
} catch (error) {
console.error('获取执行详情失败', error)
} finally {
loading.value = false
}
}
const getStatusType = (status) => {
const types = {
success: 'success',
error: 'danger',
waiting: 'warning',
running: 'primary'
}
return types[status] || 'info'
}
const getNodeStatusType = (node) => {
// 根据节点执行情况判断状态
if (node.error) return 'danger'
return 'success'
}
const goBack = () => {
router.back()
}
onMounted(() => {
fetchExecutionDetail()
})
</script>
<style scoped>
.execution-detail {
padding: 20px;
}
.execution-info {
margin: 20px 0;
}
.error-alert {
margin: 20px 0;
}
.node-results-card {
margin-top: 20px;
}
.node-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.node-name {
font-weight: bold;
}
.node-data pre {
background: #f5f5f5;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
font-size: 12px;
}
</style>
接口描述: 通过 Webhook 触发工作流执行。
POST/workflows/{workflow_id}/executeContent-Type: application/json| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| workflow_id | string | 是 | 工作流ID |
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| webhook_path | string | 是 | Webhook 路径(工作流中配置的路径) |
| data | object | 否 | 传递给工作流的数据 |
{
"webhook_path": "my-webhook-path",
"data": {
"user_id": 12345,
"action": "process",
"params": {
"key1": "value1",
"key2": "value2"
}
}
}
成功响应:
{
"code": 200,
"message": "工作流触发成功",
"data": {
"success": true,
"message": "工作流已通过 Webhook 触发",
"workflow_id": "9w5VhCRlRrjFqDpX",
"response": {
"result": "OK"
}
}
}
缺少 webhook_path 响应:
{
"code": 400,
"message": "请提供 Webhook 路径以触发工作流",
"data": {
"success": false,
"message": "请提供 Webhook 路径以触发工作流",
"workflow_id": "9w5VhCRlRrjFqDpX"
}
}
<template>
<div class="trigger-workflow">
<el-card>
<template #header>
<span>触发工作流执行</span>
</template>
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
<el-form-item label="Webhook路径" prop="webhook_path">
<el-input v-model="form.webhook_path" placeholder="请输入Webhook路径" />
<div class="form-tip">工作流中 Webhook 节点配置的路径</div>
</el-form-item>
<el-form-item label="传递数据">
<el-input
v-model="form.dataJson"
type="textarea"
:rows="8"
placeholder="请输入JSON格式的数据(可选)"
/>
<div class="form-tip">JSON格式,例如:{"key": "value"}</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="triggerWorkflow" :loading="loading">
触发执行
</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 执行结果 -->
<el-card v-if="result" class="result-card">
<template #header>
<span>执行结果</span>
</template>
<el-result
:icon="result.success ? 'success' : 'error'"
:title="result.success ? '触发成功' : '触发失败'"
:sub-title="result.message"
>
<template #extra>
<pre v-if="result.response">{{ JSON.stringify(result.response, null, 2) }}</pre>
</template>
</el-result>
</el-card>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { useRoute } from 'vue-router'
import axios from 'axios'
import { ElMessage } from 'element-plus'
const route = useRoute()
const formRef = ref()
const loading = ref(false)
const result = ref(null)
const workflowId = route.params.id
const form = reactive({
webhook_path: '',
dataJson: ''
})
const rules = {
webhook_path: [
{ required: true, message: '请输入Webhook路径', trigger: 'blur' }
]
}
const triggerWorkflow = async () => {
try {
await formRef.value.validate()
} catch {
return
}
// 解析JSON数据
let data = {}
if (form.dataJson) {
try {
data = JSON.parse(form.dataJson)
} catch (e) {
ElMessage.error('数据格式错误,请输入有效的JSON')
return
}
}
loading.value = true
result.value = null
try {
const response = await axios.post(`/api/datafactory/workflows/${workflowId}/execute`, {
webhook_path: form.webhook_path,
data: data
})
if (response.data.code === 200) {
result.value = response.data.data
ElMessage.success('工作流触发成功')
} else {
result.value = {
success: false,
message: response.data.message
}
ElMessage.error(response.data.message)
}
} catch (error) {
result.value = {
success: false,
message: error.message || '触发失败'
}
ElMessage.error('触发工作流失败')
} finally {
loading.value = false
}
}
const resetForm = () => {
formRef.value?.resetFields()
result.value = null
}
</script>
<style scoped>
.trigger-workflow {
padding: 20px;
}
.form-tip {
font-size: 12px;
color: #909399;
margin-top: 4px;
}
.result-card {
margin-top: 20px;
}
.result-card pre {
background: #f5f5f5;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
text-align: left;
}
</style>
接口描述: 检查 n8n 服务的连接状态。
GET/healthContent-Type: application/json连接正常:
{
"code": 200,
"message": "n8n 服务连接正常",
"data": {
"status": "healthy",
"connected": true,
"api_url": "https://n8n.citupro.com"
}
}
连接失败:
{
"code": 503,
"message": "n8n 服务连接失败: n8n API 认证失败,请检查 API Key 配置",
"data": {
"status": "unhealthy",
"connected": false,
"error": "n8n API 认证失败,请检查 API Key 配置",
"api_url": "https://n8n.citupro.com"
}
}
<template>
<div class="health-check">
<el-card>
<template #header>
<div class="card-header">
<span>n8n 服务状态</span>
<el-button type="primary" size="small" @click="checkHealth" :loading="loading">
<el-icon><Refresh /></el-icon>
检查
</el-button>
</div>
</template>
<el-result
:icon="health.connected ? 'success' : 'error'"
:title="health.connected ? '服务正常' : '服务异常'"
>
<template #sub-title>
<p>API地址: {{ health.api_url }}</p>
<p v-if="health.error" class="error-text">错误: {{ health.error }}</p>
</template>
</el-result>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { Refresh } from '@element-plus/icons-vue'
import axios from 'axios'
const loading = ref(false)
const health = ref({
connected: false,
api_url: ''
})
const checkHealth = async () => {
loading.value = true
try {
const response = await axios.get('/api/datafactory/health')
health.value = response.data.data
} catch (error) {
health.value = {
connected: false,
error: '无法连接到服务'
}
} finally {
loading.value = false
}
}
onMounted(() => {
checkHealth()
})
</script>
<style scoped>
.health-check {
padding: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.error-text {
color: #F56C6C;
}
</style>
建议将 API 调用封装成独立的服务模块,便于统一管理和维护。
import axios from 'axios'
const BASE_URL = '/api/datafactory'
export const datafactoryApi = {
// 工作流相关
getWorkflows(params) {
return axios.get(`${BASE_URL}/workflows`, { params })
},
getWorkflow(workflowId) {
return axios.get(`${BASE_URL}/workflows/${workflowId}`)
},
getWorkflowStatus(workflowId) {
return axios.get(`${BASE_URL}/workflows/${workflowId}/status`)
},
activateWorkflow(workflowId) {
return axios.post(`${BASE_URL}/workflows/${workflowId}/activate`)
},
deactivateWorkflow(workflowId) {
return axios.post(`${BASE_URL}/workflows/${workflowId}/deactivate`)
},
// 执行记录相关
getWorkflowExecutions(workflowId, params) {
return axios.get(`${BASE_URL}/workflows/${workflowId}/executions`, { params })
},
getAllExecutions(params) {
return axios.get(`${BASE_URL}/executions`, { params })
},
getExecution(executionId) {
return axios.get(`${BASE_URL}/executions/${executionId}`)
},
// 触发执行
triggerWorkflow(workflowId, data) {
return axios.post(`${BASE_URL}/workflows/${workflowId}/execute`, data)
},
// 健康检查
healthCheck() {
return axios.get(`${BASE_URL}/health`)
}
}
import { datafactoryApi } from '@/api/datafactory'
// 获取工作流列表
const response = await datafactoryApi.getWorkflows({
page: 1,
page_size: 20,
active: 'true'
})
// 触发工作流
const result = await datafactoryApi.triggerWorkflow('workflow-id', {
webhook_path: 'my-webhook',
data: { key: 'value' }
})
export default [
{
path: '/datafactory',
name: 'DataFactory',
component: () => import('@/views/datafactory/index.vue'),
meta: { title: '数据工厂' },
children: [
{
path: '',
name: 'WorkflowList',
component: () => import('@/views/datafactory/WorkflowList.vue'),
meta: { title: '工作流列表' }
},
{
path: 'workflow/:id',
name: 'WorkflowDetail',
component: () => import('@/views/datafactory/WorkflowDetail.vue'),
meta: { title: '工作流详情' }
},
{
path: 'workflow/:id/status',
name: 'WorkflowStatus',
component: () => import('@/views/datafactory/WorkflowStatus.vue'),
meta: { title: '工作流状态' }
},
{
path: 'workflow/:id/executions',
name: 'WorkflowExecutions',
component: () => import('@/views/datafactory/ExecutionList.vue'),
meta: { title: '执行记录' }
},
{
path: 'workflow/:id/trigger',
name: 'TriggerWorkflow',
component: () => import('@/views/datafactory/TriggerWorkflow.vue'),
meta: { title: '触发执行' }
},
{
path: 'executions/:id',
name: 'ExecutionDetail',
component: () => import('@/views/datafactory/ExecutionDetail.vue'),
meta: { title: '执行详情' }
}
]
}
]
如有问题请联系后端开发团队或查看详细的 API 文档。
文档版本: v1.0
最后更新: 2025-12-24