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
|
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)
|
||||||
|
|||||||
Reference in new issue
Block a user