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
import (
"context"
"encoding/json"
"fmt"
"net/http"
@@ -211,23 +212,76 @@ func CreateTicket(c *gin.Context) {
return
}
// Auto-create approval record: step 1, approver = instance owner or admin
stepName := "审核"
stepType := "audit"
approverRole := "admin"
// Find first admin as default approver
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,
// Create approval records based on workflow or default to admin
if input.WorkflowID != nil && *input.WorkflowID > 0 {
// Load workflow steps
var wfSteps []model.WorkflowStep
model.DB.Where("workflow_id = ?", *input.WorkflowID).Order("step_number").Find(&wfSteps)
for _, ws := range wfSteps {
if ws.StepType == "submit" {
continue // submit step needs no approver
}
// Parse approver_ids from JSON
var approverIDs []int
if ws.ApproverIDs != nil && *ws.ApproverIDs != "" {
json.Unmarshal([]byte(*ws.ApproverIDs), &approverIDs)
}
// 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)
@@ -339,7 +393,36 @@ func ExecuteTicket(c *gin.Context) {
var execErr string
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 {
// MySQL/PG execution
db, err := getDBConnection(&instance, ticket.TargetDatabase)