Files
aiops/backend/controller/approval_workflow.go
T
attacker be2a377f01 feat(rbac): F3 前端 RBAC 按钮级控制
后端:
- 新增 GET /user/permissions 端点返回当前用户权限码
- Login 响应附带 permissions 数组
- collectUserPermissionCodes 用 raw SQL JOIN 避免 GORM many2many 表名问题
- admin 用户返回 ['*'] 通配符

前端:
- usePermission hook (hasPermission/hasAnyPermission/hasAllPermissions)
- <HasPermission> 组件 (permission/anyOf/allOf + fallback)
- zustand store 新增 permissions 字段
- Sidebar 菜单递归过滤 (42 菜单项全部标注 permission)
- ServerManager: 添加/导入/批量删除按钮按权限显隐
- ServerTableRow: 终端/编辑/文件/删除按权限显隐
- SystemUserTab: 新建/导入/编辑/删除按钮按权限显隐
- MainLayout: 页面刷新自动拉取权限
- Login: 登录时存储 permissions

验证:
- admin: permissions=['*'] 通配符
- viewer: 7 个只读权限 (dashboard:view/server:view/...)
- viewer 无 server:create/delete
- 10/10 回归测试通过
- npm run build 零错误
2026-06-25 22:51:25 +08:00

319 lines
8.6 KiB
Go

package controller
import (
"encoding/json"
"net/http"
"strconv"
"ezops/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// WorkflowHandler 审批流程配置处理器
type WorkflowHandler struct {
db *gorm.DB
}
// NewWorkflowHandler 创建处理器
func NewWorkflowHandler(db *gorm.DB) *WorkflowHandler {
return &WorkflowHandler{db: db}
}
// ListWorkflows 获取审批流程列表
func (h *WorkflowHandler) ListWorkflows(c *gin.Context) {
category := c.Query("category")
db := h.db.Model(&model.ApprovalWorkflow{})
if category != "" {
db = db.Where("category = ?", category)
}
var total int64
db.Count(&total)
var workflows []model.ApprovalWorkflow
db.Order("id ASC").Find(&workflows)
// 预加载步骤
for i := range workflows {
var steps []model.ApprovalWorkflowStep
h.db.Where("workflow_id = ?", workflows[i].ID).Order("step_number ASC").Find(&steps)
if steps == nil {
steps = []model.ApprovalWorkflowStep{}
}
workflows[i].Steps = steps
}
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: "success",
Data: model.ListResponse{
List: workflows,
Total: total,
},
})
}
// GetWorkflow 获取审批流程详情
func (h *WorkflowHandler) GetWorkflow(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var workflow model.ApprovalWorkflow
if err := h.db.First(&workflow, id).Error; err != nil {
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "流程不存在"})
return
}
var steps []model.ApprovalWorkflowStep
h.db.Where("workflow_id = ?", workflow.ID).Order("step_number ASC").Find(&steps)
if steps == nil {
steps = []model.ApprovalWorkflowStep{}
}
workflow.Steps = steps
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "success", Data: workflow})
}
// CreateWorkflow 创建审批流程
func (h *WorkflowHandler) CreateWorkflow(c *gin.Context) {
var req model.CreateWorkflowRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "参数错误: " + err.Error()})
return
}
if len(req.Steps) == 0 {
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "至少需要一个步骤"})
return
}
cat := req.Category
if cat == "" {
cat = "resource"
}
workflow := model.ApprovalWorkflow{
Name: req.Name,
Description: req.Description,
Category: cat,
IsActive: true,
}
tx := h.db.Begin()
if err := tx.Create(&workflow).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "创建失败"})
return
}
for _, s := range req.Steps {
step := model.ApprovalWorkflowStep{
WorkflowID: workflow.ID,
StepNumber: s.StepNumber,
Name: s.Name,
StepType: s.StepType,
ApproverIDs: s.ApproverIDs,
IsRequired: s.IsRequired,
TimeoutHours: s.TimeoutHours,
AutoApprove: s.AutoApproveTimeout,
}
if step.TimeoutHours == 0 {
step.TimeoutHours = 24
}
if err := tx.Create(&step).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "创建步骤失败"})
return
}
}
tx.Commit()
// Reload with steps
h.db.First(&workflow, workflow.ID)
var steps []model.ApprovalWorkflowStep
h.db.Where("workflow_id = ?", workflow.ID).Order("step_number ASC").Find(&steps)
workflow.Steps = steps
c.JSON(http.StatusCreated, model.Response{Code: 201, Message: "创建成功", Data: workflow})
}
// UpdateWorkflow 更新审批流程
func (h *WorkflowHandler) UpdateWorkflow(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var workflow model.ApprovalWorkflow
if err := h.db.First(&workflow, id).Error; err != nil {
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "流程不存在"})
return
}
var req model.UpdateWorkflowRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "参数错误: " + err.Error()})
return
}
tx := h.db.Begin()
// 更新基本字段
updates := map[string]interface{}{}
if req.Name != nil {
updates["name"] = *req.Name
}
if req.Description != nil {
updates["description"] = *req.Description
}
if req.Category != nil {
updates["category"] = *req.Category
}
if req.IsActive != nil {
updates["is_active"] = *req.IsActive
}
if req.IsDefault != nil {
// 设为默认时,先取消同 category 其他默认
if *req.IsDefault {
tx.Model(&model.ApprovalWorkflow{}).
Where("category = ? AND is_default = ?", workflow.Category, true).
Update("is_default", false)
}
updates["is_default"] = *req.IsDefault
}
if len(updates) > 0 {
if err := tx.Model(&workflow).Updates(updates).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "更新失败"})
return
}
}
// 更新步骤(先删后建)
if req.Steps != nil {
tx.Where("workflow_id = ?", workflow.ID).Delete(&model.ApprovalWorkflowStep{})
for _, s := range req.Steps {
step := model.ApprovalWorkflowStep{
WorkflowID: workflow.ID,
StepNumber: s.StepNumber,
Name: s.Name,
StepType: s.StepType,
ApproverIDs: s.ApproverIDs,
IsRequired: s.IsRequired,
TimeoutHours: s.TimeoutHours,
AutoApprove: s.AutoApproveTimeout,
}
if step.TimeoutHours == 0 {
step.TimeoutHours = 24
}
if err := tx.Create(&step).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "更新步骤失败"})
return
}
}
}
tx.Commit()
// Reload
h.db.First(&workflow, id)
var steps []model.ApprovalWorkflowStep
h.db.Where("workflow_id = ?", workflow.ID).Order("step_number ASC").Find(&steps)
workflow.Steps = steps
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "更新成功", Data: workflow})
}
// DeleteWorkflow 删除审批流程
func (h *WorkflowHandler) DeleteWorkflow(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
tx := h.db.Begin()
tx.Where("workflow_id = ?", id).Delete(&model.ApprovalWorkflowStep{})
if err := tx.Delete(&model.ApprovalWorkflow{}, id).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "删除失败"})
return
}
tx.Commit()
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "删除成功"})
}
// SetDefaultWorkflow 设为默认流程
func (h *WorkflowHandler) SetDefaultWorkflow(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var workflow model.ApprovalWorkflow
if err := h.db.First(&workflow, id).Error; err != nil {
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "流程不存在"})
return
}
tx := h.db.Begin()
// 取消同 category 的其他默认
tx.Model(&model.ApprovalWorkflow{}).
Where("category = ? AND is_default = ?", workflow.Category, true).
Update("is_default", false)
// 设置当前为默认
tx.Model(&workflow).Update("is_default", true)
tx.Commit()
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "已设为默认"})
}
// GetWorkflowSteps 获取流程步骤(用于申请页加载)
func (h *WorkflowHandler) GetWorkflowSteps(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var workflow model.ApprovalWorkflow
if err := h.db.First(&workflow, id).Error; err != nil {
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "流程不存在"})
return
}
var steps []model.ApprovalWorkflowStep
h.db.Where("workflow_id = ?", workflow.ID).Order("step_number ASC").Find(&steps)
if steps == nil {
steps = []model.ApprovalWorkflowStep{}
}
// Parse approver_ids from JSON string to arrays for the frontend
type stepResp struct {
ID uint `json:"id"`
StepNumber int `json:"step_number"`
Name string `json:"name"`
StepType string `json:"step_type"`
ApproverIDs []int `json:"approver_ids"`
IsRequired bool `json:"is_required"`
TimeoutHours int `json:"timeout_hours"`
AutoApproveTimeout bool `json:"auto_approve_on_timeout"`
}
result := make([]stepResp, 0, len(steps))
for _, s := range steps {
var ids []int
if s.ApproverIDs != "" {
json.Unmarshal([]byte(s.ApproverIDs), &ids)
}
if ids == nil {
ids = []int{}
}
result = append(result, stepResp{
ID: s.ID,
StepNumber: s.StepNumber,
Name: s.Name,
StepType: s.StepType,
ApproverIDs: ids,
IsRequired: s.IsRequired,
TimeoutHours: s.TimeoutHours,
AutoApproveTimeout: s.AutoApprove,
})
}
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "success", Data: result})
}