feat: 角色权限体系改造 — developer可审批, auditor纯只读

角色权限矩阵:
- 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
This commit is contained in:
logan committed 2026-06-03 01:19:24 +08:00
1 parent 97fd4ff069
commit b1a3d74661
5 files changed
+48 -27

No files matched your search

+13 -7
View File
@@ -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
+26 -12
View File
@@ -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)
}
}
}
+3 -3
View File
@@ -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' },
];
+4 -4
View File
@@ -114,19 +114,19 @@ const routePermissionMap: Record<string, UserRole[]> = {
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<string, UserRole[]> = {
'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'],
+2 -1
View File
@@ -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'),