sync: 同步 sql-platform-pro 最新代码 — 权限体系安全修复 v0.8.1

- P0: 实例访问控制全线接入权限组 (829测试)
- P1: permissions字段激活 + 操作级权限检查 (843测试)
- 前端: 删除数据权限页面, 权限组合并为唯一入口
- 新增: CodeMirror编辑器 + 查询模板 + 慢查询分析
- 新增: MongoDB管理页面 + Dashboard增强
- 修复: Pydantic V2迁移 + 前端TS错误修复
This commit is contained in:
logan committed 2026-05-23 14:41:06 +08:00
1 parent 6fcd9465cf
commit 4c9427e546
105 files changed
+23680 -471

No files matched your search

+244
View File
@@ -0,0 +1,244 @@
"""审批流程管理 API。"""
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.core.database import get_db
from app.core.security import require_role
from app.models.user import User
from app.models.workflow import WorkflowDefinition, WorkflowStep
from app.schemas.workflow import (
WorkflowCreate, WorkflowUpdate, WorkflowResponse,
WorkflowStepCreate,
)
router = APIRouter(prefix="/workflows", tags=["审批流程"])
def _serialize_step(step: WorkflowStep) -> dict:
return {
"id": step.id,
"workflow_id": step.workflow_id,
"step_number": step.step_number,
"name": step.name,
"step_type": step.step_type,
"approver_role": step.approver_role,
"approver_ids": step.approver_ids,
"is_required": step.is_required,
"timeout_hours": step.timeout_hours,
"auto_approve_on_timeout": step.auto_approve_on_timeout,
}
def _serialize_workflow(wf: WorkflowDefinition) -> dict:
return {
"id": wf.id,
"name": wf.name,
"description": wf.description,
"is_default": wf.is_default,
"is_active": wf.is_active,
"created_at": wf.created_at,
"steps": [_serialize_step(s) for s in wf.steps],
}
@router.get("", response_model=List[WorkflowResponse])
async def list_workflows(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role("admin", "dba")),
):
result = await db.execute(
select(WorkflowDefinition)
.where(WorkflowDefinition.is_active == True)
.options(selectinload(WorkflowDefinition.steps))
)
workflows = result.scalars().unique().all()
return [_serialize_workflow(wf) for wf in workflows]
@router.post("", response_model=WorkflowResponse, status_code=201)
async def create_workflow(
data: WorkflowCreate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
# 如果设为默认,取消其他默认
if data.is_default:
from sqlalchemy import update as sa_update
await db.execute(
sa_update(WorkflowDefinition)
.where(WorkflowDefinition.is_default == True)
.values(is_default=False)
)
workflow = WorkflowDefinition(
name=data.name,
description=data.description,
is_default=data.is_default,
)
db.add(workflow)
await db.flush()
for step_data in data.steps:
step = WorkflowStep(
workflow_id=workflow.id,
step_number=step_data.step_number,
name=step_data.name,
step_type=step_data.step_type,
approver_role=step_data.approver_role,
approver_ids=step_data.approver_ids,
is_required=step_data.is_required,
timeout_hours=step_data.timeout_hours,
auto_approve_on_timeout=step_data.auto_approve_on_timeout,
)
db.add(step)
await db.commit()
# Reload with steps
result = await db.execute(
select(WorkflowDefinition)
.where(WorkflowDefinition.id == workflow.id)
.options(selectinload(WorkflowDefinition.steps))
)
workflow = result.scalar_one()
return _serialize_workflow(workflow)
@router.get("/{workflow_id}", response_model=WorkflowResponse)
async def get_workflow(
workflow_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role("admin", "dba")),
):
result = await db.execute(
select(WorkflowDefinition)
.where(WorkflowDefinition.id == workflow_id)
.options(selectinload(WorkflowDefinition.steps))
)
workflow = result.scalar_one_or_none()
if not workflow:
raise HTTPException(status_code=404, detail="审批流程不存在")
return _serialize_workflow(workflow)
@router.put("/{workflow_id}", response_model=WorkflowResponse)
async def update_workflow(
workflow_id: int,
data: WorkflowUpdate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
result = await db.execute(
select(WorkflowDefinition)
.where(WorkflowDefinition.id == workflow_id)
.options(selectinload(WorkflowDefinition.steps))
)
workflow = result.scalar_one_or_none()
if not workflow:
raise HTTPException(status_code=404, detail="审批流程不存在")
if data.name is not None:
workflow.name = data.name
if data.description is not None:
workflow.description = data.description
if data.is_active is not None:
workflow.is_active = data.is_active
await db.commit()
await db.refresh(workflow)
return _serialize_workflow(workflow)
@router.delete("/{workflow_id}")
async def delete_workflow(
workflow_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
result = await db.execute(
select(WorkflowDefinition).where(WorkflowDefinition.id == workflow_id)
)
workflow = result.scalar_one_or_none()
if not workflow:
raise HTTPException(status_code=404, detail="审批流程不存在")
workflow.is_active = False
await db.commit()
return {"message": "审批流程已删除"}
@router.post("/{workflow_id}/set-default")
async def set_default_workflow(
workflow_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
"""设为默认审批流程。"""
# 取消所有默认
result = await db.execute(select(WorkflowDefinition))
all_workflows = result.scalars().all()
for wf in all_workflows:
wf.is_default = False
# 设置指定流程为默认
result = await db.execute(
select(WorkflowDefinition).where(WorkflowDefinition.id == workflow_id)
)
workflow = result.scalar_one_or_none()
if not workflow:
raise HTTPException(status_code=404, detail="审批流程不存在")
workflow.is_default = True
await db.commit()
return {"message": "审批流程 '{}' 已设为默认".format(workflow.name)}
@router.post("/{workflow_id}/clone", response_model=WorkflowResponse)
async def clone_workflow(
workflow_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
"""克隆审批流程。"""
result = await db.execute(
select(WorkflowDefinition)
.where(WorkflowDefinition.id == workflow_id)
.options(selectinload(WorkflowDefinition.steps))
)
source = result.scalar_one_or_none()
if not source:
raise HTTPException(status_code=404, detail="审批流程不存在")
new_workflow = WorkflowDefinition(
name="{} (副本)".format(source.name),
description=source.description,
is_default=False,
)
db.add(new_workflow)
await db.flush()
for step in source.steps:
new_step = WorkflowStep(
workflow_id=new_workflow.id,
step_number=step.step_number,
name=step.name,
step_type=step.step_type,
approver_role=step.approver_role,
approver_ids=step.approver_ids,
is_required=step.is_required,
timeout_hours=step.timeout_hours,
auto_approve_on_timeout=step.auto_approve_on_timeout,
)
db.add(new_step)
await db.commit()
# Reload with steps
result = await db.execute(
select(WorkflowDefinition)
.where(WorkflowDefinition.id == new_workflow.id)
.options(selectinload(WorkflowDefinition.steps))
)
new_workflow = result.scalar_one()
return _serialize_workflow(new_workflow)