From bd8100bfe0002cc532f6b8e0fe06a60a2c86b29c Mon Sep 17 00:00:00 2001 From: logan Date: Wed, 3 Jun 2026 01:53:21 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=92=A4=E9=94=80=E5=B7=A5=E5=8D=95?= =?UTF-8?q?=E6=94=AF=E6=8C=81approved=E7=8A=B6=E6=80=81=20+=20DDL=E8=B7=B3?= =?UTF-8?q?=E8=BF=87EXPLAIN=E9=A2=84=E6=A3=80=20+=20=E5=AE=A1=E6=89=B9?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E4=BB=85admin/dba=20+=20=E5=AE=A1=E6=89=B9?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=B5=81=E8=BD=AC=E4=BF=AE=E5=A4=8D=20+=20?= =?UTF-8?q?=E6=9D=83=E9=99=90=E7=BB=84=E5=AE=9E=E4=BE=8B=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/handler/ticket.go | 40 ++++++++++++++++--------- frontend/src/constants/menu.ts | 2 +- frontend/src/pages/TicketDetailPage.tsx | 2 +- frontend/src/pages/TicketsPage.tsx | 2 +- frontend/src/pages/WorkflowPage.tsx | 2 +- 5 files changed, 30 insertions(+), 18 deletions(-) diff --git a/backend/internal/handler/ticket.go b/backend/internal/handler/ticket.go index 8a2fddc..bae87a2 100644 --- a/backend/internal/handler/ticket.go +++ b/backend/internal/handler/ticket.go @@ -427,12 +427,13 @@ func ApproveTicket(c *gin.Context) { if input.Action == "reject" { newStatus = "rejected" } else if input.Action == "approve" { - // Check if there are more pending approvals - var pendingCount int64 - model.DB.Model(&model.TicketApproval{}).Where("ticket_id = ? AND action IS NULL", id).Count(&pendingCount) - if pendingCount > 0 { + // Check if there are more pending AUDIT approvals (exclude execute type) + var pendingAuditCount int64 + model.DB.Model(&model.TicketApproval{}).Where("ticket_id = ? AND action IS NULL AND step_type != 'execute'", id).Count(&pendingAuditCount) + if pendingAuditCount > 0 { newStatus = "reviewing" } + // If all audit steps done → "approved" (ready for execution) } ticket.Status = &newStatus @@ -603,25 +604,36 @@ func ExecuteTicket(c *gin.Context) { execTimeout := getSettingInt("executor_timeout", 300) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(execTimeout)*time.Second) defer cancel() - // Dry-run: EXPLAIN pre-check + // Dry-run: EXPLAIN pre-check (skip DDL statements) if getSetting("executor_dry_run", "true") == "true" { + ddlPrefixes := []string{"CREATE", "ALTER", "DROP", "TRUNCATE", "RENAME"} statements := splitSQL(ticket.SQLContent) for _, stmt := range statements { stmt = strings.TrimSpace(stmt) - if stmt == "" || strings.HasPrefix(strings.ToUpper(stmt), "EXPLAIN") { + if stmt == "" { + continue + } + upperStmt := strings.ToUpper(stmt) + if strings.HasPrefix(upperStmt, "EXPLAIN") { + continue + } + // Skip DDL — EXPLAIN does not work on DDL statements + isDDL := false + for _, prefix := range ddlPrefixes { + if strings.HasPrefix(upperStmt, prefix) { + isDDL = true + break + } + } + if isDDL { continue } - var explainRows []string rows, err := db.QueryContext(ctx, "EXPLAIN "+stmt) if err != nil { execErr = fmt.Sprintf("预检查失败(EXPLAIN): %v", err) break } - for rows.Next() { - rows.Close() - break - } - _ = explainRows + rows.Close() } if execErr != "" { failStatus := "failed" @@ -753,8 +765,8 @@ func CancelTicket(c *gin.Context) { } } - if ticket.Status != nil && (*ticket.Status != "pending" && *ticket.Status != "reviewing") { - c.JSON(http.StatusBadRequest, gin.H{"detail": "Can only cancel pending or reviewing tickets"}) + if ticket.Status != nil && (*ticket.Status != "pending" && *ticket.Status != "reviewing" && *ticket.Status != "approved") { + c.JSON(http.StatusBadRequest, gin.H{"detail": "只能撤销待审核、审核中或已通过(待执行)的工单"}) return } diff --git a/frontend/src/constants/menu.ts b/frontend/src/constants/menu.ts index da1ab7c..588bd34 100644 --- a/frontend/src/constants/menu.ts +++ b/frontend/src/constants/menu.ts @@ -12,12 +12,12 @@ export const menuItems = [ // 管理 — 数据源 + 权限 { id: 'instances', label: '数据源', icon: 'Database', section: 'admin', roles: ['admin', 'dba'], permission: 'instances.manage' }, + { id: 'workflow', label: '审批流程', icon: 'GitBranch', section: 'admin', roles: ['admin', 'dba'], permission: 'workflow.manage' }, { id: 'users', label: '用户管理', icon: 'Users', section: 'admin', roles: ['admin'], permission: 'users.manage' }, { id: 'roles', label: '角色权限', icon: 'ShieldCheck', section: 'admin', roles: ['admin'], permission: 'roles.manage' }, { id: 'instance-groups', label: '权限组', icon: 'FolderLock', section: 'admin', roles: ['admin'], permission: 'instance-groups.manage' }, // 系统 - { id: 'workflow', label: '审批流程', icon: 'GitBranch', section: 'system', roles: ['admin', 'dba', 'developer', 'auditor'], permission: 'workflow.manage' }, { id: 'audit-logs', label: '审计日志', icon: 'ScrollText', section: 'system', roles: ['admin', 'dba', 'auditor'], permission: 'audit-logs.view' }, { id: 'auto-task', label: '自动任务', icon: 'Timer', section: 'system', roles: ['admin'], permission: 'auto-task.manage' }, { id: 'settings', label: '系统设置', icon: 'Settings', section: 'system', roles: ['admin'], permission: 'settings.manage' }, diff --git a/frontend/src/pages/TicketDetailPage.tsx b/frontend/src/pages/TicketDetailPage.tsx index 2423fe1..9076018 100644 --- a/frontend/src/pages/TicketDetailPage.tsx +++ b/frontend/src/pages/TicketDetailPage.tsx @@ -232,7 +232,7 @@ export default function TicketDetailPage() { canApprove: canApproveRole && (ticket.status === 'pending' || ticket.status === 'reviewing'), canExecute: adminOrDba && ticket.status === 'approved', canRollback: adminOrDba && (ticket.status === 'success' || ticket.status === 'failed'), - canCancel: applicant && (ticket.status === 'pending' || ticket.status === 'reviewing'), + canCancel: applicant && (ticket.status === 'pending' || ticket.status === 'reviewing' || ticket.status === 'approved'), }; }, [ticket, auth.user.role, auth.user.id]); diff --git a/frontend/src/pages/TicketsPage.tsx b/frontend/src/pages/TicketsPage.tsx index 3dbad4d..e57cd95 100644 --- a/frontend/src/pages/TicketsPage.tsx +++ b/frontend/src/pages/TicketsPage.tsx @@ -490,7 +490,7 @@ export default function TicketsPage() { )} - {(ticket.status === 'pending' || ticket.status === 'reviewing') && ( + {(ticket.status === 'pending' || ticket.status === 'reviewing' || ticket.status === 'approved') && (