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:
1 parent
c5270f82c1
commit
cdb52aa4a9
1 file changed
+100
-17
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user