From b1a3d74661102419f879943d20502e240322dde5 Mon Sep 17 00:00:00 2001 From: logan Date: Wed, 3 Jun 2026 01:19:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A7=92=E8=89=B2=E6=9D=83=E9=99=90?= =?UTF-8?q?=E4=BD=93=E7=B3=BB=E6=94=B9=E9=80=A0=20=E2=80=94=20developer?= =?UTF-8?q?=E5=8F=AF=E5=AE=A1=E6=89=B9,=20auditor=E7=BA=AF=E5=8F=AA?= =?UTF-8?q?=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 角色权限矩阵: - admin: 全部权限 - dba: 数据源管理+工单全流程+审批+执行 - developer: 查询+提交工单+审批(不能执行) - auditor: 只读(审计日志+审核规则+审批流程查看) 后端改动: - ticket.go: 修复GetCurrentUser返回值顺序bug(userID,username,role) - ticket.go: CreateTicket拦截auditor提交工单 - ticket.go: ApproveTicket允许admin/dba/developer - ticket.go: ExecuteTicket仅允许admin/dba - router.go: query执行限制admin/dba/developer - router.go: workflow写操作限制admin/dba - router.go: audit-rules写操作限制admin/dba 前端改动: - menu.ts: tickets去掉auditor, workflow加developer+auditor, audit-logs加auditor - useAuth.tsx: routePermissionMap和actionPermissionMap同步更新 - TicketDetailPage: canApprove加developer角色 DB迁移: test123/test234从auditor→developer --- backend/internal/handler/ticket.go | 20 ++++++++----- backend/router/router.go | 38 +++++++++++++++++-------- frontend/src/constants/menu.ts | 6 ++-- frontend/src/hooks/useAuth.tsx | 8 +++--- frontend/src/pages/TicketDetailPage.tsx | 3 +- 5 files changed, 48 insertions(+), 27 deletions(-) diff --git a/backend/internal/handler/ticket.go b/backend/internal/handler/ticket.go index 41f7f12..8a2fddc 100644 --- a/backend/internal/handler/ticket.go +++ b/backend/internal/handler/ticket.go @@ -198,8 +198,14 @@ func GetTicket(c *gin.Context) { // POST /api/v1/tickets func CreateTicket(c *gin.Context) { - userID, _, _ := middleware.GetCurrentUser(c) - + userID, _, userRole := middleware.GetCurrentUser(c) + + // auditor cannot create tickets + if userRole == "auditor" { + c.JSON(http.StatusForbidden, gin.H{"detail": "权限不足:审计员不能提交工单"}) + return + } + var input struct { Title string `json:"title" binding:"required"` DBType string `json:"db_type" binding:"required"` @@ -367,11 +373,11 @@ func CreateTicket(c *gin.Context) { // POST /api/v1/tickets/:id/approve func ApproveTicket(c *gin.Context) { id := c.Param("id") - userID, userRole, _ := middleware.GetCurrentUser(c) + userID, _, userRole := middleware.GetCurrentUser(c) - // Role check: only admin/dba/auditor can approve - if userRole != "admin" && userRole != "dba" && userRole != "auditor" { - c.JSON(http.StatusForbidden, gin.H{"detail": "权限不足:审核操作需要管理员、DBA 或审核员角色"}) + // Role check: admin/dba/developer can approve + if userRole != "admin" && userRole != "dba" && userRole != "developer" { + c.JSON(http.StatusForbidden, gin.H{"detail": "权限不足:审核操作需要管理员、DBA 或开发者角色"}) return } @@ -441,7 +447,7 @@ func ExecuteTicket(c *gin.Context) { id := c.Param("id") // Role check: only admin/dba can execute - _, userRole, _ := middleware.GetCurrentUser(c) + _, _, userRole := middleware.GetCurrentUser(c) if userRole != "admin" && userRole != "dba" { c.JSON(http.StatusForbidden, gin.H{"detail": "权限不足:执行操作需要管理员或 DBA 角色"}) return diff --git a/backend/router/router.go b/backend/router/router.go index f38f014..5df20e8 100644 --- a/backend/router/router.go +++ b/backend/router/router.go @@ -153,10 +153,14 @@ func Setup(r *gin.Engine) { // Audit Logs auth.GET("/audit-logs", handler.ListAuditLogs) - // Query execution - auth.POST("/query/execute", handler.ExecuteQuery) - auth.POST("/query", handler.ExecuteQuery) - auth.POST("/query/export", handler.ExportQueryCSV) + // Query execution (auditor excluded) + queryGroup := auth.Group("") + queryGroup.Use(middleware.RequireRole("admin", "dba", "developer")) + { + queryGroup.POST("/query/execute", handler.ExecuteQuery) + queryGroup.POST("/query", handler.ExecuteQuery) + queryGroup.POST("/query/export", handler.ExportQueryCSV) + } // SQL Assistant @@ -180,11 +184,15 @@ func Setup(r *gin.Engine) { auth.POST("/mongo/:id/execute", handler.MongoExecute) auth.POST("/mongo/:id/test-connection", handler.MongoTestConnection) - // Read-only resources + // Audit rules — read for all, write for admin/dba auth.GET("/audit-rules", handler.ListAuditRules) - auth.POST("/audit-rules/seed/:dbType", handler.SeedAuditRules) - auth.POST("/audit-rules/:id/toggle", handler.ToggleAuditRule) - auth.PUT("/audit-rules/:id", handler.UpdateAuditRule) + ruleAdmin := auth.Group("") + ruleAdmin.Use(middleware.RequireRole("admin", "dba")) + { + ruleAdmin.POST("/audit-rules/seed/:dbType", handler.SeedAuditRules) + ruleAdmin.POST("/audit-rules/:id/toggle", handler.ToggleAuditRule) + ruleAdmin.PUT("/audit-rules/:id", handler.UpdateAuditRule) + } auth.GET("/announcements", handler.ListAnnouncements) auth.GET("/query-histories", handler.ListQueryHistories) // Notifications CRUD (replaces old channels/templates endpoints) @@ -195,11 +203,17 @@ func Setup(r *gin.Engine) { auth.POST("/notifications/:id/test", handler.TestNotificationSend) auth.GET("/workflows", handler.ListWorkflows) auth.GET("/workflows/:id", handler.GetWorkflow) - auth.POST("/workflows", handler.CreateWorkflow) - auth.PUT("/workflows/:id", handler.UpdateWorkflow) - auth.DELETE("/workflows/:id", handler.DeleteWorkflow) - auth.POST("/workflows/:id/set-default", handler.SetDefaultWorkflow) auth.POST("/workflows/:id/clone", handler.CloneWorkflow) + // Workflow management (admin/dba only) + wfAdmin := auth.Group("") + wfAdmin.Use(middleware.RequireRole("admin", "dba")) + { + wfAdmin.POST("/workflows", handler.CreateWorkflow) + wfAdmin.PUT("/workflows/:id", handler.UpdateWorkflow) + wfAdmin.DELETE("/workflows/:id", handler.DeleteWorkflow) + wfAdmin.POST("/workflows/:id/set-default", handler.SetDefaultWorkflow) + } + } } diff --git a/frontend/src/constants/menu.ts b/frontend/src/constants/menu.ts index dd64ff1..da1ab7c 100644 --- a/frontend/src/constants/menu.ts +++ b/frontend/src/constants/menu.ts @@ -8,7 +8,7 @@ export const menuItems = [ // 工作台 — 日常操作 { id: 'dashboard', label: '仪表盘', icon: 'LayoutDashboard', section: 'workspace', roles: ['admin', 'dba', 'developer', 'auditor'], permission: 'dashboard.view' }, { id: 'queries', label: '查询数据', icon: 'Code2', section: 'workspace', roles: ['admin', 'dba', 'developer'], permission: 'queries.execute' }, - { id: 'tickets', label: '数据变更', icon: 'FileEdit', section: 'workspace', roles: ['admin', 'dba', 'developer', 'auditor'], permission: 'tickets.manage' }, + { id: 'tickets', label: '数据变更', icon: 'FileEdit', section: 'workspace', roles: ['admin', 'dba', 'developer'], permission: 'tickets.manage' }, // 管理 — 数据源 + 权限 { id: 'instances', label: '数据源', icon: 'Database', section: 'admin', roles: ['admin', 'dba'], permission: 'instances.manage' }, @@ -17,8 +17,8 @@ export const menuItems = [ { id: 'instance-groups', label: '权限组', icon: 'FolderLock', section: 'admin', roles: ['admin'], permission: 'instance-groups.manage' }, // 系统 - { id: 'workflow', label: '审批流程', icon: 'GitBranch', section: 'system', roles: ['admin', 'dba'], permission: 'workflow.manage' }, - { id: 'audit-logs', label: '审计日志', icon: 'ScrollText', section: 'system', roles: ['admin', 'dba'], permission: 'audit-logs.view' }, + { 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/hooks/useAuth.tsx b/frontend/src/hooks/useAuth.tsx index f0d902b..3165dbc 100644 --- a/frontend/src/hooks/useAuth.tsx +++ b/frontend/src/hooks/useAuth.tsx @@ -114,19 +114,19 @@ const routePermissionMap: Record = { instances: ['admin', 'dba'], 'instance-groups': ['admin'], 'data-dictionary': ['admin', 'dba', 'developer'], - tickets: ['admin', 'dba', 'developer', 'auditor'], + tickets: ['admin', 'dba', 'developer'], queries: ['admin', 'dba', 'developer'], roles: ['admin'], users: ['admin'], - 'audit-logs': ['admin', 'dba'], - workflow: ['admin', 'dba'], + 'audit-logs': ['admin', 'dba', 'auditor'], + workflow: ['admin', 'dba', 'developer', 'auditor'], 'auto-task': ['admin'], settings: ['admin'], }; export const actionPermissionMap: Record = { 'ticket.create': ['admin', 'dba', 'developer'], - 'ticket.approve': ['admin', 'dba'], + 'ticket.approve': ['admin', 'dba', 'developer'], 'ticket.execute': ['admin', 'dba'], 'ticket.rollback': ['admin', 'dba'], 'ticket.delete': ['admin'], diff --git a/frontend/src/pages/TicketDetailPage.tsx b/frontend/src/pages/TicketDetailPage.tsx index 8ca81b7..2423fe1 100644 --- a/frontend/src/pages/TicketDetailPage.tsx +++ b/frontend/src/pages/TicketDetailPage.tsx @@ -226,9 +226,10 @@ export default function TicketDetailPage() { return { canApprove: false, canExecute: false, canRollback: false, canCancel: false }; } const adminOrDba = auth.user.role === 'admin' || auth.user.role === 'dba'; + const canApproveRole = adminOrDba || auth.user.role === 'developer'; const applicant = ticket.applicant_id === auth.user.id; return { - canApprove: adminOrDba && (ticket.status === 'pending' || ticket.status === 'reviewing'), + 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'),