feat: ZRANGE WITHSCORES JSON自动解析为结构化表格 + Redis审计规则 + 前端安全检查
- ZRANGE WITHSCORES 自动检测JSON member,展开为 open/close/high/low/volume 等独立列 - Redis 审计: 只读命令安全,写命令warning,FLUSHALL等危险命令error - TicketCreatePage: auditResult.findings 添加 undefined 安全检查 - 审计API支持 instance_id 自动解析 db_type
This commit is contained in:
1 parent
9c5cb04cfa
commit
24c633d9f0
3 files changed
+255
-54
No files matched your search
@@ -13,6 +13,7 @@ import (
|
||||
"sql-platform/internal/middleware"
|
||||
"strings"
|
||||
"strconv"
|
||||
"sort"
|
||||
"encoding/json"
|
||||
"bytes"
|
||||
"regexp"
|
||||
@@ -869,7 +870,122 @@ func executeRedisQuery(instance *model.Instance, dbSelect string, command string
|
||||
|
||||
switch v := result.(type) {
|
||||
case []interface{}:
|
||||
// Array result (MGET, LRANGE, etc.)
|
||||
// ZRANGE WITHSCORES: Do() returns [[member1, score1], [member2, score2], ...]
|
||||
// Each element is a []interface{} pair
|
||||
if (cmd == "ZRANGE" || cmd == "ZRANGEBYSCORE") && len(v) >= 1 {
|
||||
withScores := false
|
||||
if pair, ok := v[0].([]interface{}); ok && len(pair) == 2 {
|
||||
// Check if second element is numeric (score)
|
||||
switch pair[1].(type) {
|
||||
case float64, int64, string:
|
||||
withScores = true
|
||||
}
|
||||
}
|
||||
if withScores {
|
||||
type jsonEntry struct {
|
||||
Member string
|
||||
Score float64
|
||||
Fields map[string]interface{}
|
||||
}
|
||||
entries := make([]jsonEntry, 0, len(v))
|
||||
allKeys := map[string]bool{}
|
||||
|
||||
for _, item := range v {
|
||||
pair, ok := item.([]interface{})
|
||||
if !ok || len(pair) < 2 {
|
||||
continue
|
||||
}
|
||||
member := fmt.Sprintf("%v", pair[0])
|
||||
var score float64
|
||||
switch s := pair[1].(type) {
|
||||
case float64:
|
||||
score = s
|
||||
case int64:
|
||||
score = float64(s)
|
||||
case string:
|
||||
score, _ = strconv.ParseFloat(s, 64)
|
||||
}
|
||||
entry := jsonEntry{Member: member, Score: score}
|
||||
|
||||
var parsed interface{}
|
||||
if json.Unmarshal([]byte(member), &parsed) == nil {
|
||||
if m, ok := parsed.(map[string]interface{}); ok {
|
||||
entry.Fields = m
|
||||
for k := range m {
|
||||
allKeys[k] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
if len(allKeys) > 0 {
|
||||
// JSON members → expand into table columns
|
||||
sortedKeys := make([]string, 0, len(allKeys))
|
||||
for k := range allKeys {
|
||||
sortedKeys = append(sortedKeys, k)
|
||||
}
|
||||
sort.Strings(sortedKeys)
|
||||
|
||||
cols := []string{"#", "timestamp"}
|
||||
cols = append(cols, sortedKeys...)
|
||||
|
||||
for idx, entry := range entries {
|
||||
row := map[string]interface{}{
|
||||
"#": idx + 1,
|
||||
"timestamp": int64(entry.Score),
|
||||
}
|
||||
for _, k := range sortedKeys {
|
||||
if entry.Fields != nil {
|
||||
if fv, ok := entry.Fields[k]; ok {
|
||||
row[k] = fv
|
||||
} else {
|
||||
row[k] = nil
|
||||
}
|
||||
} else {
|
||||
row[k] = nil
|
||||
}
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
columns = cols
|
||||
} else {
|
||||
// Plain members with scores
|
||||
for idx, item := range v {
|
||||
pair, ok := item.([]interface{})
|
||||
if !ok || len(pair) < 2 {
|
||||
continue
|
||||
}
|
||||
member := fmt.Sprintf("%v", pair[0])
|
||||
var score float64
|
||||
switch s := pair[1].(type) {
|
||||
case float64:
|
||||
score = s
|
||||
case int64:
|
||||
score = float64(s)
|
||||
case string:
|
||||
score, _ = strconv.ParseFloat(s, 64)
|
||||
}
|
||||
rows = append(rows, map[string]interface{}{
|
||||
"#": idx + 1,
|
||||
"member": member,
|
||||
"timestamp": int64(score),
|
||||
})
|
||||
}
|
||||
columns = []string{"#", "member", "timestamp"}
|
||||
}
|
||||
|
||||
total := len(rows)
|
||||
return gin.H{
|
||||
"columns": columns,
|
||||
"rows": rows,
|
||||
"total": total,
|
||||
"count": total,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Array result (MGET, LRANGE, ZRANGE without WITHSCORES, etc.)
|
||||
if cmd == "MGET" {
|
||||
for i, val := range v {
|
||||
rows = append(rows, map[string]interface{}{
|
||||
|
||||
Reference in new issue
Block a user