refactor(app): 优化 URL 处理和 JSON 解析逻辑
- 更新应用程序处理程序中的 URL 赋值逻辑,确保静态文件使用原始路径 - 修改缓存数据库的 JSON 解析方法,使用 JsonToObj 函数替代 json.Unmarshal,提升代码可读性和性能 - 在 Map 和 Slice 类型中新增获取四舍五入浮点数的方法,增强数据处理能力 - 在 Obj 类型中添加四舍五入功能,支持精度控制 - 改进数据库查询结果的处理逻辑,确保数据类型的准确性和一致性 - 优化日志格式设置,增强日志信息的可读性
This commit is contained in:
+177
-14
@@ -4,7 +4,9 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
@@ -358,34 +360,195 @@ func (that *HoTimeDB) expandArrayPlaceholder(query string, args []interface{}) (
|
||||
return result.String(), newArgs
|
||||
}
|
||||
|
||||
const (
|
||||
dbTypeUnknown = iota
|
||||
dbTypeInteger
|
||||
dbTypeFloat
|
||||
dbTypeDecimal
|
||||
)
|
||||
|
||||
// classifyDBType 将数据库类型名分类,覆盖 MySQL/SQLite/PostgreSQL
|
||||
func classifyDBType(typeName string) int {
|
||||
t := strings.ToUpper(strings.TrimSpace(typeName))
|
||||
t = strings.TrimSuffix(strings.TrimSuffix(t, " UNSIGNED"), " SIGNED")
|
||||
t = strings.TrimSpace(t)
|
||||
base := t
|
||||
if idx := strings.IndexByte(t, '('); idx > 0 {
|
||||
base = t[:idx]
|
||||
}
|
||||
switch base {
|
||||
case "INT", "INTEGER", "BIGINT", "TINYINT", "SMALLINT", "MEDIUMINT",
|
||||
"INT2", "INT4", "INT8",
|
||||
"SERIAL", "BIGSERIAL", "SMALLSERIAL",
|
||||
"YEAR", "BOOL", "BOOLEAN", "OID":
|
||||
return dbTypeInteger
|
||||
case "DECIMAL", "NUMERIC", "NEWDECIMAL":
|
||||
return dbTypeDecimal
|
||||
case "FLOAT", "DOUBLE", "REAL", "FLOAT4", "FLOAT8", "DOUBLE PRECISION":
|
||||
return dbTypeFloat
|
||||
}
|
||||
if strings.Contains(base, "INT") {
|
||||
return dbTypeInteger
|
||||
}
|
||||
if strings.Contains(base, "DECIMAL") || strings.Contains(base, "NUMERIC") {
|
||||
return dbTypeDecimal
|
||||
}
|
||||
if strings.Contains(base, "DOUBLE") || strings.Contains(base, "FLOAT") {
|
||||
return dbTypeFloat
|
||||
}
|
||||
return dbTypeUnknown
|
||||
}
|
||||
|
||||
// getDecimalScale 提取 DECIMAL/NUMERIC 精度,失败返回 -1
|
||||
func getDecimalScale(colType *sql.ColumnType, typeName string) int {
|
||||
_, scale, ok := colType.DecimalSize()
|
||||
if ok && scale >= 0 {
|
||||
return int(scale)
|
||||
}
|
||||
// SQLite 不支持 DecimalSize,从类型名解析 "DECIMAL(10,2)" → 2
|
||||
upper := strings.ToUpper(typeName)
|
||||
if idx := strings.Index(upper, ","); idx > 0 {
|
||||
rest := upper[idx+1:]
|
||||
if end := strings.IndexByte(rest, ')'); end > 0 {
|
||||
if s, err := strconv.Atoi(strings.TrimSpace(rest[:end])); err == nil {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// getFloatScale 提取 FLOAT/DOUBLE 显式精度,无显式精度返回 -1 表示不截断
|
||||
func getFloatScale(colType *sql.ColumnType, typeName string) int {
|
||||
_, scale, ok := colType.DecimalSize()
|
||||
if ok && scale > 0 && scale < 20 {
|
||||
return int(scale)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// fixFloatValue 对 float64 值做类型感知精度修正
|
||||
func fixFloatValue(f float64, category int, scale int) interface{} {
|
||||
if math.IsNaN(f) || math.IsInf(f, 0) {
|
||||
return float64(0)
|
||||
}
|
||||
switch category {
|
||||
case dbTypeInteger:
|
||||
return int64(math.Round(f))
|
||||
case dbTypeDecimal:
|
||||
return roundFloat(f, scale)
|
||||
case dbTypeFloat:
|
||||
return roundFloat(f, scale)
|
||||
default:
|
||||
return f
|
||||
}
|
||||
}
|
||||
|
||||
// roundFloat 使用 strconv 精确舍入,避免 f*pow 中间值产生精度误差
|
||||
func roundFloat(f float64, scale int) interface{} {
|
||||
if math.IsNaN(f) || math.IsInf(f, 0) {
|
||||
return float64(0)
|
||||
}
|
||||
if scale == 0 {
|
||||
// DECIMAL(10,0) 明确是整数
|
||||
return math.Round(f)
|
||||
}
|
||||
if scale < 0 {
|
||||
// 精度未知,不修正,原样返回
|
||||
return f
|
||||
}
|
||||
s := strconv.FormatFloat(f, 'f', scale, 64)
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return f
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// convertBytes 对 []byte 类型的列做类型感知解析
|
||||
// MySQL 文本协议和 PG 的 DECIMAL/NUMERIC 以 []byte 返回,需要按列类型解析为正确 Go 类型
|
||||
func convertBytes(raw []byte, category int, scale int) interface{} {
|
||||
s := string(raw)
|
||||
switch category {
|
||||
case dbTypeInteger:
|
||||
if n, err := strconv.ParseInt(s, 10, 64); err == nil {
|
||||
return n
|
||||
}
|
||||
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
||||
return int64(math.Round(f))
|
||||
}
|
||||
return s
|
||||
case dbTypeDecimal:
|
||||
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
||||
return roundFloat(f, scale)
|
||||
}
|
||||
return s
|
||||
case dbTypeFloat:
|
||||
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
||||
return roundFloat(f, scale)
|
||||
}
|
||||
return s
|
||||
default:
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// Row 数据库数据解析
|
||||
func (that *HoTimeDB) Row(resl *sql.Rows) []Map {
|
||||
defer resl.Close()
|
||||
|
||||
dest := make([]Map, 0)
|
||||
strs, _ := resl.Columns()
|
||||
colTypes, _ := resl.ColumnTypes()
|
||||
|
||||
for i := 0; resl.Next(); i++ {
|
||||
lis := make(Map, 0)
|
||||
a := make([]interface{}, len(strs))
|
||||
colCount := len(strs)
|
||||
categories := make([]int, colCount)
|
||||
scales := make([]int, colCount)
|
||||
if colTypes != nil {
|
||||
for j := 0; j < len(colTypes) && j < colCount; j++ {
|
||||
typeName := colTypes[j].DatabaseTypeName()
|
||||
categories[j] = classifyDBType(typeName)
|
||||
switch categories[j] {
|
||||
case dbTypeDecimal:
|
||||
scales[j] = getDecimalScale(colTypes[j], typeName)
|
||||
case dbTypeFloat:
|
||||
scales[j] = getFloatScale(colTypes[j], typeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b := make([]interface{}, len(a))
|
||||
for j := 0; j < len(a); j++ {
|
||||
for resl.Next() {
|
||||
lis := make(Map, colCount)
|
||||
a := make([]interface{}, colCount)
|
||||
b := make([]interface{}, colCount)
|
||||
for j := 0; j < colCount; j++ {
|
||||
b[j] = &a[j]
|
||||
}
|
||||
err := resl.Scan(b...)
|
||||
if err != nil {
|
||||
if err := resl.Scan(b...); err != nil {
|
||||
that.LastErr.SetError(err)
|
||||
return nil
|
||||
}
|
||||
for j := 0; j < len(a); j++ {
|
||||
if a[j] != nil && reflect.ValueOf(a[j]).Type().String() == "[]uint8" {
|
||||
lis[strs[j]] = string(a[j].([]byte))
|
||||
} else {
|
||||
lis[strs[j]] = a[j] // 取实际类型
|
||||
for j := 0; j < colCount; j++ {
|
||||
val := a[j]
|
||||
if val == nil {
|
||||
lis[strs[j]] = nil
|
||||
continue
|
||||
}
|
||||
switch v := val.(type) {
|
||||
case []byte:
|
||||
lis[strs[j]] = convertBytes(v, categories[j], scales[j])
|
||||
case float64:
|
||||
lis[strs[j]] = fixFloatValue(v, categories[j], scales[j])
|
||||
case float32:
|
||||
lis[strs[j]] = fixFloatValue(float64(v), categories[j], scales[j])
|
||||
default:
|
||||
lis[strs[j]] = val
|
||||
}
|
||||
}
|
||||
|
||||
dest = append(dest, lis)
|
||||
}
|
||||
|
||||
if err := resl.Err(); err != nil {
|
||||
that.LastErr.SetError(err)
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user