Fix duplicate dependency in requirements.txt
This commit is contained in:
Binary file not shown.
BIN
backend/__pycache__/workflow_manager.cpython-312.pyc
Normal file
BIN
backend/__pycache__/workflow_manager.cpython-312.pyc
Normal file
Binary file not shown.
543
backend/main.py
543
backend/main.py
@@ -3414,74 +3414,21 @@ async def system_status():
|
||||
|
||||
# ==================== Phase 7: Workflow Automation Endpoints ====================
|
||||
|
||||
class WorkflowCreateRequest(BaseModel):
|
||||
"""创建工作流请求"""
|
||||
name: str = Field(..., description="工作流名称")
|
||||
task_type: str = Field(..., description="工作流类型: auto_analyze, auto_align, auto_relation, custom")
|
||||
config: Dict = Field(default={}, description="工作流配置")
|
||||
trigger_type: str = Field(default="manual", description="触发方式: schedule, event, manual")
|
||||
schedule: Optional[str] = Field(default=None, description="定时规则 (Cron 表达式或间隔秒数)")
|
||||
project_id: Optional[str] = Field(default=None, description="关联项目ID")
|
||||
enabled: bool = Field(default=True, description="是否启用")
|
||||
# Workflow Manager singleton
|
||||
_workflow_manager = None
|
||||
|
||||
|
||||
class WorkflowResponse(BaseModel):
|
||||
"""工作流响应"""
|
||||
id: str
|
||||
name: str
|
||||
task_type: str
|
||||
config: Dict
|
||||
trigger_type: str
|
||||
schedule: Optional[str]
|
||||
project_id: Optional[str]
|
||||
enabled: bool
|
||||
created_at: str
|
||||
updated_at: str
|
||||
last_run_at: Optional[str]
|
||||
run_count: int
|
||||
fail_count: int
|
||||
|
||||
|
||||
class WorkflowLogResponse(BaseModel):
|
||||
"""工作流日志响应"""
|
||||
id: str
|
||||
workflow_id: str
|
||||
status: str
|
||||
started_at: str
|
||||
completed_at: Optional[str]
|
||||
result: Optional[Dict]
|
||||
error_message: Optional[str]
|
||||
created_at: str
|
||||
|
||||
|
||||
class WebhookCreateRequest(BaseModel):
|
||||
"""创建 Webhook 请求"""
|
||||
name: str = Field(..., description="Webhook 名称")
|
||||
webhook_type: str = Field(..., description="类型: feishu, dingtalk, slack, custom")
|
||||
url: str = Field(..., description="Webhook URL")
|
||||
secret: Optional[str] = Field(default=None, description="密钥")
|
||||
headers: Dict = Field(default={}, description="自定义请求头")
|
||||
project_id: Optional[str] = Field(default=None, description="关联项目ID")
|
||||
events: List[str] = Field(default=[], description="订阅的事件列表")
|
||||
enabled: bool = Field(default=True, description="是否启用")
|
||||
|
||||
|
||||
class WebhookResponse(BaseModel):
|
||||
"""Webhook 响应"""
|
||||
id: str
|
||||
name: str
|
||||
webhook_type: str
|
||||
url: str
|
||||
headers: Dict
|
||||
project_id: Optional[str]
|
||||
events: List[str]
|
||||
enabled: bool
|
||||
created_at: str
|
||||
updated_at: str
|
||||
def get_workflow_manager_instance():
|
||||
global _workflow_manager
|
||||
if _workflow_manager is None and WORKFLOW_AVAILABLE and DB_AVAILABLE:
|
||||
from workflow_manager import WorkflowManager
|
||||
db = get_db_manager()
|
||||
_workflow_manager = WorkflowManager(db)
|
||||
_workflow_manager.start()
|
||||
return _workflow_manager
|
||||
|
||||
|
||||
@app.post("/api/v1/workflows", response_model=WorkflowResponse, tags=["Workflows"])
|
||||
async def create_workflow(request: WorkflowCreateRequest, _=Depends(verify_api_key)):
|
||||
async def create_workflow_endpoint(request: WorkflowCreate, _=Depends(verify_api_key)):
|
||||
"""
|
||||
创建工作流
|
||||
|
||||
@@ -3489,90 +3436,112 @@ async def create_workflow(request: WorkflowCreateRequest, _=Depends(verify_api_k
|
||||
- **auto_analyze**: 自动分析新上传的文件
|
||||
- **auto_align**: 自动实体对齐
|
||||
- **auto_relation**: 自动关系发现
|
||||
- **scheduled_report**: 定时报告
|
||||
- **custom**: 自定义工作流
|
||||
|
||||
触发方式:
|
||||
调度类型:
|
||||
- **manual**: 手动触发
|
||||
- **schedule**: 定时触发 (需要设置 schedule 字段)
|
||||
- **event**: 事件触发
|
||||
- **cron**: Cron 表达式调度
|
||||
- **interval**: 间隔调度(分钟数)
|
||||
|
||||
定时规则示例:
|
||||
- `cron:0 9 * * *` - 每天上午9点
|
||||
- `interval:3600` - 每小时执行一次
|
||||
- `60` - 每60分钟执行一次
|
||||
- `0 9 * * *` - 每天上午9点 (cron)
|
||||
- `60` - 每60分钟执行一次 (interval)
|
||||
"""
|
||||
if not WORKFLOW_AVAILABLE:
|
||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||
|
||||
manager = get_workflow_manager()
|
||||
manager = get_workflow_manager_instance()
|
||||
|
||||
try:
|
||||
workflow = await manager.create_workflow(
|
||||
workflow = Workflow(
|
||||
id=str(uuid.uuid4())[:8],
|
||||
name=request.name,
|
||||
task_type=WorkflowType(request.task_type),
|
||||
config=request.config,
|
||||
trigger_type=WorkflowTrigger(request.trigger_type),
|
||||
schedule=request.schedule,
|
||||
description=request.description,
|
||||
workflow_type=request.workflow_type,
|
||||
project_id=request.project_id,
|
||||
enabled=request.enabled
|
||||
schedule=request.schedule,
|
||||
schedule_type=request.schedule_type,
|
||||
config=request.config,
|
||||
webhook_ids=request.webhook_ids
|
||||
)
|
||||
|
||||
created = manager.create_workflow(workflow)
|
||||
|
||||
return WorkflowResponse(
|
||||
id=workflow.id,
|
||||
name=workflow.name,
|
||||
task_type=workflow.task_type.value,
|
||||
config=workflow.config,
|
||||
trigger_type=workflow.trigger_type.value,
|
||||
schedule=workflow.schedule,
|
||||
project_id=workflow.project_id,
|
||||
enabled=workflow.enabled,
|
||||
created_at=workflow.created_at.isoformat(),
|
||||
updated_at=workflow.updated_at.isoformat(),
|
||||
last_run_at=workflow.last_run_at.isoformat() if workflow.last_run_at else None,
|
||||
run_count=workflow.run_count,
|
||||
fail_count=workflow.fail_count
|
||||
id=created.id,
|
||||
name=created.name,
|
||||
description=created.description,
|
||||
workflow_type=created.workflow_type,
|
||||
project_id=created.project_id,
|
||||
status=created.status,
|
||||
schedule=created.schedule,
|
||||
schedule_type=created.schedule_type,
|
||||
config=created.config,
|
||||
webhook_ids=created.webhook_ids,
|
||||
is_active=created.is_active,
|
||||
created_at=created.created_at,
|
||||
updated_at=created.updated_at,
|
||||
last_run_at=created.last_run_at,
|
||||
next_run_at=created.next_run_at,
|
||||
run_count=created.run_count,
|
||||
success_count=created.success_count,
|
||||
fail_count=created.fail_count
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/v1/workflows", response_model=List[WorkflowResponse], tags=["Workflows"])
|
||||
async def list_workflows(project_id: Optional[str] = None, _=Depends(verify_api_key)):
|
||||
@app.get("/api/v1/workflows", response_model=WorkflowListResponse, tags=["Workflows"])
|
||||
async def list_workflows_endpoint(
|
||||
project_id: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
workflow_type: Optional[str] = None,
|
||||
_=Depends(verify_api_key)
|
||||
):
|
||||
"""获取工作流列表"""
|
||||
if not WORKFLOW_AVAILABLE:
|
||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||
|
||||
manager = get_workflow_manager()
|
||||
workflows = await manager.get_workflows(project_id)
|
||||
manager = get_workflow_manager_instance()
|
||||
workflows = manager.list_workflows(project_id, status, workflow_type)
|
||||
|
||||
return [
|
||||
WorkflowResponse(
|
||||
id=w.id,
|
||||
name=w.name,
|
||||
task_type=w.task_type.value,
|
||||
config=w.config,
|
||||
trigger_type=w.trigger_type.value,
|
||||
schedule=w.schedule,
|
||||
project_id=w.project_id,
|
||||
enabled=w.enabled,
|
||||
created_at=w.created_at.isoformat(),
|
||||
updated_at=w.updated_at.isoformat(),
|
||||
last_run_at=w.last_run_at.isoformat() if w.last_run_at else None,
|
||||
run_count=w.run_count,
|
||||
fail_count=w.fail_count
|
||||
)
|
||||
for w in workflows
|
||||
]
|
||||
return WorkflowListResponse(
|
||||
workflows=[
|
||||
WorkflowResponse(
|
||||
id=w.id,
|
||||
name=w.name,
|
||||
description=w.description,
|
||||
workflow_type=w.workflow_type,
|
||||
project_id=w.project_id,
|
||||
status=w.status,
|
||||
schedule=w.schedule,
|
||||
schedule_type=w.schedule_type,
|
||||
config=w.config,
|
||||
webhook_ids=w.webhook_ids,
|
||||
is_active=w.is_active,
|
||||
created_at=w.created_at,
|
||||
updated_at=w.updated_at,
|
||||
last_run_at=w.last_run_at,
|
||||
next_run_at=w.next_run_at,
|
||||
run_count=w.run_count,
|
||||
success_count=w.success_count,
|
||||
fail_count=w.fail_count
|
||||
)
|
||||
for w in workflows
|
||||
],
|
||||
total=len(workflows)
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/v1/workflows/{workflow_id}", response_model=WorkflowResponse, tags=["Workflows"])
|
||||
async def get_workflow(workflow_id: str, _=Depends(verify_api_key)):
|
||||
async def get_workflow_endpoint(workflow_id: str, _=Depends(verify_api_key)):
|
||||
"""获取单个工作流详情"""
|
||||
if not WORKFLOW_AVAILABLE:
|
||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||
|
||||
manager = get_workflow_manager()
|
||||
workflow = await manager.get_workflow(workflow_id)
|
||||
manager = get_workflow_manager_instance()
|
||||
workflow = manager.get_workflow(workflow_id)
|
||||
|
||||
if not workflow:
|
||||
raise HTTPException(status_code=404, detail="Workflow not found")
|
||||
@@ -3580,118 +3549,155 @@ async def get_workflow(workflow_id: str, _=Depends(verify_api_key)):
|
||||
return WorkflowResponse(
|
||||
id=workflow.id,
|
||||
name=workflow.name,
|
||||
task_type=workflow.task_type.value,
|
||||
config=workflow.config,
|
||||
trigger_type=workflow.trigger_type.value,
|
||||
schedule=w.schedule,
|
||||
description=workflow.description,
|
||||
workflow_type=workflow.workflow_type,
|
||||
project_id=workflow.project_id,
|
||||
enabled=workflow.enabled,
|
||||
created_at=workflow.created_at.isoformat(),
|
||||
updated_at=workflow.updated_at.isoformat(),
|
||||
last_run_at=workflow.last_run_at.isoformat() if workflow.last_run_at else None,
|
||||
status=workflow.status,
|
||||
schedule=workflow.schedule,
|
||||
schedule_type=workflow.schedule_type,
|
||||
config=workflow.config,
|
||||
webhook_ids=workflow.webhook_ids,
|
||||
is_active=workflow.is_active,
|
||||
created_at=workflow.created_at,
|
||||
updated_at=workflow.updated_at,
|
||||
last_run_at=workflow.last_run_at,
|
||||
next_run_at=workflow.next_run_at,
|
||||
run_count=workflow.run_count,
|
||||
success_count=workflow.success_count,
|
||||
fail_count=workflow.fail_count
|
||||
)
|
||||
|
||||
|
||||
@app.patch("/api/v1/workflows/{workflow_id}", response_model=WorkflowResponse, tags=["Workflows"])
|
||||
async def update_workflow(workflow_id: str, request: WorkflowCreateRequest, _=Depends(verify_api_key)):
|
||||
async def update_workflow_endpoint(workflow_id: str, request: WorkflowUpdate, _=Depends(verify_api_key)):
|
||||
"""更新工作流"""
|
||||
if not WORKFLOW_AVAILABLE:
|
||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||
|
||||
manager = get_workflow_manager()
|
||||
manager = get_workflow_manager_instance()
|
||||
|
||||
update_data = request.dict(exclude_unset=True)
|
||||
workflow = await manager.update_workflow(workflow_id, **update_data)
|
||||
update_data = {k: v for k, v in request.dict().items() if v is not None}
|
||||
updated = manager.update_workflow(workflow_id, **update_data)
|
||||
|
||||
if not workflow:
|
||||
if not updated:
|
||||
raise HTTPException(status_code=404, detail="Workflow not found")
|
||||
|
||||
return WorkflowResponse(
|
||||
id=workflow.id,
|
||||
name=workflow.name,
|
||||
task_type=workflow.task_type.value,
|
||||
config=workflow.config,
|
||||
trigger_type=workflow.trigger_type.value,
|
||||
schedule=workflow.schedule,
|
||||
project_id=workflow.project_id,
|
||||
enabled=workflow.enabled,
|
||||
created_at=workflow.created_at.isoformat(),
|
||||
updated_at=workflow.updated_at.isoformat(),
|
||||
last_run_at=workflow.last_run_at.isoformat() if workflow.last_run_at else None,
|
||||
run_count=workflow.run_count,
|
||||
fail_count=workflow.fail_count
|
||||
id=updated.id,
|
||||
name=updated.name,
|
||||
description=updated.description,
|
||||
workflow_type=updated.workflow_type,
|
||||
project_id=updated.project_id,
|
||||
status=updated.status,
|
||||
schedule=updated.schedule,
|
||||
schedule_type=updated.schedule_type,
|
||||
config=updated.config,
|
||||
webhook_ids=updated.webhook_ids,
|
||||
is_active=updated.is_active,
|
||||
created_at=updated.created_at,
|
||||
updated_at=updated.updated_at,
|
||||
last_run_at=updated.last_run_at,
|
||||
next_run_at=updated.next_run_at,
|
||||
run_count=updated.run_count,
|
||||
success_count=updated.success_count,
|
||||
fail_count=updated.fail_count
|
||||
)
|
||||
|
||||
|
||||
@app.delete("/api/v1/workflows/{workflow_id}", tags=["Workflows"])
|
||||
async def delete_workflow(workflow_id: str, _=Depends(verify_api_key)):
|
||||
async def delete_workflow_endpoint(workflow_id: str, _=Depends(verify_api_key)):
|
||||
"""删除工作流"""
|
||||
if not WORKFLOW_AVAILABLE:
|
||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||
|
||||
manager = get_workflow_manager()
|
||||
success = await manager.delete_workflow(workflow_id)
|
||||
manager = get_workflow_manager_instance()
|
||||
success = manager.delete_workflow(workflow_id)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Workflow not found")
|
||||
|
||||
return {"message": "Workflow deleted successfully"}
|
||||
return {"success": True, "message": "Workflow deleted successfully"}
|
||||
|
||||
|
||||
@app.post("/api/v1/workflows/{workflow_id}/trigger", response_model=WorkflowLogResponse, tags=["Workflows"])
|
||||
async def trigger_workflow(workflow_id: str, _=Depends(verify_api_key)):
|
||||
@app.post("/api/v1/workflows/{workflow_id}/trigger", response_model=WorkflowTriggerResponse, tags=["Workflows"])
|
||||
async def trigger_workflow_endpoint(workflow_id: str, request: WorkflowTriggerRequest = None, _=Depends(verify_api_key)):
|
||||
"""手动触发工作流"""
|
||||
if not WORKFLOW_AVAILABLE:
|
||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||
|
||||
manager = get_workflow_manager()
|
||||
log = await manager.trigger_workflow(workflow_id)
|
||||
manager = get_workflow_manager_instance()
|
||||
|
||||
if not log:
|
||||
raise HTTPException(status_code=404, detail="Workflow not found")
|
||||
|
||||
return WorkflowLogResponse(
|
||||
id=log.id,
|
||||
workflow_id=log.workflow_id,
|
||||
status=log.status.value,
|
||||
started_at=log.started_at.isoformat(),
|
||||
completed_at=log.completed_at.isoformat() if log.completed_at else None,
|
||||
result=log.result,
|
||||
error_message=log.error_message,
|
||||
created_at=log.created_at.isoformat()
|
||||
)
|
||||
try:
|
||||
result = await manager.execute_workflow(
|
||||
workflow_id,
|
||||
input_data=request.input_data if request else {}
|
||||
)
|
||||
|
||||
return WorkflowTriggerResponse(
|
||||
success=result["success"],
|
||||
workflow_id=result["workflow_id"],
|
||||
log_id=result["log_id"],
|
||||
results=result["results"],
|
||||
duration_ms=result["duration_ms"]
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/v1/workflows/{workflow_id}/logs", response_model=List[WorkflowLogResponse], tags=["Workflows"])
|
||||
async def get_workflow_logs(workflow_id: str, limit: int = 50, _=Depends(verify_api_key)):
|
||||
@app.get("/api/v1/workflows/{workflow_id}/logs", response_model=WorkflowLogListResponse, tags=["Workflows"])
|
||||
async def get_workflow_logs_endpoint(
|
||||
workflow_id: str,
|
||||
status: Optional[str] = None,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
_=Depends(verify_api_key)
|
||||
):
|
||||
"""获取工作流执行日志"""
|
||||
if not WORKFLOW_AVAILABLE:
|
||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||
|
||||
manager = get_workflow_manager()
|
||||
logs = await manager.get_workflow_logs(workflow_id, limit)
|
||||
manager = get_workflow_manager_instance()
|
||||
logs = manager.list_logs(workflow_id=workflow_id, status=status, limit=limit, offset=offset)
|
||||
|
||||
return [
|
||||
WorkflowLogResponse(
|
||||
id=log.id,
|
||||
workflow_id=log.workflow_id,
|
||||
status=log.status.value,
|
||||
started_at=log.started_at.isoformat(),
|
||||
completed_at=log.completed_at.isoformat() if log.completed_at else None,
|
||||
result=log.result,
|
||||
error_message=log.error_message,
|
||||
created_at=log.created_at.isoformat()
|
||||
)
|
||||
for log in logs
|
||||
]
|
||||
return WorkflowLogListResponse(
|
||||
logs=[
|
||||
WorkflowLogResponse(
|
||||
id=log.id,
|
||||
workflow_id=log.workflow_id,
|
||||
task_id=log.task_id,
|
||||
status=log.status,
|
||||
start_time=log.start_time,
|
||||
end_time=log.end_time,
|
||||
duration_ms=log.duration_ms,
|
||||
input_data=log.input_data,
|
||||
output_data=log.output_data,
|
||||
error_message=log.error_message,
|
||||
created_at=log.created_at
|
||||
)
|
||||
for log in logs
|
||||
],
|
||||
total=len(logs)
|
||||
)
|
||||
|
||||
|
||||
# Webhook Endpoints
|
||||
@app.get("/api/v1/workflows/{workflow_id}/stats", response_model=WorkflowStatsResponse, tags=["Workflows"])
|
||||
async def get_workflow_stats_endpoint(workflow_id: str, days: int = 30, _=Depends(verify_api_key)):
|
||||
"""获取工作流执行统计"""
|
||||
if not WORKFLOW_AVAILABLE:
|
||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||
|
||||
manager = get_workflow_manager_instance()
|
||||
stats = manager.get_workflow_stats(workflow_id, days)
|
||||
|
||||
return WorkflowStatsResponse(**stats)
|
||||
|
||||
@app.post("/api/v1/webhooks", response_model=WebhookResponse, tags=["Workflows"])
|
||||
async def create_webhook(request: WebhookCreateRequest, _=Depends(verify_api_key)):
|
||||
|
||||
# ==================== Phase 7: Webhook Endpoints ====================
|
||||
|
||||
@app.post("/api/v1/webhooks", response_model=WebhookResponse, tags=["Webhooks"])
|
||||
async def create_webhook_endpoint(request: WebhookCreate, _=Depends(verify_api_key)):
|
||||
"""
|
||||
创建 Webhook 配置
|
||||
|
||||
@@ -3700,79 +3706,82 @@ async def create_webhook(request: WebhookCreateRequest, _=Depends(verify_api_key
|
||||
- **dingtalk**: 钉钉机器人
|
||||
- **slack**: Slack Incoming Webhook
|
||||
- **custom**: 自定义 Webhook
|
||||
|
||||
事件类型:
|
||||
- **workflow_completed**: 工作流完成
|
||||
- **workflow_failed**: 工作流失败
|
||||
- **entity_created**: 实体创建
|
||||
- **relation_created**: 关系创建
|
||||
"""
|
||||
if not WORKFLOW_AVAILABLE:
|
||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||
|
||||
manager = get_workflow_manager()
|
||||
manager = get_workflow_manager_instance()
|
||||
|
||||
try:
|
||||
webhook = await manager.create_webhook(
|
||||
webhook = WebhookConfig(
|
||||
id=str(uuid.uuid4())[:8],
|
||||
name=request.name,
|
||||
webhook_type=WebhookType(request.webhook_type),
|
||||
webhook_type=request.webhook_type,
|
||||
url=request.url,
|
||||
secret=request.secret,
|
||||
headers=request.headers,
|
||||
project_id=request.project_id,
|
||||
events=request.events
|
||||
template=request.template
|
||||
)
|
||||
|
||||
created = manager.create_webhook(webhook)
|
||||
|
||||
return WebhookResponse(
|
||||
id=webhook.id,
|
||||
name=webhook.name,
|
||||
webhook_type=webhook.webhook_type.value,
|
||||
url=webhook.url,
|
||||
headers=webhook.headers,
|
||||
project_id=webhook.project_id,
|
||||
events=webhook.events,
|
||||
enabled=webhook.enabled,
|
||||
created_at=webhook.created_at.isoformat(),
|
||||
updated_at=webhook.updated_at.isoformat()
|
||||
id=created.id,
|
||||
name=created.name,
|
||||
webhook_type=created.webhook_type,
|
||||
url=created.url,
|
||||
headers=created.headers,
|
||||
template=created.template,
|
||||
is_active=created.is_active,
|
||||
created_at=created.created_at,
|
||||
updated_at=created.updated_at,
|
||||
last_used_at=created.last_used_at,
|
||||
success_count=created.success_count,
|
||||
fail_count=created.fail_count
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/v1/webhooks", response_model=List[WebhookResponse], tags=["Workflows"])
|
||||
async def list_webhooks(project_id: Optional[str] = None, _=Depends(verify_api_key)):
|
||||
@app.get("/api/v1/webhooks", response_model=WebhookListResponse, tags=["Webhooks"])
|
||||
async def list_webhooks_endpoint(_=Depends(verify_api_key)):
|
||||
"""获取 Webhook 列表"""
|
||||
if not WORKFLOW_AVAILABLE:
|
||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||
|
||||
manager = get_workflow_manager()
|
||||
webhooks = await manager.get_webhooks(project_id)
|
||||
manager = get_workflow_manager_instance()
|
||||
webhooks = manager.list_webhooks()
|
||||
|
||||
return [
|
||||
WebhookResponse(
|
||||
id=w.id,
|
||||
name=w.name,
|
||||
webhook_type=w.webhook_type.value,
|
||||
url=w.url,
|
||||
headers=w.headers,
|
||||
project_id=w.project_id,
|
||||
events=w.events,
|
||||
enabled=w.enabled,
|
||||
created_at=w.created_at.isoformat(),
|
||||
updated_at=w.updated_at.isoformat()
|
||||
)
|
||||
for w in webhooks
|
||||
]
|
||||
return WebhookListResponse(
|
||||
webhooks=[
|
||||
WebhookResponse(
|
||||
id=w.id,
|
||||
name=w.name,
|
||||
webhook_type=w.webhook_type,
|
||||
url=w.url,
|
||||
headers=w.headers,
|
||||
template=w.template,
|
||||
is_active=w.is_active,
|
||||
created_at=w.created_at,
|
||||
updated_at=w.updated_at,
|
||||
last_used_at=w.last_used_at,
|
||||
success_count=w.success_count,
|
||||
fail_count=w.fail_count
|
||||
)
|
||||
for w in webhooks
|
||||
],
|
||||
total=len(webhooks)
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/v1/webhooks/{webhook_id}", response_model=WebhookResponse, tags=["Workflows"])
|
||||
async def get_webhook(webhook_id: str, _=Depends(verify_api_key)):
|
||||
@app.get("/api/v1/webhooks/{webhook_id}", response_model=WebhookResponse, tags=["Webhooks"])
|
||||
async def get_webhook_endpoint(webhook_id: str, _=Depends(verify_api_key)):
|
||||
"""获取单个 Webhook 详情"""
|
||||
if not WORKFLOW_AVAILABLE:
|
||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||
|
||||
manager = get_workflow_manager()
|
||||
webhook = await manager.get_webhook(webhook_id)
|
||||
manager = get_workflow_manager_instance()
|
||||
webhook = manager.get_webhook(webhook_id)
|
||||
|
||||
if not webhook:
|
||||
raise HTTPException(status_code=404, detail="Webhook not found")
|
||||
@@ -3780,71 +3789,89 @@ async def get_webhook(webhook_id: str, _=Depends(verify_api_key)):
|
||||
return WebhookResponse(
|
||||
id=webhook.id,
|
||||
name=webhook.name,
|
||||
webhook_type=webhook.webhook_type.value,
|
||||
webhook_type=webhook.webhook_type,
|
||||
url=webhook.url,
|
||||
headers=webhook.headers,
|
||||
project_id=webhook.project_id,
|
||||
events=webhook.events,
|
||||
enabled=webhook.enabled,
|
||||
created_at=webhook.created_at.isoformat(),
|
||||
updated_at=webhook.updated_at.isoformat()
|
||||
template=webhook.template,
|
||||
is_active=webhook.is_active,
|
||||
created_at=webhook.created_at,
|
||||
updated_at=webhook.updated_at,
|
||||
last_used_at=webhook.last_used_at,
|
||||
success_count=webhook.success_count,
|
||||
fail_count=webhook.fail_count
|
||||
)
|
||||
|
||||
|
||||
@app.patch("/api/v1/webhooks/{webhook_id}", response_model=WebhookResponse, tags=["Workflows"])
|
||||
async def update_webhook(webhook_id: str, request: WebhookCreateRequest, _=Depends(verify_api_key)):
|
||||
@app.patch("/api/v1/webhooks/{webhook_id}", response_model=WebhookResponse, tags=["Webhooks"])
|
||||
async def update_webhook_endpoint(webhook_id: str, request: WebhookUpdate, _=Depends(verify_api_key)):
|
||||
"""更新 Webhook 配置"""
|
||||
if not WORKFLOW_AVAILABLE:
|
||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||
|
||||
manager = get_workflow_manager()
|
||||
manager = get_workflow_manager_instance()
|
||||
|
||||
update_data = request.dict(exclude_unset=True)
|
||||
webhook = await manager.update_webhook(webhook_id, **update_data)
|
||||
update_data = {k: v for k, v in request.dict().items() if v is not None}
|
||||
updated = manager.update_webhook(webhook_id, **update_data)
|
||||
|
||||
if not webhook:
|
||||
if not updated:
|
||||
raise HTTPException(status_code=404, detail="Webhook not found")
|
||||
|
||||
return WebhookResponse(
|
||||
id=webhook.id,
|
||||
name=webhook.name,
|
||||
webhook_type=webhook.webhook_type.value,
|
||||
url=webhook.url,
|
||||
headers=webhook.headers,
|
||||
project_id=webhook.project_id,
|
||||
events=webhook.events,
|
||||
enabled=webhook.enabled,
|
||||
created_at=webhook.created_at.isoformat(),
|
||||
updated_at=webhook.updated_at.isoformat()
|
||||
id=updated.id,
|
||||
name=updated.name,
|
||||
webhook_type=updated.webhook_type,
|
||||
url=updated.url,
|
||||
headers=updated.headers,
|
||||
template=updated.template,
|
||||
is_active=updated.is_active,
|
||||
created_at=updated.created_at,
|
||||
updated_at=updated.updated_at,
|
||||
last_used_at=updated.last_used_at,
|
||||
success_count=updated.success_count,
|
||||
fail_count=updated.fail_count
|
||||
)
|
||||
|
||||
|
||||
@app.delete("/api/v1/webhooks/{webhook_id}", tags=["Workflows"])
|
||||
async def delete_webhook(webhook_id: str, _=Depends(verify_api_key)):
|
||||
@app.delete("/api/v1/webhooks/{webhook_id}", tags=["Webhooks"])
|
||||
async def delete_webhook_endpoint(webhook_id: str, _=Depends(verify_api_key)):
|
||||
"""删除 Webhook 配置"""
|
||||
if not WORKFLOW_AVAILABLE:
|
||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||
|
||||
manager = get_workflow_manager()
|
||||
success = await manager.delete_webhook(webhook_id)
|
||||
manager = get_workflow_manager_instance()
|
||||
success = manager.delete_webhook(webhook_id)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Webhook not found")
|
||||
|
||||
return {"message": "Webhook deleted successfully"}
|
||||
return {"success": True, "message": "Webhook deleted successfully"}
|
||||
|
||||
|
||||
@app.post("/api/v1/webhooks/{webhook_id}/test", tags=["Workflows"])
|
||||
async def test_webhook(webhook_id: str, _=Depends(verify_api_key)):
|
||||
@app.post("/api/v1/webhooks/{webhook_id}/test", tags=["Webhooks"])
|
||||
async def test_webhook_endpoint(webhook_id: str, _=Depends(verify_api_key)):
|
||||
"""测试 Webhook 配置"""
|
||||
if not WORKFLOW_AVAILABLE:
|
||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||
|
||||
manager = get_workflow_manager()
|
||||
success = await manager.test_webhook(webhook_id)
|
||||
manager = get_workflow_manager_instance()
|
||||
webhook = manager.get_webhook(webhook_id)
|
||||
|
||||
if not webhook:
|
||||
raise HTTPException(status_code=404, detail="Webhook not found")
|
||||
|
||||
# 构建测试消息
|
||||
test_message = {
|
||||
"content": "🔔 这是来自 InsightFlow 的 Webhook 测试消息\n\n如果您收到这条消息,说明 Webhook 配置正确!"
|
||||
}
|
||||
|
||||
if webhook.webhook_type == "slack":
|
||||
test_message = {"text": "🔔 这是来自 InsightFlow 的 Webhook 测试消息\n\n如果您收到这条消息,说明 Webhook 配置正确!"}
|
||||
|
||||
success = await manager.notifier.send(webhook, test_message)
|
||||
manager.update_webhook_stats(webhook_id, success)
|
||||
|
||||
if success:
|
||||
return {"message": "Webhook test sent successfully"}
|
||||
return {"success": True, "message": "Webhook test sent successfully"}
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="Webhook test failed")
|
||||
|
||||
|
||||
@@ -34,8 +34,5 @@ neo4j==5.15.0
|
||||
# API Documentation (Swagger/OpenAPI)
|
||||
fastapi-offline-swagger==0.1.0
|
||||
|
||||
# Workflow Automation
|
||||
apscheduler==3.10.4
|
||||
|
||||
# Phase 7: Workflow Automation
|
||||
apscheduler==3.10.4
|
||||
|
||||
@@ -80,7 +80,7 @@ CREATE TABLE IF NOT EXISTS attribute_templates (
|
||||
id TEXT PRIMARY KEY,
|
||||
project_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL, -- text/number/date/select/multiselect
|
||||
type TEXT NOT NULL, -- text/number/date/select/multiselect/boolean
|
||||
description TEXT,
|
||||
options TEXT, -- JSON 数组,用于 select/multiselect 类型
|
||||
is_required INTEGER DEFAULT 0,
|
||||
@@ -111,54 +111,13 @@ CREATE TABLE IF NOT EXISTS entity_attributes (
|
||||
CREATE TABLE IF NOT EXISTS attribute_history (
|
||||
id TEXT PRIMARY KEY,
|
||||
entity_id TEXT NOT NULL,
|
||||
template_id TEXT,
|
||||
attribute_name TEXT NOT NULL,
|
||||
old_value TEXT,
|
||||
new_value TEXT,
|
||||
changed_by TEXT, -- 用户ID或系统
|
||||
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
change_reason TEXT,
|
||||
FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Phase 5: 属性模板表(项目级自定义属性定义)
|
||||
CREATE TABLE IF NOT EXISTS attribute_templates (
|
||||
id TEXT PRIMARY KEY,
|
||||
project_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL, -- 属性名称,如"年龄"、"职位"
|
||||
type TEXT NOT NULL, -- 属性类型: text, number, date, select, multiselect, boolean
|
||||
options TEXT, -- JSON 数组,用于 select/multiselect 类型
|
||||
default_value TEXT, -- 默认值
|
||||
description TEXT, -- 属性描述
|
||||
is_required BOOLEAN DEFAULT 0, -- 是否必填
|
||||
display_order INTEGER DEFAULT 0, -- 显示顺序
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (project_id) REFERENCES projects(id)
|
||||
);
|
||||
|
||||
-- Phase 5: 实体属性值表
|
||||
CREATE TABLE IF NOT EXISTS entity_attributes (
|
||||
id TEXT PRIMARY KEY,
|
||||
entity_id TEXT NOT NULL,
|
||||
template_id TEXT NOT NULL,
|
||||
value TEXT, -- 属性值(以JSON或字符串形式存储)
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (template_id) REFERENCES attribute_templates(id) ON DELETE CASCADE,
|
||||
UNIQUE(entity_id, template_id) -- 每个实体每个属性只能有一个值
|
||||
);
|
||||
|
||||
-- Phase 5: 属性变更历史表
|
||||
CREATE TABLE IF NOT EXISTS attribute_history (
|
||||
id TEXT PRIMARY KEY,
|
||||
entity_id TEXT NOT NULL,
|
||||
template_id TEXT NOT NULL,
|
||||
old_value TEXT,
|
||||
new_value TEXT,
|
||||
changed_by TEXT, -- 用户ID或"system"
|
||||
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
change_reason TEXT, -- 变更原因
|
||||
FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (template_id) REFERENCES attribute_templates(id) ON DELETE CASCADE
|
||||
);
|
||||
@@ -263,60 +222,3 @@ CREATE INDEX IF NOT EXISTS idx_workflow_logs_workflow ON workflow_logs(workflow_
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_logs_task ON workflow_logs(task_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_logs_status ON workflow_logs(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_logs_created ON workflow_logs(created_at);
|
||||
|
||||
-- Phase 7: 工作流自动化表
|
||||
-- 工作流配置表
|
||||
CREATE TABLE IF NOT EXISTS workflows (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
task_type TEXT NOT NULL, -- auto_analyze, auto_align, auto_relation, custom
|
||||
config TEXT, -- JSON 配置
|
||||
trigger_type TEXT DEFAULT 'manual', -- schedule, event, manual
|
||||
schedule TEXT, -- Cron 表达式或间隔
|
||||
project_id TEXT,
|
||||
enabled INTEGER DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
last_run_at TIMESTAMP,
|
||||
next_run_at TIMESTAMP,
|
||||
run_count INTEGER DEFAULT 0,
|
||||
fail_count INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (project_id) REFERENCES projects(id)
|
||||
);
|
||||
|
||||
-- 工作流执行日志表
|
||||
CREATE TABLE IF NOT EXISTS workflow_logs (
|
||||
id TEXT PRIMARY KEY,
|
||||
workflow_id TEXT NOT NULL,
|
||||
task_id TEXT NOT NULL,
|
||||
status TEXT NOT NULL, -- pending, running, success, failed, cancelled
|
||||
started_at TIMESTAMP NOT NULL,
|
||||
completed_at TIMESTAMP,
|
||||
result TEXT, -- JSON 结果
|
||||
error_message TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (workflow_id) REFERENCES workflows(id)
|
||||
);
|
||||
|
||||
-- Webhook 配置表
|
||||
CREATE TABLE IF NOT EXISTS webhook_configs (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
webhook_type TEXT NOT NULL, -- feishu, dingtalk, slack, custom
|
||||
url TEXT NOT NULL,
|
||||
secret TEXT,
|
||||
headers TEXT, -- JSON 格式
|
||||
project_id TEXT,
|
||||
events TEXT, -- JSON 数组,如 ["workflow_completed", "workflow_failed"]
|
||||
enabled INTEGER DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (project_id) REFERENCES projects(id)
|
||||
);
|
||||
|
||||
-- Phase 7: 工作流相关索引
|
||||
CREATE INDEX IF NOT EXISTS idx_workflows_project ON workflows(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_workflows_enabled ON workflows(enabled);
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_logs_workflow ON workflow_logs(workflow_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_logs_created ON workflow_logs(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_webhook_configs_project ON webhook_configs(project_id);
|
||||
|
||||
Reference in New Issue
Block a user