Files
MyGoNavi/internal/db/query_value.go
Syngnat e3b142053f 🐛 fix(precision): 修复查询链路与分页统计的大整数精度丢失
- 代理响应数据解码改为 UseNumber,避免默认 float64 吞精度
- 统一归一化 json.Number 与超界整数,超出 JS 安全范围转字符串
- 修复 DataViewer 总数解析,超大值不再误转 Number 参与分页
- refs #142
2026-03-02 14:40:59 +08:00

255 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package db
import (
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"reflect"
"strconv"
"strings"
"unicode"
"unicode/utf8"
)
const (
jsMaxSafeInteger int64 = 9007199254740991
jsMinSafeInteger int64 = -9007199254740991
jsMaxSafeUint uint64 = 9007199254740991
)
var (
jsMaxSafeBigInt = big.NewInt(jsMaxSafeInteger)
jsMinSafeBigInt = big.NewInt(jsMinSafeInteger)
)
// normalizeQueryValue normalizes driver-returned values for UI/JSON transport.
// 当前主要处理 []byte如果是可读文本则转为 string否则转为十六进制字符串避免前端出现“空白值”。
func normalizeQueryValue(v interface{}) interface{} {
return normalizeQueryValueWithDBType(v, "")
}
func normalizeQueryValueWithDBType(v interface{}, databaseTypeName string) interface{} {
if b, ok := v.([]byte); ok {
return bytesToDisplayValue(b, databaseTypeName)
}
return normalizeCompositeQueryValue(v)
}
func normalizeCompositeQueryValue(v interface{}) interface{} {
if v == nil {
return nil
}
switch typed := v.(type) {
case []interface{}:
items := make([]interface{}, len(typed))
for i, item := range typed {
items[i] = normalizeQueryValue(item)
}
return items
case map[string]interface{}:
out := make(map[string]interface{}, len(typed))
for key, value := range typed {
out[key] = normalizeQueryValue(value)
}
return out
case json.Number:
return normalizeJSONNumberForJS(typed)
}
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Pointer:
if rv.IsNil() {
return nil
}
return normalizeQueryValue(rv.Elem().Interface())
case reflect.Map:
if rv.IsNil() {
return nil
}
out := make(map[string]interface{}, rv.Len())
iter := rv.MapRange()
for iter.Next() {
out[mapKeyToString(iter.Key().Interface())] = normalizeQueryValue(iter.Value().Interface())
}
return out
case reflect.Slice, reflect.Array:
// []byte 在上层已单独处理,这里保留对其它切片/数组的递归规整。
if rv.Kind() == reflect.Slice && rv.IsNil() {
return nil
}
size := rv.Len()
items := make([]interface{}, size)
for i := 0; i < size; i++ {
items[i] = normalizeQueryValue(rv.Index(i).Interface())
}
return items
default:
return normalizeUnsafeIntegerForJS(rv, v)
}
}
func normalizeJSONNumberForJS(n json.Number) interface{} {
text := strings.TrimSpace(n.String())
if text == "" {
return ""
}
if integer, ok := parseJSONInteger(text); ok {
if integer.Cmp(jsMaxSafeBigInt) > 0 || integer.Cmp(jsMinSafeBigInt) < 0 {
return text
}
return integer.Int64()
}
if f, err := n.Float64(); err == nil {
return f
}
return text
}
func parseJSONInteger(text string) (*big.Int, bool) {
if text == "" {
return nil, false
}
start := 0
if text[0] == '+' || text[0] == '-' {
if len(text) == 1 {
return nil, false
}
start = 1
}
for i := start; i < len(text); i++ {
if text[i] < '0' || text[i] > '9' {
return nil, false
}
}
value, ok := new(big.Int).SetString(text, 10)
if !ok {
return nil, false
}
return value, true
}
func mapKeyToString(key interface{}) string {
if key == nil {
return "null"
}
if s, ok := key.(string); ok {
return s
}
return fmt.Sprintf("%v", key)
}
func bytesToDisplayValue(b []byte, databaseTypeName string) interface{} {
if b == nil {
return nil
}
if len(b) == 0 {
return ""
}
dbType := strings.ToUpper(strings.TrimSpace(databaseTypeName))
if isBitLikeDBType(dbType) {
if u, ok := bytesToUint64(b); ok {
// JS number precision is limited; keep large bitmasks as string.
if u <= jsMaxSafeUint {
return int64(u)
}
return fmt.Sprintf("%d", u)
}
}
if utf8.Valid(b) {
s := string(b)
if isMostlyPrintable(s) {
return s
}
}
// Fallback: some drivers return BIT(1) as []byte{0} / []byte{1} without type info.
if dbType == "" && len(b) == 1 && (b[0] == 0 || b[0] == 1) {
return int64(b[0])
}
return bytesToReadableString(b)
}
func bytesToReadableString(b []byte) interface{} {
if b == nil {
return nil
}
if len(b) == 0 {
return ""
}
return "0x" + hex.EncodeToString(b)
}
func isBitLikeDBType(typeName string) bool {
if typeName == "" {
return false
}
switch typeName {
case "BIT", "VARBIT":
return true
default:
}
return strings.HasPrefix(typeName, "BIT")
}
func bytesToUint64(b []byte) (uint64, bool) {
if len(b) == 0 || len(b) > 8 {
return 0, false
}
var u uint64
for _, v := range b {
u = (u << 8) | uint64(v)
}
return u, true
}
func normalizeUnsafeIntegerForJS(rv reflect.Value, original interface{}) interface{} {
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
n := rv.Int()
if n > jsMaxSafeInteger || n < jsMinSafeInteger {
return strconv.FormatInt(n, 10)
}
return original
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
u := rv.Uint()
if u > jsMaxSafeUint {
return strconv.FormatUint(u, 10)
}
return original
default:
return original
}
}
func isMostlyPrintable(s string) bool {
if s == "" {
return true
}
total := 0
printable := 0
for _, r := range s {
total++
switch r {
case '\n', '\r', '\t':
printable++
continue
default:
}
if unicode.IsPrint(r) {
printable++
}
}
// 允许少量不可见字符,避免把正常文本误判为二进制。
return printable*100 >= total*90
}