feat: 工单审批流程支持 workflow 指定用户审批 + Redis 命令执行

1. 工单创建时读取 workflow_steps 的 approver_ids 创建审批记录
   - 有指定用户:为每个用户创建审批记录(任一通过即可)
   - 无指定用户:按 approver_role 角色匹配 fallback
   - 无 workflow:默认 admin 审批
2. Redis 工单执行:复用 getRedisClient + parseCommandParts 执行写命令
   - 支持 SET/DEL/HSET 等所有写操作
   - 解析 db0/db1 格式的目标库
   - 返回 [OK] 命令 → 结果
This commit is contained in:
logan committed 2026-05-31 21:40:30 +08:00
1 parent c5270f82c1
commit cdb52aa4a9
1 file changed
+100 -17
+100 -17
View File
@@ -1,6 +1,7 @@
package handler package handler
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
@@ -211,23 +212,76 @@ func CreateTicket(c *gin.Context) {
return return
} }
// Auto-create approval record: step 1, approver = instance owner or admin // Create approval records based on workflow or default to admin
stepName := "审核" if input.WorkflowID != nil && *input.WorkflowID > 0 {
stepType := "audit" // Load workflow steps
approverRole := "admin" var wfSteps []model.WorkflowStep
// Find first admin as default approver model.DB.Where("workflow_id = ?", *input.WorkflowID).Order("step_number").Find(&wfSteps)
var adminUser model.User for _, ws := range wfSteps {
if err := model.DB.Where("role = ? AND is_active = 1", "admin").First(&adminUser).Error; err == nil { if ws.StepType == "submit" {
adminID := adminUser.ID continue // submit step needs no approver
approval := model.TicketApproval{ }
TicketID: ticket.ID, // Parse approver_ids from JSON
StepNumber: 1, var approverIDs []int
StepName: &stepName, if ws.ApproverIDs != nil && *ws.ApproverIDs != "" {
StepType: &stepType, json.Unmarshal([]byte(*ws.ApproverIDs), &approverIDs)
ApproverID: &adminID, }
ApproverRole: &approverRole, // Create one approval record per approver (any one can approve)
if len(approverIDs) > 0 {
for _, uid := range approverIDs {
uidCopy := uid
stepName := ws.Name
stepType := ws.StepType
approval := model.TicketApproval{
TicketID: ticket.ID,
StepNumber: ws.StepNumber,
StepName: &stepName,
StepType: &stepType,
ApproverID: &uidCopy,
}
model.DB.Create(&approval)
}
} else {
// No specific users — use role-based fallback
stepName := ws.Name
stepType := ws.StepType
var roleUser model.User
role := "admin"
if ws.ApproverRole != nil && *ws.ApproverRole != "" {
role = *ws.ApproverRole
}
if model.DB.Where("role = ? AND is_active = 1", role).First(&roleUser).Error == nil {
rid := roleUser.ID
approval := model.TicketApproval{
TicketID: ticket.ID,
StepNumber: ws.StepNumber,
StepName: &stepName,
StepType: &stepType,
ApproverID: &rid,
ApproverRole: &role,
}
model.DB.Create(&approval)
}
}
}
} else {
// No workflow — default: first admin as approver
stepName := "审核"
stepType := "audit"
approverRole := "admin"
var adminUser model.User
if err := model.DB.Where("role = ? AND is_active = 1", "admin").First(&adminUser).Error; err == nil {
adminID := adminUser.ID
approval := model.TicketApproval{
TicketID: ticket.ID,
StepNumber: 1,
StepName: &stepName,
StepType: &stepType,
ApproverID: &adminID,
ApproverRole: &approverRole,
}
model.DB.Create(&approval)
} }
model.DB.Create(&approval)
} }
c.JSON(http.StatusCreated, ticket) c.JSON(http.StatusCreated, ticket)
@@ -339,7 +393,36 @@ func ExecuteTicket(c *gin.Context) {
var execErr string var execErr string
if ticket.DBType == "redis" { if ticket.DBType == "redis" {
execErr = "Redis command execution not yet supported in ticket workflow" // Execute Redis command (write operations allowed in ticket workflow)
redisDB := 0
if ticket.TargetDatabase != "" {
if n, err := strconv.Atoi(strings.TrimPrefix(ticket.TargetDatabase, "db")); err == nil && n >= 0 {
redisDB = n
}
}
client, err := getRedisClient(&instance, redisDB)
if err != nil {
execErr = fmt.Sprintf("Redis connection failed: %v", err)
} else {
defer client.Close()
ctx := context.Background()
parts := parseCommandParts(ticket.SQLContent)
if len(parts) == 0 {
execErr = "empty command"
} else {
cmd := strings.ToUpper(parts[0])
args := make([]interface{}, len(parts))
for i, p := range parts {
args[i] = p
}
result, err := client.Do(ctx, args...).Result()
if err != nil {
execErr = fmt.Sprintf("%s failed: %v", cmd, err)
} else {
execResult = fmt.Sprintf("[OK] %s → %v", ticket.SQLContent, result)
}
}
}
} else { } else {
// MySQL/PG execution // MySQL/PG execution
db, err := getDBConnection(&instance, ticket.TargetDatabase) db, err := getDBConnection(&instance, ticket.TargetDatabase)