结构修改
This commit is contained in:
parent
abb52eb1d3
commit
2bb054940a
|
|
@ -6,10 +6,13 @@ package main
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/biz"
|
"ai_scheduler/internal/biz"
|
||||||
"ai_scheduler/internal/config"
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/data/impl"
|
||||||
"ai_scheduler/internal/pkg"
|
"ai_scheduler/internal/pkg"
|
||||||
"ai_scheduler/internal/server"
|
"ai_scheduler/internal/server"
|
||||||
"ai_scheduler/internal/services"
|
"ai_scheduler/internal/services"
|
||||||
"ai_scheduler/internal/tools"
|
"ai_scheduler/internal/tools"
|
||||||
|
"ai_scheduler/utils"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
"github.com/gofiber/fiber/v2/log"
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
)
|
)
|
||||||
|
|
@ -22,6 +25,8 @@ func InitializeApp(*config.Config, log.AllLogger) (*server.Servers, func(), erro
|
||||||
pkg.ProviderSetClient,
|
pkg.ProviderSetClient,
|
||||||
services.ProviderSetServices,
|
services.ProviderSetServices,
|
||||||
biz.ProviderSetBiz,
|
biz.ProviderSetBiz,
|
||||||
|
impl.ProviderImpl,
|
||||||
|
utils.ProviderUtils,
|
||||||
))
|
))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,20 @@ ollama:
|
||||||
timeout: "120s"
|
timeout: "120s"
|
||||||
level: "info"
|
level: "info"
|
||||||
format: "json"
|
format: "json"
|
||||||
|
|
||||||
|
sys:
|
||||||
|
session_len: 3
|
||||||
|
|
||||||
|
Redis:
|
||||||
|
host: 47.97.27.195:6379
|
||||||
|
type: node
|
||||||
|
pass: lansexiongdi@666
|
||||||
|
key: report-api-test
|
||||||
|
pollSize: 5 #连接池大小,不配置,或配置为0表示不启用连接池
|
||||||
|
minIdleConns: 2 #最小空闲连接数
|
||||||
|
maxIdleTime: 30 #每个连接最大空闲时间,如果超过了这个时间会被关闭
|
||||||
|
tls: 30
|
||||||
|
db:
|
||||||
|
DB:
|
||||||
|
driver: mysql
|
||||||
|
source: transfer:Lsxd@34234QW@tcp(lsxdpolar.rwlb.rds.aliyuncs.com:3306)/transfer?charset=utf8mb4&parseTime=true&
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 使用方法:
|
||||||
|
# ./genModel.sh usercenter user
|
||||||
|
# ./genModel.sh usercenter user_auth
|
||||||
|
# 再将./genModel下的文件剪切到对应服务的model目录里面,记得改package
|
||||||
|
|
||||||
|
|
||||||
|
#生成的表名
|
||||||
|
tables=$1
|
||||||
|
#表生成的genmodel目录
|
||||||
|
modeldir=./internal/data/model
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
prefix=ai_
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
gentool --dsn "root:SD###sdf323r343@tcp(121.199.38.107:3306)/sys_ai?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai" -outPath ${modeldir} -onlyModel -modelPkgName "model" -tables ${prefix}${tables}
|
||||||
44
go.mod
44
go.mod
|
|
@ -5,42 +5,41 @@ go 1.24.0
|
||||||
toolchain go1.24.7
|
toolchain go1.24.7
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/emirpasic/gods v1.18.1
|
||||||
|
github.com/go-kratos/kratos/v2 v2.9.1
|
||||||
github.com/gofiber/fiber/v2 v2.52.9
|
github.com/gofiber/fiber/v2 v2.52.9
|
||||||
github.com/gofiber/websocket/v2 v2.2.1
|
github.com/gofiber/websocket/v2 v2.2.1
|
||||||
github.com/google/uuid v1.6.0
|
|
||||||
github.com/google/wire v0.7.0
|
github.com/google/wire v0.7.0
|
||||||
github.com/ollama/ollama v0.11.10
|
github.com/ollama/ollama v0.11.10
|
||||||
|
github.com/redis/go-redis/v9 v9.14.0
|
||||||
github.com/spf13/viper v1.17.0
|
github.com/spf13/viper v1.17.0
|
||||||
go.opentelemetry.io/otel v1.38.0
|
google.golang.org/grpc v1.61.1
|
||||||
|
google.golang.org/protobuf v1.34.1
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
gorm.io/driver/mysql v1.6.0
|
||||||
|
gorm.io/gorm v1.31.0
|
||||||
|
xorm.io/builder v0.3.13
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
github.com/bytedance/sonic v1.11.6 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
|
||||||
github.com/fasthttp/websocket v1.5.3 // indirect
|
github.com/fasthttp/websocket v1.5.3 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
|
||||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/klauspost/compress v1.17.9 // indirect
|
github.com/klauspost/compress v1.17.9 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||||
|
|
@ -50,22 +49,17 @@ require (
|
||||||
github.com/spf13/afero v1.10.0 // indirect
|
github.com/spf13/afero v1.10.0 // indirect
|
||||||
github.com/spf13/cast v1.5.1 // indirect
|
github.com/spf13/cast v1.5.1 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
golang.org/x/arch v0.8.0 // indirect
|
|
||||||
golang.org/x/crypto v0.36.0 // indirect
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
|
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
|
||||||
golang.org/x/net v0.38.0 // indirect
|
|
||||||
golang.org/x/sys v0.31.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
94
go.sum
94
go.sum
|
|
@ -36,23 +36,25 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||||
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
|
||||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
|
||||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
|
@ -60,6 +62,10 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
|
@ -72,25 +78,13 @@ github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0X
|
||||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
|
||||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
|
||||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-kratos/kratos/v2 v2.9.1 h1:EGif6/S/aK/RCR5clIbyhioTNyoSrii3FC118jG40Z0=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-kratos/kratos/v2 v2.9.1/go.mod h1:a1MQLjMhIh7R0kcJS9SzJYR43BRI7EPzzN0J1Ksu2bA=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
|
||||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
|
||||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
|
||||||
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
|
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
|
||||||
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||||
github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w=
|
github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w=
|
||||||
|
|
@ -120,6 +114,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
|
@ -133,7 +129,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
|
@ -162,28 +157,23 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
|
@ -195,11 +185,6 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
||||||
github.com/ollama/ollama v0.11.10 h1:J9zaoTPwIXOrYXCRAqI7rV4cJ+FOMuQc/vBqQ5GIdWg=
|
github.com/ollama/ollama v0.11.10 h1:J9zaoTPwIXOrYXCRAqI7rV4cJ+FOMuQc/vBqQ5GIdWg=
|
||||||
github.com/ollama/ollama v0.11.10/go.mod h1:9+1//yWPsDE2u+l1a5mpaKrYw4VdnSsRU3ioq5BvMms=
|
github.com/ollama/ollama v0.11.10/go.mod h1:9+1//yWPsDE2u+l1a5mpaKrYw4VdnSsRU3ioq5BvMms=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
|
|
@ -210,11 +195,13 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE=
|
||||||
|
github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
|
github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
|
||||||
github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
|
github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
|
|
@ -241,17 +228,12 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||||
|
|
@ -268,15 +250,10 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
|
||||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
|
||||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
|
||||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
|
||||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
|
@ -374,6 +351,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||||
|
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
@ -411,7 +390,6 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
|
@ -544,6 +522,8 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D
|
||||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
|
@ -560,6 +540,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
|
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
|
||||||
|
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
|
@ -583,6 +565,10 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||||
|
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||||
|
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
|
||||||
|
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
@ -590,8 +576,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
|
xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
|
||||||
|
xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,98 @@
|
||||||
package biz
|
package biz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
errors "ai_scheduler/internal/data/error"
|
||||||
|
"ai_scheduler/internal/data/impl"
|
||||||
|
"ai_scheduler/internal/data/model"
|
||||||
"ai_scheduler/internal/entitys"
|
"ai_scheduler/internal/entitys"
|
||||||
"ai_scheduler/internal/tools"
|
"ai_scheduler/internal/tools"
|
||||||
|
"ai_scheduler/tmpl/dataTemp"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gofiber/websocket/v2"
|
||||||
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AiRouterService 智能路由服务
|
// AiRouterService 智能路由服务
|
||||||
type AiRouterService struct {
|
type AiRouterService struct {
|
||||||
aiClient entitys.AIClient
|
aiClient entitys.AIClient
|
||||||
toolManager *tools.Manager
|
toolManager *tools.Manager
|
||||||
|
sessionImpl *impl.SessionImpl
|
||||||
|
conf *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRouterService 创建路由服务
|
// NewRouterService 创建路由服务
|
||||||
func NewAiRouterBiz(aiClient entitys.AIClient, toolManager *tools.Manager) entitys.RouterService {
|
func NewAiRouterBiz(aiClient entitys.AIClient, toolManager *tools.Manager, sessionImpl *impl.SessionImpl, conf *config.Config) entitys.RouterService {
|
||||||
return &AiRouterService{
|
return &AiRouterService{
|
||||||
aiClient: aiClient,
|
aiClient: aiClient,
|
||||||
toolManager: toolManager,
|
toolManager: toolManager,
|
||||||
|
sessionImpl: sessionImpl,
|
||||||
|
conf: conf,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route 执行智能路由
|
// Route 执行智能路由
|
||||||
func (r *AiRouterService) Route(ctx context.Context, req *entitys.ChatRequest) (*entitys.ChatResponse, error) {
|
func (r *AiRouterService) Route(ctx context.Context, req *entitys.ChatRequest) (*entitys.ChatResponse, error) {
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route 执行智能路由
|
||||||
|
func (r *AiRouterService) RouteWithSocket(c *websocket.Conn, req *entitys.ChatSockRequest) error {
|
||||||
|
session := c.Headers("x-session", "")
|
||||||
|
if len(session) == 0 {
|
||||||
|
return errors.SessionNotFound
|
||||||
|
}
|
||||||
|
auth := c.Headers("x-authorization", "")
|
||||||
|
|
||||||
|
if len(auth) == 0 {
|
||||||
|
return errors.AuthNotFound
|
||||||
|
}
|
||||||
|
key := c.Headers("x-app-key", "")
|
||||||
|
if len(key) == 0 {
|
||||||
|
return errors.KeyNotFound
|
||||||
|
}
|
||||||
|
var sysInfo model.AiSy
|
||||||
|
cond := builder.NewCond()
|
||||||
|
cond = cond.And(builder.Eq{"app_key": key})
|
||||||
|
err := r.sessionImpl.GetOneBySearchToStrut(&cond, &sysInfo)
|
||||||
|
if err != nil {
|
||||||
|
return errors.SysNotFound
|
||||||
|
}
|
||||||
|
cond = builder.NewCond()
|
||||||
|
cond = cond.And(builder.Eq{"session_id": session})
|
||||||
|
history, _, err := r.sessionImpl.GetList(&cond, &dataTemp.ReqPageBo{Limit: r.conf.Sys.SessionLen})
|
||||||
|
if err != nil {
|
||||||
|
return errors.SystemError
|
||||||
|
}
|
||||||
|
fmt.Printf("history:%v\n", history)
|
||||||
|
var (
|
||||||
|
messages = make([]entitys.Message, 0)
|
||||||
|
onece sync.Once
|
||||||
|
)
|
||||||
|
onece.Do(func() {
|
||||||
|
|
||||||
|
messages = append(messages, entitys.Message{
|
||||||
|
Role: "system",
|
||||||
|
Content: r.buildSystemPrompt(sysInfo.SysPrompt),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
messages = append(messages, entitys.Message{}, entitys.Message{
|
||||||
|
Role: "assistant",
|
||||||
|
Content: r.buildIntentPrompt(req.Text),
|
||||||
|
}, entitys.Message{
|
||||||
|
Role: "user",
|
||||||
|
Content: req.Text,
|
||||||
|
})
|
||||||
// 构建消息
|
// 构建消息
|
||||||
//messages := []entitys.Message{
|
//messages := []entitys.Message{
|
||||||
// // {
|
|
||||||
// // Role: "system",
|
|
||||||
// // Content: r.buildSystemPrompt(),
|
|
||||||
// // },
|
|
||||||
// {
|
|
||||||
// Role: "assistant",
|
|
||||||
// Content: r.buildIntentPrompt(req.UserInput),
|
|
||||||
// },
|
|
||||||
// {
|
// {
|
||||||
// Role: "user",
|
// Role: "user",
|
||||||
// Content: req.UserInput,
|
// Content: req.UserInput,
|
||||||
|
|
@ -113,35 +170,36 @@ func (r *AiRouterService) Route(ctx context.Context, req *entitys.ChatRequest) (
|
||||||
//log.Printf("Router processed request: %s, used %d tools", req.UserInput, len(toolResults))
|
//log.Printf("Router processed request: %s, used %d tools", req.UserInput, len(toolResults))
|
||||||
|
|
||||||
//return finalResponse, nil
|
//return finalResponse, nil
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildSystemPrompt 构建系统提示词
|
// buildSystemPrompt 构建系统提示词
|
||||||
func (r *AiRouterService) buildSystemPrompt() string {
|
func (r *AiRouterService) buildSystemPrompt(prompt string) string {
|
||||||
prompt := `你是一个智能路由系统,你的任务是根据用户输入判断用户的意图,并且执行对应的任务。`
|
if len(prompt) == 0 {
|
||||||
|
prompt = "[system] 你是一个智能路由系统,核心职责是 **精准解析用户意图并路由至对应任务模块**\n[rule]\n1.返回以下格式的JSON:{ \"index\": \"工具索引index\", \"confidence\": 0.0-1.0,\"reasoning\": \"判断理由\"}\n2.严格返回字符串格式,禁用markdown格式返回\n3.只返回json字符串,不包含任何其他解释性文字\n4.当用户意图非常不清晰时使用,尝试进行追问具体希望查询内容"
|
||||||
|
}
|
||||||
|
|
||||||
return prompt
|
return prompt
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildIntentPrompt 构建意图识别提示词
|
// buildIntentPrompt 构建意图识别提示词
|
||||||
func (r *AiRouterService) buildIntentPrompt(userInput string) string {
|
func (r *AiRouterService) buildIntentPrompt(userInput string) string {
|
||||||
prompt := `请分析以下用户输入,判断用户的意图类型。
|
prompt := `##任务
|
||||||
|
分析用户输入,判断用户的意图类型,没有使用Markdown格式的json格式回复
|
||||||
用户输入:{user_input}
|
##意图类型
|
||||||
|
1. product_diagnosis - 商品诊断:用户想要查询、诊断或了解商品相关信息
|
||||||
意图类型说明:
|
2. order_diagnosis - 订单诊断:用户想要查询、诊断或了解订单相关信息
|
||||||
1. order_diagnosis - 订单诊断:用户想要查询、诊断或了解订单相关信息
|
3. knowledge_qa - 知识问答:用户想要进行一般性问答或获取知识信息
|
||||||
2. knowledge_qa - 知识问答:用户想要进行一般性问答或获取知识信息
|
##判断规则
|
||||||
|
1.当用户意图不够清晰且不匹配 knowledge_qa 以外意图时,使用knowledge_qa
|
||||||
- 当用户意图不够清晰且不匹配 knowledge_qa 以外意图时,使用knowledge_qa
|
2.当用户意图非常不清晰时使用 unknown
|
||||||
- 当用户意图非常不清晰时使用 unknown
|
##格式要求
|
||||||
|
1.返回以下格式的JSON:
|
||||||
请只返回以下格式的JSON:
|
{ "intent": "product_diagnosis" | "order_diagnosis" | "knowledge_qa" | "unknown", "confidence": 0.0-1.0,"reasoning": "判断理由"}
|
||||||
{
|
2.严格返回字符串格式,禁用markdown格式返回
|
||||||
"intent": "order_diagnosis" | "knowledge_qa" | "unknown",
|
3.只返回json字符串,不包含任何其他解释性文字
|
||||||
"confidence": 0.0-1.0,
|
## 用户当前的问题是:
|
||||||
"reasoning": "判断理由"
|
{user_input}
|
||||||
}
|
|
||||||
`
|
`
|
||||||
|
|
||||||
prompt = strings.ReplaceAll(prompt, "{user_input}", userInput)
|
prompt = strings.ReplaceAll(prompt, "{user_input}", userInput)
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,16 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Server ServerConfig `mapstructure:"server"`
|
Server ServerConfig `mapstructure:"server"`
|
||||||
Ollama OllamaConfig `mapstructure:"ollama"`
|
Ollama OllamaConfig `mapstructure:"ollama"`
|
||||||
|
Sys SysConfig `mapstructure:"sys"`
|
||||||
Tools ToolsConfig `mapstructure:"tools"`
|
Tools ToolsConfig `mapstructure:"tools"`
|
||||||
Logging LoggingConfig `mapstructure:"logging"`
|
Logging LoggingConfig `mapstructure:"logging"`
|
||||||
|
Redis *Redis `protobuf:"bytes,1,opt,name=Redis,proto3" json:"Redis,omitempty"`
|
||||||
|
DB *DB `protobuf:"bytes,3,opt,name=TransDB,proto3" json:"TransDB,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SysConfig 系统配置
|
||||||
|
type SysConfig struct {
|
||||||
|
SessionLen int `mapstructure:"session_len"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerConfig 服务器配置
|
// ServerConfig 服务器配置
|
||||||
|
|
@ -28,6 +36,27 @@ type OllamaConfig struct {
|
||||||
Timeout time.Duration `mapstructure:"timeout"`
|
Timeout time.Duration `mapstructure:"timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Redis struct {
|
||||||
|
Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
|
||||||
|
Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
|
||||||
|
Pass string `protobuf:"bytes,3,opt,name=pass,proto3" json:"pass,omitempty"`
|
||||||
|
Key string `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"`
|
||||||
|
Tls int32 `protobuf:"varint,5,opt,name=tls,proto3" json:"tls,omitempty"`
|
||||||
|
Db int32 `protobuf:"varint,6,opt,name=db,proto3" json:"db,omitempty"`
|
||||||
|
MaxIdle int32 `protobuf:"varint,7,opt,name=maxIdle,proto3" json:"maxIdle,omitempty"`
|
||||||
|
PoolSize int32 `protobuf:"varint,8,opt,name=poolSize,proto3" json:"poolSize,omitempty"`
|
||||||
|
MaxIdleTime int32 `protobuf:"varint,9,opt,name=maxIdleTime,proto3" json:"maxIdleTime,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
Driver string `protobuf:"bytes,1,opt,name=driver,proto3" json:"driver,omitempty"`
|
||||||
|
Source string `protobuf:"bytes,2,opt,name=source,proto3" json:"source,omitempty"`
|
||||||
|
MaxIdle int32 `protobuf:"varint,3,opt,name=maxIdle,proto3" json:"maxIdle,omitempty"`
|
||||||
|
MaxOpen int32 `protobuf:"varint,4,opt,name=maxOpen,proto3" json:"maxOpen,omitempty"`
|
||||||
|
MaxLifetime int32 `protobuf:"varint,5,opt,name=maxLifetime,proto3" json:"maxLifetime,omitempty"`
|
||||||
|
IsDebug bool `protobuf:"varint,6,opt,name=isDebug,proto3" json:"isDebug,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// ToolsConfig 工具配置
|
// ToolsConfig 工具配置
|
||||||
type ToolsConfig struct {
|
type ToolsConfig struct {
|
||||||
Weather ToolConfig `mapstructure:"weather"`
|
Weather ToolConfig `mapstructure:"weather"`
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
package constant
|
package constant
|
||||||
|
|
||||||
const ()
|
type ConnStatus int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConnStatusClosed ConnStatus = iota
|
||||||
|
ConnStatusNormal
|
||||||
|
ConnStatusIgnore
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,11 @@ var (
|
||||||
SystemError = &BusinessErr{code: "0005", message: "系统错误"}
|
SystemError = &BusinessErr{code: "0005", message: "系统错误"}
|
||||||
|
|
||||||
SupplierNotFound = &BusinessErr{code: "0006", message: "供应商不存在"}
|
SupplierNotFound = &BusinessErr{code: "0006", message: "供应商不存在"}
|
||||||
SupplierApiError = &BusinessErr{code: "0007", message: "第三方供应商接口报错"}
|
SessionNotFound = &BusinessErr{code: "0007", message: "未找到会话信息"}
|
||||||
|
AuthNotFound = &BusinessErr{code: "0008", message: "身份验证失败"}
|
||||||
InvalidParam = &BusinessErr{code: InvalidParamCode, message: "无效参数"}
|
KeyNotFound = &BusinessErr{code: "0009", message: "身份验证失败"}
|
||||||
|
SysNotFound = &BusinessErr{code: "0010", message: "未找到系统信息"}
|
||||||
|
InvalidParam = &BusinessErr{code: InvalidParamCode, message: "无效参数"}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -41,7 +43,3 @@ func NewBusinessErr(code string, message string) *BusinessErr {
|
||||||
func (e *BusinessErr) Wrap(err error) *BusinessErr {
|
func (e *BusinessErr) Wrap(err error) *BusinessErr {
|
||||||
return NewBusinessErr(e.code, err.Error())
|
return NewBusinessErr(e.code, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func SupplierApiErrorDiy(message string) *BusinessErr {
|
|
||||||
return &BusinessErr{code: SupplierApiError.code, message: message}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package impl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"trans_hub/app/physical_goods/supplier/goods/service/internal/data/model"
|
|
||||||
"trans_hub/tmpl/dataTemp"
|
|
||||||
"trans_hub/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NotifyDataImpl struct {
|
|
||||||
dataTemp.DataTemp
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOrderImpl(db *utils.Db) *NotifyDataImpl {
|
|
||||||
return &NotifyDataImpl{*dataTemp.NewDataTemp(db, new(model.SupplierNotifyDatum))}
|
|
||||||
}
|
|
||||||
|
|
@ -4,4 +4,4 @@ import (
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ProviderImpl = wire.NewSet(NewOrderImpl)
|
var ProviderImpl = wire.NewSet(NewSessionImpl)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/model"
|
||||||
|
"ai_scheduler/tmpl/dataTemp"
|
||||||
|
"ai_scheduler/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SessionImpl struct {
|
||||||
|
dataTemp.DataTemp
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSessionImpl(db *utils.Db) *SessionImpl {
|
||||||
|
return &SessionImpl{*dataTemp.NewDataTemp(db, new(model.AiSession))}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/model"
|
||||||
|
"ai_scheduler/tmpl/dataTemp"
|
||||||
|
"ai_scheduler/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SysImpl struct {
|
||||||
|
dataTemp.DataTemp
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSysImpl(db *utils.Db) *SysImpl {
|
||||||
|
return &SysImpl{*dataTemp.NewDataTemp(db, new(model.AiSy))}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/model"
|
||||||
|
"ai_scheduler/tmpl/dataTemp"
|
||||||
|
"ai_scheduler/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TaskImpl struct {
|
||||||
|
dataTemp.DataTemp
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTaskImpl(db *utils.Db) *TaskImpl {
|
||||||
|
return &TaskImpl{*dataTemp.NewDataTemp(db, new(model.AiTask))}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TableNameAiSession = "ai_session"
|
||||||
|
|
||||||
|
// AiSession mapped from table <ai_session>
|
||||||
|
type AiSession struct {
|
||||||
|
SysID int32 `gorm:"column:sys_id;not null" json:"sys_id"`
|
||||||
|
SessionID string `gorm:"column:session_id;primaryKey" json:"session_id"`
|
||||||
|
KnowlegeSessionID string `gorm:"column:knowlege_session_id;not null" json:"knowlege_session_id"`
|
||||||
|
Title string `gorm:"column:title;not null" json:"title"`
|
||||||
|
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
|
||||||
|
UpdateAt time.Time `gorm:"column:update_at;default:CURRENT_TIMESTAMP" json:"update_at"`
|
||||||
|
Status int32 `gorm:"column:status;not null" json:"status"`
|
||||||
|
DeleteAt time.Time `gorm:"column:delete_at" json:"delete_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName AiSession's table name
|
||||||
|
func (*AiSession) TableName() string {
|
||||||
|
return TableNameAiSession
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TableNameAiSy = "ai_sys"
|
||||||
|
|
||||||
|
// AiSy mapped from table <ai_sys>
|
||||||
|
type AiSy struct {
|
||||||
|
SysID int32 `gorm:"column:sys_id;primaryKey;autoIncrement:true" json:"sys_id"`
|
||||||
|
SysName string `gorm:"column:sys_name;not null" json:"sys_name"`
|
||||||
|
AppKey string `gorm:"column:app_key;not null" json:"app_key"`
|
||||||
|
KnowlegeTenantKey string `gorm:"column:knowlege_tenant_key;not null" json:"knowlege_tenant_key"`
|
||||||
|
KnowlegeBaseID string `gorm:"column:knowlege_base_id;not null" json:"knowlege_base_id"`
|
||||||
|
SysPrompt string `gorm:"column:sys_prompt" json:"sys_prompt"`
|
||||||
|
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
|
||||||
|
UpdateAt time.Time `gorm:"column:update_at;default:CURRENT_TIMESTAMP" json:"update_at"`
|
||||||
|
Status int32 `gorm:"column:status;not null" json:"status"`
|
||||||
|
DeleteAt time.Time `gorm:"column:delete_at" json:"delete_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName AiSy's table name
|
||||||
|
func (*AiSy) TableName() string {
|
||||||
|
return TableNameAiSy
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TableNameAiTask = "ai_task"
|
||||||
|
|
||||||
|
// AiTask mapped from table <ai_task>
|
||||||
|
type AiTask struct {
|
||||||
|
TaskID int32 `gorm:"column:task_id;primaryKey" json:"task_id"`
|
||||||
|
SysID int32 `gorm:"column:sys_id;not null" json:"sys_id"`
|
||||||
|
Name string `gorm:"column:name;not null" json:"name"`
|
||||||
|
Index string `gorm:"column:index;not null" json:"index"`
|
||||||
|
Desc string `gorm:"column:desc;not null" json:"desc"`
|
||||||
|
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
|
||||||
|
UpdateAt time.Time `gorm:"column:update_at;default:CURRENT_TIMESTAMP" json:"update_at"`
|
||||||
|
Status int32 `gorm:"column:status;not null;default:1" json:"status"`
|
||||||
|
DeleteAt time.Time `gorm:"column:delete_at" json:"delete_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName AiTask's table name
|
||||||
|
func (*AiTask) TableName() string {
|
||||||
|
return TableNameAiTask
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,8 @@ package entitys
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/gofiber/websocket/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ChatRequest 聊天请求
|
// ChatRequest 聊天请求
|
||||||
|
|
@ -80,4 +82,5 @@ type Message struct {
|
||||||
// RouterService 路由服务接口
|
// RouterService 路由服务接口
|
||||||
type RouterService interface {
|
type RouterService interface {
|
||||||
Route(ctx context.Context, req *ChatRequest) (*ChatResponse, error)
|
Route(ctx context.Context, req *ChatRequest) (*ChatResponse, error)
|
||||||
|
RouteWithSocket(c *websocket.Conn, req *ChatSockRequest) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"knowlege-lsxd/internal/config"
|
|
||||||
"knowlege-lsxd/internal/types"
|
|
||||||
"knowlege-lsxd/internal/types/interfaces"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 无需认证的API列表
|
|
||||||
var noAuthAPI = map[string][]string{
|
|
||||||
"/api/v1/test-data": {"GET"},
|
|
||||||
"/api/v1/tenants": {"POST"},
|
|
||||||
"/api/v1/initialization/*": {"GET", "POST"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查请求是否在无需认证的API列表中
|
|
||||||
func isNoAuthAPI(path string, method string) bool {
|
|
||||||
for api, methods := range noAuthAPI {
|
|
||||||
// 如果以*结尾,按照前缀匹配,否则按照全路径匹配
|
|
||||||
if strings.HasSuffix(api, "*") {
|
|
||||||
if strings.HasPrefix(path, strings.TrimSuffix(api, "*")) && slices.Contains(methods, method) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else if path == api && slices.Contains(methods, method) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auth 认证中间件
|
|
||||||
func Auth(tenantService interfaces.TenantService, cfg *config.Config) gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
// ignore OPTIONS request
|
|
||||||
if c.Request.Method == "OPTIONS" {
|
|
||||||
c.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查请求是否在无需认证的API列表中
|
|
||||||
if isNoAuthAPI(c.Request.URL.Path, c.Request.Method) {
|
|
||||||
c.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get API Key from request header
|
|
||||||
apiKey := c.GetHeader("X-API-Key")
|
|
||||||
if apiKey == "" {
|
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get tenant information
|
|
||||||
//tenantID, err := tenantService.ExtractTenantIDFromAPIKey(apiKey)
|
|
||||||
//if err != nil {
|
|
||||||
// c.JSON(http.StatusUnauthorized, gin.H{
|
|
||||||
// "error": "Unauthorized: invalid API key format",
|
|
||||||
// })
|
|
||||||
// c.Abort()
|
|
||||||
// return
|
|
||||||
//}
|
|
||||||
|
|
||||||
// Verify API key validity (matches the one in database)
|
|
||||||
t, err := tenantService.GetTenantByApiKey(c.Request.Context(), apiKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error getting tenant by ID: %v, tenantID: %d, apiKey: %s", err, t.ID, apiKey)
|
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{
|
|
||||||
"error": "Unauthorized: invalid API key",
|
|
||||||
})
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if t == nil || t.APIKey != apiKey {
|
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{
|
|
||||||
"error": "Unauthorized: invalid API key",
|
|
||||||
})
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store tenant ID in context
|
|
||||||
c.Set(types.TenantIDContextKey.String(), t.ID)
|
|
||||||
c.Set(types.TenantInfoContextKey.String(), t)
|
|
||||||
c.Request = c.Request.WithContext(
|
|
||||||
context.WithValue(
|
|
||||||
context.WithValue(c.Request.Context(), types.TenantIDContextKey, t.ID),
|
|
||||||
types.TenantInfoContextKey, t,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTenantIDFromContext helper function to get tenant ID from context
|
|
||||||
func GetTenantIDFromContext(ctx context.Context) (uint, error) {
|
|
||||||
tenantID, ok := ctx.Value("tenantID").(uint)
|
|
||||||
if !ok {
|
|
||||||
return 0, errors.New("tenant ID not found in context")
|
|
||||||
}
|
|
||||||
return tenantID, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
"ai_scheduler/internal/data/error"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrorHandler 是一个处理应用错误的中间件
|
|
||||||
func ErrorHandler() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
// 处理请求
|
|
||||||
c.Next()
|
|
||||||
|
|
||||||
// 检查是否有错误
|
|
||||||
if len(c.Errors) > 0 {
|
|
||||||
// 获取最后一个错误
|
|
||||||
err := c.Errors.Last().Err
|
|
||||||
|
|
||||||
// 检查是否为应用错误
|
|
||||||
if appErr, ok := errors.IsAppError(err); ok {
|
|
||||||
// 返回应用错误
|
|
||||||
c.JSON(appErr.Code(), gin.H{
|
|
||||||
"status": "error",
|
|
||||||
"error": gin.H{
|
|
||||||
"code": appErr.Code(),
|
|
||||||
"message": appErr.Error(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理其他类型的错误
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"status": "error",
|
|
||||||
"error": gin.H{
|
|
||||||
"code": http.StatusInternalServerError,
|
|
||||||
"message": "Internal server error",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"knowlege-lsxd/internal/logger"
|
|
||||||
"knowlege-lsxd/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RequestID middleware adds a unique request ID to the context
|
|
||||||
func RequestID() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
// Get request ID from header or generate a new one
|
|
||||||
requestID := c.GetHeader("X-Request-ID")
|
|
||||||
if requestID == "" {
|
|
||||||
requestID = uuid.New().String()
|
|
||||||
}
|
|
||||||
// Set request ID in header
|
|
||||||
c.Header("X-Request-ID", requestID)
|
|
||||||
|
|
||||||
// Set request ID in context
|
|
||||||
c.Set(types.RequestIDContextKey.String(), requestID)
|
|
||||||
|
|
||||||
// Set logger in context
|
|
||||||
requestLogger := logger.GetLogger(c)
|
|
||||||
requestLogger = requestLogger.WithField("request_id", requestID)
|
|
||||||
c.Set(types.LoggerContextKey.String(), requestLogger)
|
|
||||||
|
|
||||||
// Set request ID in the global context for logging
|
|
||||||
c.Request = c.Request.WithContext(
|
|
||||||
context.WithValue(
|
|
||||||
context.WithValue(c.Request.Context(), types.RequestIDContextKey, requestID),
|
|
||||||
types.LoggerContextKey, requestLogger,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logger middleware logs request details with request ID
|
|
||||||
func Logger() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
start := time.Now()
|
|
||||||
path := c.Request.URL.Path
|
|
||||||
raw := c.Request.URL.RawQuery
|
|
||||||
|
|
||||||
// Process request
|
|
||||||
c.Next()
|
|
||||||
|
|
||||||
// Get request ID from context
|
|
||||||
requestID, exists := c.Get(types.RequestIDContextKey.String())
|
|
||||||
if !exists {
|
|
||||||
requestID = "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate latency
|
|
||||||
latency := time.Since(start)
|
|
||||||
|
|
||||||
// Get client IP and status code
|
|
||||||
clientIP := c.ClientIP()
|
|
||||||
statusCode := c.Writer.Status()
|
|
||||||
method := c.Request.Method
|
|
||||||
|
|
||||||
if raw != "" {
|
|
||||||
path = path + "?" + raw
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log with request ID
|
|
||||||
logger.GetLogger(c).Infof("[%s] %d | %3d | %13v | %15s | %s %s",
|
|
||||||
requestID,
|
|
||||||
statusCode,
|
|
||||||
c.Writer.Size(),
|
|
||||||
latency,
|
|
||||||
clientIP,
|
|
||||||
method,
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"runtime/debug"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Recovery is a middleware that recovers from panics
|
|
||||||
func Recovery() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
// Get request ID
|
|
||||||
requestID, _ := c.Get("RequestID")
|
|
||||||
|
|
||||||
// Print stacktrace
|
|
||||||
stacktrace := debug.Stack()
|
|
||||||
// Log error
|
|
||||||
log.Printf("[PANIC] %s | %v | %s", requestID, err, stacktrace)
|
|
||||||
|
|
||||||
// 返回500错误
|
|
||||||
c.AbortWithStatusJSON(500, gin.H{
|
|
||||||
"error": "Internal Server Error",
|
|
||||||
"message": fmt.Sprintf("%v", err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
|
||||||
"go.opentelemetry.io/otel/codes"
|
|
||||||
|
|
||||||
"knowlege-lsxd/internal/tracing"
|
|
||||||
"knowlege-lsxd/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Custom ResponseWriter to capture response content
|
|
||||||
type responseBodyWriter struct {
|
|
||||||
gin.ResponseWriter
|
|
||||||
body *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override Write method to write response content to buffer and original writer
|
|
||||||
func (r responseBodyWriter) Write(b []byte) (int, error) {
|
|
||||||
r.body.Write(b)
|
|
||||||
return r.ResponseWriter.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TracingMiddleware provides a Gin middleware that creates a trace span for each request
|
|
||||||
func TracingMiddleware() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
// Extract trace context from request headers
|
|
||||||
propagator := tracing.GetTracer()
|
|
||||||
if propagator == nil {
|
|
||||||
c.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get request ID as Span ID
|
|
||||||
requestID := c.GetString(string(types.RequestIDContextKey))
|
|
||||||
if requestID == "" {
|
|
||||||
requestID = c.GetHeader("X-Request-ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new span
|
|
||||||
spanName := fmt.Sprintf("%s %s", c.Request.Method, c.FullPath())
|
|
||||||
ctx, span := tracing.ContextWithSpan(c.Request.Context(), spanName)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
// Set basic span attributes
|
|
||||||
span.SetAttributes(
|
|
||||||
attribute.String("http.method", c.Request.Method),
|
|
||||||
attribute.String("http.url", c.Request.URL.String()),
|
|
||||||
attribute.String("http.path", c.FullPath()),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Record request headers (optional, or selectively record important headers)
|
|
||||||
for key, values := range c.Request.Header {
|
|
||||||
// Skip sensitive or unnecessary headers
|
|
||||||
if strings.ToLower(key) == "authorization" || strings.ToLower(key) == "cookie" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
span.SetAttributes(attribute.String("http.request.header."+key, strings.Join(values, ";")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record request body (for POST/PUT/PATCH requests)
|
|
||||||
if c.Request.Method == "POST" || c.Request.Method == "PUT" || c.Request.Method == "PATCH" {
|
|
||||||
if c.Request.Body != nil {
|
|
||||||
bodyBytes, _ := io.ReadAll(c.Request.Body)
|
|
||||||
span.SetAttributes(attribute.String("http.request.body", string(bodyBytes)))
|
|
||||||
// Reset request body because ReadAll consumes the Reader content
|
|
||||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record query parameters
|
|
||||||
if len(c.Request.URL.RawQuery) > 0 {
|
|
||||||
span.SetAttributes(attribute.String("http.request.query", c.Request.URL.RawQuery))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set request context with span context
|
|
||||||
c.Request = c.Request.WithContext(ctx)
|
|
||||||
|
|
||||||
// Store tracing context in Gin context
|
|
||||||
c.Set("trace.span", span)
|
|
||||||
c.Set("trace.ctx", ctx)
|
|
||||||
|
|
||||||
// Create response body capturer
|
|
||||||
responseBody := &bytes.Buffer{}
|
|
||||||
responseWriter := &responseBodyWriter{
|
|
||||||
ResponseWriter: c.Writer,
|
|
||||||
body: responseBody,
|
|
||||||
}
|
|
||||||
c.Writer = responseWriter
|
|
||||||
|
|
||||||
// Process request
|
|
||||||
c.Next()
|
|
||||||
|
|
||||||
// Set response status code
|
|
||||||
statusCode := c.Writer.Status()
|
|
||||||
span.SetAttributes(attribute.Int("http.status_code", statusCode))
|
|
||||||
|
|
||||||
// Record response body
|
|
||||||
responseContent := responseBody.String()
|
|
||||||
if len(responseContent) > 0 {
|
|
||||||
span.SetAttributes(attribute.String("http.response.body", responseContent))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record response headers (optional, or selectively record important headers)
|
|
||||||
for key, values := range c.Writer.Header() {
|
|
||||||
span.SetAttributes(attribute.String("http.response.header."+key, strings.Join(values, ";")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark as error if status code >= 400
|
|
||||||
if statusCode >= 400 {
|
|
||||||
span.SetStatus(codes.Error, fmt.Sprintf("HTTP %d", statusCode))
|
|
||||||
if err := c.Errors.Last(); err != nil {
|
|
||||||
span.RecordError(err.Err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
span.SetStatus(codes.Ok, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package pkg
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/pkg/utils_gorm"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Db struct {
|
||||||
|
Client *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGormDb(c *config.Config) (*Db, func()) {
|
||||||
|
transDBClient, mf := utils_gorm.DBConn(c.DB)
|
||||||
|
//directDBClient, df := directDB(c, hLog)
|
||||||
|
cleanup := func() {
|
||||||
|
mf()
|
||||||
|
//df()
|
||||||
|
}
|
||||||
|
return &Db{
|
||||||
|
Client: transDBClient,
|
||||||
|
//DirectDBClient: directDBClient,
|
||||||
|
}, cleanup
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,279 @@
|
||||||
|
package mapstructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns
|
||||||
|
// it into the proper DecodeHookFunc type, such as DecodeHookFuncType.
|
||||||
|
func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
|
||||||
|
// Create variables here so we can reference them with the reflect pkg
|
||||||
|
var f1 DecodeHookFuncType
|
||||||
|
var f2 DecodeHookFuncKind
|
||||||
|
var f3 DecodeHookFuncValue
|
||||||
|
|
||||||
|
// Fill in the variables into this interface and the rest is done
|
||||||
|
// automatically using the reflect package.
|
||||||
|
potential := []interface{}{f1, f2, f3}
|
||||||
|
|
||||||
|
v := reflect.ValueOf(h)
|
||||||
|
vt := v.Type()
|
||||||
|
for _, raw := range potential {
|
||||||
|
pt := reflect.ValueOf(raw).Type()
|
||||||
|
if vt.ConvertibleTo(pt) {
|
||||||
|
return v.Convert(pt).Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeHookExec executes the given decode hook. This should be used
|
||||||
|
// since it'll naturally degrade to the older backwards compatible DecodeHookFunc
|
||||||
|
// that took reflect.Kind instead of reflect.Type.
|
||||||
|
func DecodeHookExec(
|
||||||
|
raw DecodeHookFunc,
|
||||||
|
from reflect.Value, to reflect.Value) (interface{}, error) {
|
||||||
|
|
||||||
|
switch f := typedDecodeHook(raw).(type) {
|
||||||
|
case DecodeHookFuncType:
|
||||||
|
return f(from.Type(), to.Type(), from.Interface())
|
||||||
|
case DecodeHookFuncKind:
|
||||||
|
return f(from.Kind(), to.Kind(), from.Interface())
|
||||||
|
case DecodeHookFuncValue:
|
||||||
|
return f(from, to)
|
||||||
|
default:
|
||||||
|
return nil, errors.New("invalid decode hook signature")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComposeDecodeHookFunc creates a single DecodeHookFunc that
|
||||||
|
// automatically composes multiple DecodeHookFuncs.
|
||||||
|
//
|
||||||
|
// The composed funcs are called in order, with the result of the
|
||||||
|
// previous transformation.
|
||||||
|
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
|
||||||
|
return func(f reflect.Value, t reflect.Value) (interface{}, error) {
|
||||||
|
var err error
|
||||||
|
data := f.Interface()
|
||||||
|
|
||||||
|
newFrom := f
|
||||||
|
for _, f1 := range fs {
|
||||||
|
data, err = DecodeHookExec(f1, newFrom, t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newFrom = reflect.ValueOf(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned.
|
||||||
|
// If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages.
|
||||||
|
func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc {
|
||||||
|
return func(a, b reflect.Value) (interface{}, error) {
|
||||||
|
var allErrs string
|
||||||
|
var out interface{}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for _, f := range ff {
|
||||||
|
out, err = DecodeHookExec(f, a, b)
|
||||||
|
if err != nil {
|
||||||
|
allErrs += err.Error() + "\n"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New(allErrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringToSliceHookFunc returns a DecodeHookFunc that converts
|
||||||
|
// string to []string by splitting on the given sep.
|
||||||
|
func StringToSliceHookFunc(sep string) DecodeHookFunc {
|
||||||
|
return func(
|
||||||
|
f reflect.Kind,
|
||||||
|
t reflect.Kind,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
if f != reflect.String || t != reflect.Slice {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := data.(string)
|
||||||
|
if raw == "" {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Split(raw, sep), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
|
||||||
|
// strings to time.Duration.
|
||||||
|
func StringToTimeDurationHookFunc() DecodeHookFunc {
|
||||||
|
return func(
|
||||||
|
f reflect.Type,
|
||||||
|
t reflect.Type,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
if f.Kind() != reflect.String {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
if t != reflect.TypeOf(time.Duration(5)) {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert it by parsing
|
||||||
|
return time.ParseDuration(data.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringToIPHookFunc returns a DecodeHookFunc that converts
|
||||||
|
// strings to net.IP
|
||||||
|
func StringToIPHookFunc() DecodeHookFunc {
|
||||||
|
return func(
|
||||||
|
f reflect.Type,
|
||||||
|
t reflect.Type,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
if f.Kind() != reflect.String {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
if t != reflect.TypeOf(net.IP{}) {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert it by parsing
|
||||||
|
ip := net.ParseIP(data.(string))
|
||||||
|
if ip == nil {
|
||||||
|
return net.IP{}, fmt.Errorf("failed parsing ip %v", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringToIPNetHookFunc returns a DecodeHookFunc that converts
|
||||||
|
// strings to net.IPNet
|
||||||
|
func StringToIPNetHookFunc() DecodeHookFunc {
|
||||||
|
return func(
|
||||||
|
f reflect.Type,
|
||||||
|
t reflect.Type,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
if f.Kind() != reflect.String {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
if t != reflect.TypeOf(net.IPNet{}) {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert it by parsing
|
||||||
|
_, net, err := net.ParseCIDR(data.(string))
|
||||||
|
return net, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringToTimeHookFunc returns a DecodeHookFunc that converts
|
||||||
|
// strings to time.Time.
|
||||||
|
func StringToTimeHookFunc(layout string) DecodeHookFunc {
|
||||||
|
return func(
|
||||||
|
f reflect.Type,
|
||||||
|
t reflect.Type,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
if f.Kind() != reflect.String {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
if t != reflect.TypeOf(time.Time{}) {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert it by parsing
|
||||||
|
return time.Parse(layout, data.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to
|
||||||
|
// the decoder.
|
||||||
|
//
|
||||||
|
// Note that this is significantly different from the WeaklyTypedInput option
|
||||||
|
// of the DecoderConfig.
|
||||||
|
func WeaklyTypedHook(
|
||||||
|
f reflect.Kind,
|
||||||
|
t reflect.Kind,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
dataVal := reflect.ValueOf(data)
|
||||||
|
switch t {
|
||||||
|
case reflect.String:
|
||||||
|
switch f {
|
||||||
|
case reflect.Bool:
|
||||||
|
if dataVal.Bool() {
|
||||||
|
return "1", nil
|
||||||
|
}
|
||||||
|
return "0", nil
|
||||||
|
case reflect.Float32:
|
||||||
|
return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
|
||||||
|
case reflect.Int:
|
||||||
|
return strconv.FormatInt(dataVal.Int(), 10), nil
|
||||||
|
case reflect.Slice:
|
||||||
|
dataType := dataVal.Type()
|
||||||
|
elemKind := dataType.Elem().Kind()
|
||||||
|
if elemKind == reflect.Uint8 {
|
||||||
|
return string(dataVal.Interface().([]uint8)), nil
|
||||||
|
}
|
||||||
|
case reflect.Uint:
|
||||||
|
return strconv.FormatUint(dataVal.Uint(), 10), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RecursiveStructToMapHookFunc() DecodeHookFunc {
|
||||||
|
return func(f reflect.Value, t reflect.Value) (interface{}, error) {
|
||||||
|
if f.Kind() != reflect.Struct {
|
||||||
|
return f.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var i interface{} = struct{}{}
|
||||||
|
if t.Type() != reflect.TypeOf(&i).Elem() {
|
||||||
|
return f.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
t.Set(reflect.ValueOf(m))
|
||||||
|
|
||||||
|
return f.Interface(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextUnmarshallerHookFunc returns a DecodeHookFunc that applies
|
||||||
|
// strings to the UnmarshalText function, when the target type
|
||||||
|
// implements the encoding.TextUnmarshaler interface
|
||||||
|
func TextUnmarshallerHookFunc() DecodeHookFuncType {
|
||||||
|
return func(
|
||||||
|
f reflect.Type,
|
||||||
|
t reflect.Type,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
if f.Kind() != reflect.String {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
result := reflect.New(t).Interface()
|
||||||
|
unmarshaller, ok := result.(encoding.TextUnmarshaler)
|
||||||
|
if !ok {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
if err := unmarshaller.UnmarshalText([]byte(data.(string))); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,567 @@
|
||||||
|
package mapstructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestComposeDecodeHookFunc(t *testing.T) {
|
||||||
|
f1 := func(
|
||||||
|
f reflect.Kind,
|
||||||
|
t reflect.Kind,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
return data.(string) + "foo", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f2 := func(
|
||||||
|
f reflect.Kind,
|
||||||
|
t reflect.Kind,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
return data.(string) + "bar", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f := ComposeDecodeHookFunc(f1, f2)
|
||||||
|
|
||||||
|
result, err := DecodeHookExec(
|
||||||
|
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
if result.(string) != "foobar" {
|
||||||
|
t.Fatalf("bad: %#v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComposeDecodeHookFunc_err(t *testing.T) {
|
||||||
|
f1 := func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) {
|
||||||
|
return nil, errors.New("foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
f2 := func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) {
|
||||||
|
panic("NOPE")
|
||||||
|
}
|
||||||
|
|
||||||
|
f := ComposeDecodeHookFunc(f1, f2)
|
||||||
|
|
||||||
|
_, err := DecodeHookExec(
|
||||||
|
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
|
||||||
|
if err.Error() != "foo" {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComposeDecodeHookFunc_kinds(t *testing.T) {
|
||||||
|
var f2From reflect.Kind
|
||||||
|
|
||||||
|
f1 := func(
|
||||||
|
f reflect.Kind,
|
||||||
|
t reflect.Kind,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
return int(42), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f2 := func(
|
||||||
|
f reflect.Kind,
|
||||||
|
t reflect.Kind,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
f2From = f
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f := ComposeDecodeHookFunc(f1, f2)
|
||||||
|
|
||||||
|
_, err := DecodeHookExec(
|
||||||
|
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
if f2From != reflect.Int {
|
||||||
|
t.Fatalf("bad: %#v", f2From)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrComposeDecodeHookFunc(t *testing.T) {
|
||||||
|
f1 := func(
|
||||||
|
f reflect.Kind,
|
||||||
|
t reflect.Kind,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
return data.(string) + "foo", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f2 := func(
|
||||||
|
f reflect.Kind,
|
||||||
|
t reflect.Kind,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
return data.(string) + "bar", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f := OrComposeDecodeHookFunc(f1, f2)
|
||||||
|
|
||||||
|
result, err := DecodeHookExec(
|
||||||
|
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
if result.(string) != "foo" {
|
||||||
|
t.Fatalf("bad: %#v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrComposeDecodeHookFunc_correctValueIsLast(t *testing.T) {
|
||||||
|
f1 := func(
|
||||||
|
f reflect.Kind,
|
||||||
|
t reflect.Kind,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
return nil, errors.New("f1 error")
|
||||||
|
}
|
||||||
|
|
||||||
|
f2 := func(
|
||||||
|
f reflect.Kind,
|
||||||
|
t reflect.Kind,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
return nil, errors.New("f2 error")
|
||||||
|
}
|
||||||
|
|
||||||
|
f3 := func(
|
||||||
|
f reflect.Kind,
|
||||||
|
t reflect.Kind,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
return data.(string) + "bar", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f := OrComposeDecodeHookFunc(f1, f2, f3)
|
||||||
|
|
||||||
|
result, err := DecodeHookExec(
|
||||||
|
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
if result.(string) != "bar" {
|
||||||
|
t.Fatalf("bad: %#v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrComposeDecodeHookFunc_err(t *testing.T) {
|
||||||
|
f1 := func(
|
||||||
|
f reflect.Kind,
|
||||||
|
t reflect.Kind,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
return nil, errors.New("f1 error")
|
||||||
|
}
|
||||||
|
|
||||||
|
f2 := func(
|
||||||
|
f reflect.Kind,
|
||||||
|
t reflect.Kind,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
return nil, errors.New("f2 error")
|
||||||
|
}
|
||||||
|
|
||||||
|
f := OrComposeDecodeHookFunc(f1, f2)
|
||||||
|
|
||||||
|
_, err := DecodeHookExec(
|
||||||
|
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("bad: should return an error")
|
||||||
|
}
|
||||||
|
if err.Error() != "f1 error\nf2 error\n" {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComposeDecodeHookFunc_safe_nofuncs(t *testing.T) {
|
||||||
|
f := ComposeDecodeHookFunc()
|
||||||
|
type myStruct2 struct {
|
||||||
|
MyInt int
|
||||||
|
}
|
||||||
|
|
||||||
|
type myStruct1 struct {
|
||||||
|
Blah map[string]myStruct2
|
||||||
|
}
|
||||||
|
|
||||||
|
src := &myStruct1{Blah: map[string]myStruct2{
|
||||||
|
"test": {
|
||||||
|
MyInt: 1,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
dst := &myStruct1{}
|
||||||
|
dConf := &DecoderConfig{
|
||||||
|
Result: dst,
|
||||||
|
ErrorUnused: true,
|
||||||
|
DecodeHook: f,
|
||||||
|
}
|
||||||
|
d, err := NewDecoder(dConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = d.Decode(src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringToSliceHookFunc(t *testing.T) {
|
||||||
|
f := StringToSliceHookFunc(",")
|
||||||
|
|
||||||
|
strValue := reflect.ValueOf("42")
|
||||||
|
sliceValue := reflect.ValueOf([]byte("42"))
|
||||||
|
cases := []struct {
|
||||||
|
f, t reflect.Value
|
||||||
|
result interface{}
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{sliceValue, sliceValue, []byte("42"), false},
|
||||||
|
{strValue, strValue, "42", false},
|
||||||
|
{
|
||||||
|
reflect.ValueOf("foo,bar,baz"),
|
||||||
|
sliceValue,
|
||||||
|
[]string{"foo", "bar", "baz"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reflect.ValueOf(""),
|
||||||
|
sliceValue,
|
||||||
|
[]string{},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
actual, err := DecodeHookExec(f, tc.f, tc.t)
|
||||||
|
if tc.err != (err != nil) {
|
||||||
|
t.Fatalf("case %d: expected jderr %#v", i, tc.err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual, tc.result) {
|
||||||
|
t.Fatalf(
|
||||||
|
"case %d: expected %#v, got %#v",
|
||||||
|
i, tc.result, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringToTimeDurationHookFunc(t *testing.T) {
|
||||||
|
f := StringToTimeDurationHookFunc()
|
||||||
|
|
||||||
|
timeValue := reflect.ValueOf(time.Duration(5))
|
||||||
|
strValue := reflect.ValueOf("")
|
||||||
|
cases := []struct {
|
||||||
|
f, t reflect.Value
|
||||||
|
result interface{}
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{reflect.ValueOf("5s"), timeValue, 5 * time.Second, false},
|
||||||
|
{reflect.ValueOf("5"), timeValue, time.Duration(0), true},
|
||||||
|
{reflect.ValueOf("5"), strValue, "5", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
actual, err := DecodeHookExec(f, tc.f, tc.t)
|
||||||
|
if tc.err != (err != nil) {
|
||||||
|
t.Fatalf("case %d: expected jderr %#v", i, tc.err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual, tc.result) {
|
||||||
|
t.Fatalf(
|
||||||
|
"case %d: expected %#v, got %#v",
|
||||||
|
i, tc.result, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringToTimeHookFunc(t *testing.T) {
|
||||||
|
strValue := reflect.ValueOf("5")
|
||||||
|
timeValue := reflect.ValueOf(time.Time{})
|
||||||
|
cases := []struct {
|
||||||
|
f, t reflect.Value
|
||||||
|
layout string
|
||||||
|
result interface{}
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{reflect.ValueOf("2006-01-02T15:04:05Z"), timeValue, time.RFC3339,
|
||||||
|
time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC), false},
|
||||||
|
{strValue, timeValue, time.RFC3339, time.Time{}, true},
|
||||||
|
{strValue, strValue, time.RFC3339, "5", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
f := StringToTimeHookFunc(tc.layout)
|
||||||
|
actual, err := DecodeHookExec(f, tc.f, tc.t)
|
||||||
|
if tc.err != (err != nil) {
|
||||||
|
t.Fatalf("case %d: expected jderr %#v", i, tc.err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual, tc.result) {
|
||||||
|
t.Fatalf(
|
||||||
|
"case %d: expected %#v, got %#v",
|
||||||
|
i, tc.result, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringToIPHookFunc(t *testing.T) {
|
||||||
|
strValue := reflect.ValueOf("5")
|
||||||
|
ipValue := reflect.ValueOf(net.IP{})
|
||||||
|
cases := []struct {
|
||||||
|
f, t reflect.Value
|
||||||
|
result interface{}
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{reflect.ValueOf("1.2.3.4"), ipValue,
|
||||||
|
net.IPv4(0x01, 0x02, 0x03, 0x04), false},
|
||||||
|
{strValue, ipValue, net.IP{}, true},
|
||||||
|
{strValue, strValue, "5", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
f := StringToIPHookFunc()
|
||||||
|
actual, err := DecodeHookExec(f, tc.f, tc.t)
|
||||||
|
if tc.err != (err != nil) {
|
||||||
|
t.Fatalf("case %d: expected jderr %#v", i, tc.err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual, tc.result) {
|
||||||
|
t.Fatalf(
|
||||||
|
"case %d: expected %#v, got %#v",
|
||||||
|
i, tc.result, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringToIPNetHookFunc(t *testing.T) {
|
||||||
|
strValue := reflect.ValueOf("5")
|
||||||
|
ipNetValue := reflect.ValueOf(net.IPNet{})
|
||||||
|
var nilNet *net.IPNet = nil
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
f, t reflect.Value
|
||||||
|
result interface{}
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{reflect.ValueOf("1.2.3.4/24"), ipNetValue,
|
||||||
|
&net.IPNet{
|
||||||
|
IP: net.IP{0x01, 0x02, 0x03, 0x00},
|
||||||
|
Mask: net.IPv4Mask(0xff, 0xff, 0xff, 0x00),
|
||||||
|
}, false},
|
||||||
|
{strValue, ipNetValue, nilNet, true},
|
||||||
|
{strValue, strValue, "5", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
f := StringToIPNetHookFunc()
|
||||||
|
actual, err := DecodeHookExec(f, tc.f, tc.t)
|
||||||
|
if tc.err != (err != nil) {
|
||||||
|
t.Fatalf("case %d: expected jderr %#v", i, tc.err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual, tc.result) {
|
||||||
|
t.Fatalf(
|
||||||
|
"case %d: expected %#v, got %#v",
|
||||||
|
i, tc.result, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWeaklyTypedHook(t *testing.T) {
|
||||||
|
var f DecodeHookFunc = WeaklyTypedHook
|
||||||
|
|
||||||
|
strValue := reflect.ValueOf("")
|
||||||
|
cases := []struct {
|
||||||
|
f, t reflect.Value
|
||||||
|
result interface{}
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
// TO STRING
|
||||||
|
{
|
||||||
|
reflect.ValueOf(false),
|
||||||
|
strValue,
|
||||||
|
"0",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
reflect.ValueOf(true),
|
||||||
|
strValue,
|
||||||
|
"1",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
reflect.ValueOf(float32(7)),
|
||||||
|
strValue,
|
||||||
|
"7",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
reflect.ValueOf(int(7)),
|
||||||
|
strValue,
|
||||||
|
"7",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
reflect.ValueOf([]uint8("foo")),
|
||||||
|
strValue,
|
||||||
|
"foo",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
reflect.ValueOf(uint(7)),
|
||||||
|
strValue,
|
||||||
|
"7",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
actual, err := DecodeHookExec(f, tc.f, tc.t)
|
||||||
|
if tc.err != (err != nil) {
|
||||||
|
t.Fatalf("case %d: expected jderr %#v", i, tc.err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual, tc.result) {
|
||||||
|
t.Fatalf(
|
||||||
|
"case %d: expected %#v, got %#v",
|
||||||
|
i, tc.result, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructToMapHookFuncTabled(t *testing.T) {
|
||||||
|
var f DecodeHookFunc = RecursiveStructToMapHookFunc()
|
||||||
|
|
||||||
|
type b struct {
|
||||||
|
TestKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
type a struct {
|
||||||
|
Sub b
|
||||||
|
}
|
||||||
|
|
||||||
|
testStruct := a{
|
||||||
|
Sub: b{
|
||||||
|
TestKey: "testval",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testMap := map[string]interface{}{
|
||||||
|
"Sub": map[string]interface{}{
|
||||||
|
"TestKey": "testval",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
receiver interface{}
|
||||||
|
input interface{}
|
||||||
|
expected interface{}
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"map receiver",
|
||||||
|
func() interface{} {
|
||||||
|
var res map[string]interface{}
|
||||||
|
return &res
|
||||||
|
}(),
|
||||||
|
testStruct,
|
||||||
|
&testMap,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"interface receiver",
|
||||||
|
func() interface{} {
|
||||||
|
var res interface{}
|
||||||
|
return &res
|
||||||
|
}(),
|
||||||
|
testStruct,
|
||||||
|
func() interface{} {
|
||||||
|
var exp interface{} = testMap
|
||||||
|
return &exp
|
||||||
|
}(),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slice receiver errors",
|
||||||
|
func() interface{} {
|
||||||
|
var res []string
|
||||||
|
return &res
|
||||||
|
}(),
|
||||||
|
testStruct,
|
||||||
|
new([]string),
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slice to slice - no change",
|
||||||
|
func() interface{} {
|
||||||
|
var res []string
|
||||||
|
return &res
|
||||||
|
}(),
|
||||||
|
[]string{"a", "b"},
|
||||||
|
&[]string{"a", "b"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"string to string - no change",
|
||||||
|
func() interface{} {
|
||||||
|
var res string
|
||||||
|
return &res
|
||||||
|
}(),
|
||||||
|
"test",
|
||||||
|
func() *string {
|
||||||
|
s := "test"
|
||||||
|
return &s
|
||||||
|
}(),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
cfg := &DecoderConfig{
|
||||||
|
DecodeHook: f,
|
||||||
|
Result: tc.receiver,
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := NewDecoder(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected jderr %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Decode(tc.input)
|
||||||
|
if tc.err != (err != nil) {
|
||||||
|
t.Fatalf("expected jderr %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tc.expected, tc.receiver) {
|
||||||
|
t.Fatalf("expected %#v, got %#v",
|
||||||
|
tc.expected, tc.receiver)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTextUnmarshallerHookFunc(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
f, t reflect.Value
|
||||||
|
result interface{}
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{reflect.ValueOf("42"), reflect.ValueOf(big.Int{}), big.NewInt(42), false},
|
||||||
|
{reflect.ValueOf("invalid"), reflect.ValueOf(big.Int{}), nil, true},
|
||||||
|
{reflect.ValueOf("5"), reflect.ValueOf("5"), "5", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
f := TextUnmarshallerHookFunc()
|
||||||
|
actual, err := DecodeHookExec(f, tc.f, tc.t)
|
||||||
|
if tc.err != (err != nil) {
|
||||||
|
t.Fatalf("case %d: expected jderr %#v", i, tc.err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual, tc.result) {
|
||||||
|
t.Fatalf(
|
||||||
|
"case %d: expected %#v, got %#v",
|
||||||
|
i, tc.result, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package mapstructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error implements the error interface and can represents multiple
|
||||||
|
// errors that occur in the course of a single decode.
|
||||||
|
type Error struct {
|
||||||
|
Errors []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
points := make([]string, len(e.Errors))
|
||||||
|
for i, err := range e.Errors {
|
||||||
|
points[i] = fmt.Sprintf("* %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(points)
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%d error(s) decoding:\n\n%s",
|
||||||
|
len(e.Errors), strings.Join(points, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrappedErrors implements the errwrap.Wrapper interface to make this
|
||||||
|
// return value more useful with the errwrap and go-multierror libraries.
|
||||||
|
func (e *Error) WrappedErrors() []error {
|
||||||
|
if e == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]error, len(e.Errors))
|
||||||
|
for i, e := range e.Errors {
|
||||||
|
result[i] = errors.New(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendErrors(errors []string, err error) []string {
|
||||||
|
switch e := err.(type) {
|
||||||
|
case *Error:
|
||||||
|
return append(errors, e.Errors...)
|
||||||
|
default:
|
||||||
|
return append(errors, e.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,285 @@
|
||||||
|
package mapstructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Person struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
Emails []string
|
||||||
|
Extra map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Decode(b *testing.B) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"name": "Mitchell",
|
||||||
|
"age": 91,
|
||||||
|
"emails": []string{"one", "two", "three"},
|
||||||
|
"extra": map[string]string{
|
||||||
|
"twitter": "mitchellh",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Person
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Decode(input, &result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeViaJSON takes the map data and passes it through encoding/json to convert it into the
|
||||||
|
// given Go native structure pointed to by v. v must be a pointer to a struct.
|
||||||
|
func decodeViaJSON(data interface{}, v interface{}) error {
|
||||||
|
// Perform the task by simply marshalling the input into JSON,
|
||||||
|
// then unmarshalling it into target native Go struct.
|
||||||
|
b, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(b, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_DecodeViaJSON(b *testing.B) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"name": "Mitchell",
|
||||||
|
"age": 91,
|
||||||
|
"emails": []string{"one", "two", "three"},
|
||||||
|
"extra": map[string]string{
|
||||||
|
"twitter": "mitchellh",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Person
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
decodeViaJSON(input, &result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_JSONUnmarshal(b *testing.B) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"name": "Mitchell",
|
||||||
|
"age": 91,
|
||||||
|
"emails": []string{"one", "two", "three"},
|
||||||
|
"extra": map[string]string{
|
||||||
|
"twitter": "mitchellh",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
inputB, err := json.Marshal(input)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal("Failed to marshal test input:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Person
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
json.Unmarshal(inputB, &result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_DecodeBasic(b *testing.B) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"vstring": "foo",
|
||||||
|
"vint": 42,
|
||||||
|
"Vuint": 42,
|
||||||
|
"vbool": true,
|
||||||
|
"Vfloat": 42.42,
|
||||||
|
"vsilent": true,
|
||||||
|
"vdata": 42,
|
||||||
|
"vjsonInt": json.Number("1234"),
|
||||||
|
"vjsonFloat": json.Number("1234.5"),
|
||||||
|
"vjsonNumber": json.Number("1234.5"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var result Basic
|
||||||
|
Decode(input, &result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_DecodeEmbedded(b *testing.B) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"vstring": "foo",
|
||||||
|
"Basic": map[string]interface{}{
|
||||||
|
"vstring": "innerfoo",
|
||||||
|
},
|
||||||
|
"vunique": "bar",
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Embedded
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Decode(input, &result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_DecodeTypeConversion(b *testing.B) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"IntToFloat": 42,
|
||||||
|
"IntToUint": 42,
|
||||||
|
"IntToBool": 1,
|
||||||
|
"IntToString": 42,
|
||||||
|
"UintToInt": 42,
|
||||||
|
"UintToFloat": 42,
|
||||||
|
"UintToBool": 42,
|
||||||
|
"UintToString": 42,
|
||||||
|
"BoolToInt": true,
|
||||||
|
"BoolToUint": true,
|
||||||
|
"BoolToFloat": true,
|
||||||
|
"BoolToString": true,
|
||||||
|
"FloatToInt": 42.42,
|
||||||
|
"FloatToUint": 42.42,
|
||||||
|
"FloatToBool": 42.42,
|
||||||
|
"FloatToString": 42.42,
|
||||||
|
"StringToInt": "42",
|
||||||
|
"StringToUint": "42",
|
||||||
|
"StringToBool": "1",
|
||||||
|
"StringToFloat": "42.42",
|
||||||
|
"SliceToMap": []interface{}{},
|
||||||
|
"MapToSlice": map[string]interface{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultStrict TypeConversionResult
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Decode(input, &resultStrict)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_DecodeMap(b *testing.B) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"vfoo": "foo",
|
||||||
|
"vother": map[interface{}]interface{}{
|
||||||
|
"foo": "foo",
|
||||||
|
"bar": "bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Map
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Decode(input, &result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_DecodeMapOfStruct(b *testing.B) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"value": map[string]interface{}{
|
||||||
|
"foo": map[string]string{"vstring": "one"},
|
||||||
|
"bar": map[string]string{"vstring": "two"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var result MapOfStruct
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Decode(input, &result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_DecodeSlice(b *testing.B) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"vfoo": "foo",
|
||||||
|
"vbar": []string{"foo", "bar", "baz"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Slice
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Decode(input, &result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_DecodeSliceOfStruct(b *testing.B) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"value": []map[string]interface{}{
|
||||||
|
{"vstring": "one"},
|
||||||
|
{"vstring": "two"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var result SliceOfStruct
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Decode(input, &result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_DecodeWeaklyTypedInput(b *testing.B) {
|
||||||
|
// This input can come from anywhere, but typically comes from
|
||||||
|
// something like decoding JSON, generated by a weakly typed language
|
||||||
|
// such as PHP.
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"name": 123, // number => string
|
||||||
|
"age": "42", // string => number
|
||||||
|
"emails": map[string]interface{}{}, // empty map => empty array
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Person
|
||||||
|
config := &DecoderConfig{
|
||||||
|
WeaklyTypedInput: true,
|
||||||
|
Result: &result,
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder, err := NewDecoder(config)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
decoder.Decode(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_DecodeMetadata(b *testing.B) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"name": "Mitchell",
|
||||||
|
"age": 91,
|
||||||
|
"email": "foo@bar.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
var md Metadata
|
||||||
|
var result Person
|
||||||
|
config := &DecoderConfig{
|
||||||
|
Metadata: &md,
|
||||||
|
Result: &result,
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder, err := NewDecoder(config)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
decoder.Decode(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_DecodeMetadataEmbedded(b *testing.B) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"vstring": "foo",
|
||||||
|
"vunique": "bar",
|
||||||
|
}
|
||||||
|
|
||||||
|
var md Metadata
|
||||||
|
var result EmbeddedSquash
|
||||||
|
config := &DecoderConfig{
|
||||||
|
Metadata: &md,
|
||||||
|
Result: &result,
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder, err := NewDecoder(config)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("jderr: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
decoder.Decode(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_DecodeTagged(b *testing.B) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
"bar": "value",
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Tagged
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Decode(input, &result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,627 @@
|
||||||
|
package mapstructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GH-1, GH-10, GH-96
|
||||||
|
func TestDecode_NilValue(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
in interface{}
|
||||||
|
target interface{}
|
||||||
|
out interface{}
|
||||||
|
metaKeys []string
|
||||||
|
metaUnused []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"all nil",
|
||||||
|
&map[string]interface{}{
|
||||||
|
"vfoo": nil,
|
||||||
|
"vother": nil,
|
||||||
|
},
|
||||||
|
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
|
||||||
|
&Map{Vfoo: "", Vother: nil},
|
||||||
|
[]string{"Vfoo", "Vother"},
|
||||||
|
[]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"partial nil",
|
||||||
|
&map[string]interface{}{
|
||||||
|
"vfoo": "baz",
|
||||||
|
"vother": nil,
|
||||||
|
},
|
||||||
|
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
|
||||||
|
&Map{Vfoo: "baz", Vother: nil},
|
||||||
|
[]string{"Vfoo", "Vother"},
|
||||||
|
[]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"partial decode",
|
||||||
|
&map[string]interface{}{
|
||||||
|
"vother": nil,
|
||||||
|
},
|
||||||
|
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
|
||||||
|
&Map{Vfoo: "foo", Vother: nil},
|
||||||
|
[]string{"Vother"},
|
||||||
|
[]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unused values",
|
||||||
|
&map[string]interface{}{
|
||||||
|
"vbar": "bar",
|
||||||
|
"vfoo": nil,
|
||||||
|
"vother": nil,
|
||||||
|
},
|
||||||
|
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
|
||||||
|
&Map{Vfoo: "", Vother: nil},
|
||||||
|
[]string{"Vfoo", "Vother"},
|
||||||
|
[]string{"vbar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"map interface all nil",
|
||||||
|
&map[interface{}]interface{}{
|
||||||
|
"vfoo": nil,
|
||||||
|
"vother": nil,
|
||||||
|
},
|
||||||
|
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
|
||||||
|
&Map{Vfoo: "", Vother: nil},
|
||||||
|
[]string{"Vfoo", "Vother"},
|
||||||
|
[]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"map interface partial nil",
|
||||||
|
&map[interface{}]interface{}{
|
||||||
|
"vfoo": "baz",
|
||||||
|
"vother": nil,
|
||||||
|
},
|
||||||
|
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
|
||||||
|
&Map{Vfoo: "baz", Vother: nil},
|
||||||
|
[]string{"Vfoo", "Vother"},
|
||||||
|
[]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"map interface partial decode",
|
||||||
|
&map[interface{}]interface{}{
|
||||||
|
"vother": nil,
|
||||||
|
},
|
||||||
|
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
|
||||||
|
&Map{Vfoo: "foo", Vother: nil},
|
||||||
|
[]string{"Vother"},
|
||||||
|
[]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"map interface unused values",
|
||||||
|
&map[interface{}]interface{}{
|
||||||
|
"vbar": "bar",
|
||||||
|
"vfoo": nil,
|
||||||
|
"vother": nil,
|
||||||
|
},
|
||||||
|
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
|
||||||
|
&Map{Vfoo: "", Vother: nil},
|
||||||
|
[]string{"Vfoo", "Vother"},
|
||||||
|
[]string{"vbar"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
config := &DecoderConfig{
|
||||||
|
Metadata: new(Metadata),
|
||||||
|
Result: tc.target,
|
||||||
|
ZeroFields: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder, err := NewDecoder(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = decoder.Decode(tc.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tc.out, tc.target) {
|
||||||
|
t.Fatalf("%q: TestDecode_NilValue() expected: %#v, got: %#v", tc.name, tc.out, tc.target)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tc.metaKeys, config.Metadata.Keys) {
|
||||||
|
t.Fatalf("%q: Metadata.Keys mismatch expected: %#v, got: %#v", tc.name, tc.metaKeys, config.Metadata.Keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tc.metaUnused, config.Metadata.Unused) {
|
||||||
|
t.Fatalf("%q: Metadata.Unused mismatch expected: %#v, got: %#v", tc.name, tc.metaUnused, config.Metadata.Unused)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #48
|
||||||
|
func TestNestedTypePointerWithDefaults(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"vfoo": "foo",
|
||||||
|
"vbar": map[string]interface{}{
|
||||||
|
"vstring": "foo",
|
||||||
|
"vint": 42,
|
||||||
|
"vbool": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := NestedPointer{
|
||||||
|
Vbar: &Basic{
|
||||||
|
Vuint: 42,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := Decode(input, &result)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("got an jderr: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vfoo != "foo" {
|
||||||
|
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vbar.Vstring != "foo" {
|
||||||
|
t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vbar.Vint != 42 {
|
||||||
|
t.Errorf("vint value should be 42: %#v", result.Vbar.Vint)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vbar.Vbool != true {
|
||||||
|
t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vbar.Vextra != "" {
|
||||||
|
t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra)
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is the error
|
||||||
|
if result.Vbar.Vuint != 42 {
|
||||||
|
t.Errorf("vuint value should be 42: %#v", result.Vbar.Vuint)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type NestedSlice struct {
|
||||||
|
Vfoo string
|
||||||
|
Vbars []Basic
|
||||||
|
Vempty []Basic
|
||||||
|
}
|
||||||
|
|
||||||
|
// #48
|
||||||
|
func TestNestedTypeSliceWithDefaults(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"vfoo": "foo",
|
||||||
|
"vbars": []map[string]interface{}{
|
||||||
|
{"vstring": "foo", "vint": 42, "vbool": true},
|
||||||
|
{"vint": 42, "vbool": true},
|
||||||
|
},
|
||||||
|
"vempty": []map[string]interface{}{
|
||||||
|
{"vstring": "foo", "vint": 42, "vbool": true},
|
||||||
|
{"vint": 42, "vbool": true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := NestedSlice{
|
||||||
|
Vbars: []Basic{
|
||||||
|
{Vuint: 42},
|
||||||
|
{Vstring: "foo"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := Decode(input, &result)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("got an jderr: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vfoo != "foo" {
|
||||||
|
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vbars[0].Vstring != "foo" {
|
||||||
|
t.Errorf("vstring value should be 'foo': %#v", result.Vbars[0].Vstring)
|
||||||
|
}
|
||||||
|
// this is the error
|
||||||
|
if result.Vbars[0].Vuint != 42 {
|
||||||
|
t.Errorf("vuint value should be 42: %#v", result.Vbars[0].Vuint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #48 workaround
|
||||||
|
func TestNestedTypeWithDefaults(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"vfoo": "foo",
|
||||||
|
"vbar": map[string]interface{}{
|
||||||
|
"vstring": "foo",
|
||||||
|
"vint": 42,
|
||||||
|
"vbool": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := Nested{
|
||||||
|
Vbar: Basic{
|
||||||
|
Vuint: 42,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := Decode(input, &result)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("got an jderr: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vfoo != "foo" {
|
||||||
|
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vbar.Vstring != "foo" {
|
||||||
|
t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vbar.Vint != 42 {
|
||||||
|
t.Errorf("vint value should be 42: %#v", result.Vbar.Vint)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vbar.Vbool != true {
|
||||||
|
t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Vbar.Vextra != "" {
|
||||||
|
t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra)
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is the error
|
||||||
|
if result.Vbar.Vuint != 42 {
|
||||||
|
t.Errorf("vuint value should be 42: %#v", result.Vbar.Vuint)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// #67 panic() on extending slices (decodeSlice with disabled ZeroValues)
|
||||||
|
func TestDecodeSliceToEmptySliceWOZeroing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
type TestStruct struct {
|
||||||
|
Vfoo []string
|
||||||
|
}
|
||||||
|
|
||||||
|
decode := func(m interface{}, rawVal interface{}) error {
|
||||||
|
config := &DecoderConfig{
|
||||||
|
Metadata: nil,
|
||||||
|
Result: rawVal,
|
||||||
|
ZeroFields: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder, err := NewDecoder(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoder.Decode(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"vfoo": []string{"1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &TestStruct{}
|
||||||
|
|
||||||
|
err := decode(input, &result)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("got an jderr: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"vfoo": []string{"1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &TestStruct{
|
||||||
|
Vfoo: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := decode(input, &result)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("got an jderr: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"vfoo": []string{"2", "3"},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &TestStruct{
|
||||||
|
Vfoo: []string{"1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := decode(input, &result)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("got an jderr: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #70
|
||||||
|
func TestNextSquashMapstructure(t *testing.T) {
|
||||||
|
data := &struct {
|
||||||
|
Level1 struct {
|
||||||
|
Level2 struct {
|
||||||
|
Foo string
|
||||||
|
} `mapstructure:",squash"`
|
||||||
|
} `mapstructure:",squash"`
|
||||||
|
}{}
|
||||||
|
err := Decode(map[interface{}]interface{}{"foo": "baz"}, &data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not error: %s", err)
|
||||||
|
}
|
||||||
|
if data.Level1.Level2.Foo != "baz" {
|
||||||
|
t.Fatal("value should be baz")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImplementsInterfacePointerReceiver struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ImplementsInterfacePointerReceiver) DoStuff() {}
|
||||||
|
|
||||||
|
type ImplementsInterfaceValueReceiver string
|
||||||
|
|
||||||
|
func (i ImplementsInterfaceValueReceiver) DoStuff() {}
|
||||||
|
|
||||||
|
// GH-140 Type error when using DecodeHook to decode into interface
|
||||||
|
func TestDecode_DecodeHookInterface(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
type Interface interface {
|
||||||
|
DoStuff()
|
||||||
|
}
|
||||||
|
type DecodeIntoInterface struct {
|
||||||
|
Test Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
testData := map[string]string{"test": "test"}
|
||||||
|
|
||||||
|
stringToPointerInterfaceDecodeHook := func(from, to reflect.Type, data interface{}) (interface{}, error) {
|
||||||
|
if from.Kind() != reflect.String {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if to != reflect.TypeOf((*Interface)(nil)).Elem() {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
// Ensure interface is satisfied
|
||||||
|
var impl Interface = &ImplementsInterfacePointerReceiver{data.(string)}
|
||||||
|
return impl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
stringToValueInterfaceDecodeHook := func(from, to reflect.Type, data interface{}) (interface{}, error) {
|
||||||
|
if from.Kind() != reflect.String {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if to != reflect.TypeOf((*Interface)(nil)).Elem() {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
// Ensure interface is satisfied
|
||||||
|
var impl Interface = ImplementsInterfaceValueReceiver(data.(string))
|
||||||
|
return impl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
decodeInto := new(DecodeIntoInterface)
|
||||||
|
|
||||||
|
decoder, _ := NewDecoder(&DecoderConfig{
|
||||||
|
DecodeHook: stringToPointerInterfaceDecodeHook,
|
||||||
|
Result: decodeInto,
|
||||||
|
})
|
||||||
|
|
||||||
|
err := decoder.Decode(testData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Decode returned error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &ImplementsInterfacePointerReceiver{"test"}
|
||||||
|
if !reflect.DeepEqual(decodeInto.Test, expected) {
|
||||||
|
t.Fatalf("expected: %#v (%T), got: %#v (%T)", decodeInto.Test, decodeInto.Test, expected, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
decodeInto := new(DecodeIntoInterface)
|
||||||
|
|
||||||
|
decoder, _ := NewDecoder(&DecoderConfig{
|
||||||
|
DecodeHook: stringToValueInterfaceDecodeHook,
|
||||||
|
Result: decodeInto,
|
||||||
|
})
|
||||||
|
|
||||||
|
err := decoder.Decode(testData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Decode returned error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := ImplementsInterfaceValueReceiver("test")
|
||||||
|
if !reflect.DeepEqual(decodeInto.Test, expected) {
|
||||||
|
t.Fatalf("expected: %#v (%T), got: %#v (%T)", decodeInto.Test, decodeInto.Test, expected, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #103 Check for data type before trying to access its composants prevent a panic error
|
||||||
|
// in decodeSlice
|
||||||
|
func TestDecodeBadDataTypeInSlice(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"Toto": "titi",
|
||||||
|
}
|
||||||
|
result := []struct {
|
||||||
|
Toto string
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := Decode(input, &result); err == nil {
|
||||||
|
t.Error("An error was expected, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #202 Ensure that intermediate maps in the struct -> struct decode process are settable
|
||||||
|
// and not just the elements within them.
|
||||||
|
func TestDecodeIntermediateMapsSettable(t *testing.T) {
|
||||||
|
type Timestamp struct {
|
||||||
|
Seconds int64
|
||||||
|
Nanos int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type TsWrapper struct {
|
||||||
|
Timestamp *Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimeWrapper struct {
|
||||||
|
Timestamp time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
input := TimeWrapper{
|
||||||
|
Timestamp: time.Unix(123456789, 987654),
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := TsWrapper{
|
||||||
|
Timestamp: &Timestamp{
|
||||||
|
Seconds: 123456789,
|
||||||
|
Nanos: 987654,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
timePtrType := reflect.TypeOf((*time.Time)(nil))
|
||||||
|
mapStrInfType := reflect.TypeOf((map[string]interface{})(nil))
|
||||||
|
|
||||||
|
var actual TsWrapper
|
||||||
|
decoder, err := NewDecoder(&DecoderConfig{
|
||||||
|
Result: &actual,
|
||||||
|
DecodeHook: func(from, to reflect.Type, data interface{}) (interface{}, error) {
|
||||||
|
if from == timePtrType && to == mapStrInfType {
|
||||||
|
ts := data.(*time.Time)
|
||||||
|
nanos := ts.UnixNano()
|
||||||
|
|
||||||
|
seconds := nanos / 1000000000
|
||||||
|
nanos = nanos % 1000000000
|
||||||
|
|
||||||
|
return &map[string]interface{}{
|
||||||
|
"Seconds": seconds,
|
||||||
|
"Nanos": int32(nanos),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create decoder: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := decoder.Decode(&input); err != nil {
|
||||||
|
t.Fatalf("failed to decode input: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Fatalf("expected: %#[1]v (%[1]T), got: %#[2]v (%[2]T)", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GH-206: decodeInt throws an error for an empty string
|
||||||
|
func TestDecode_weakEmptyStringToInt(t *testing.T) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"StringToInt": "",
|
||||||
|
"StringToUint": "",
|
||||||
|
"StringToBool": "",
|
||||||
|
"StringToFloat": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedResultWeak := TypeConversionResult{
|
||||||
|
StringToInt: 0,
|
||||||
|
StringToUint: 0,
|
||||||
|
StringToBool: false,
|
||||||
|
StringToFloat: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test weak type conversion
|
||||||
|
var resultWeak TypeConversionResult
|
||||||
|
err := WeakDecode(input, &resultWeak)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("got an jderr: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(resultWeak, expectedResultWeak) {
|
||||||
|
t.Errorf("expected \n%#v, got: \n%#v", expectedResultWeak, resultWeak)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GH-228: Squash cause *time.Time set to zero
|
||||||
|
func TestMapSquash(t *testing.T) {
|
||||||
|
type AA struct {
|
||||||
|
T *time.Time
|
||||||
|
}
|
||||||
|
type A struct {
|
||||||
|
AA
|
||||||
|
}
|
||||||
|
|
||||||
|
v := time.Now()
|
||||||
|
in := &AA{
|
||||||
|
T: &v,
|
||||||
|
}
|
||||||
|
out := &A{}
|
||||||
|
d, err := NewDecoder(&DecoderConfig{
|
||||||
|
Squash: true,
|
||||||
|
Result: out,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("jderr: %s", err)
|
||||||
|
}
|
||||||
|
if err := d.Decode(in); err != nil {
|
||||||
|
t.Fatalf("jderr: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// these failed
|
||||||
|
if !v.Equal(*out.T) {
|
||||||
|
t.Fatal("expected equal")
|
||||||
|
}
|
||||||
|
if out.T.IsZero() {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GH-238: Empty key name when decoding map from struct with only omitempty flag
|
||||||
|
func TestMapOmitEmptyWithEmptyFieldnameInTag(t *testing.T) {
|
||||||
|
type Struct struct {
|
||||||
|
Username string `mapstructure:",omitempty"`
|
||||||
|
Age int `mapstructure:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
s := Struct{
|
||||||
|
Username: "Joe",
|
||||||
|
}
|
||||||
|
var m map[string]interface{}
|
||||||
|
|
||||||
|
if err := Decode(s, &m); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m) != 1 {
|
||||||
|
t.Fatalf("fail: %#v", m)
|
||||||
|
}
|
||||||
|
if m["Username"] != "Joe" {
|
||||||
|
t.Fatalf("fail: %#v", m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,256 @@
|
||||||
|
package mapstructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleDecode() {
|
||||||
|
type Person struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
Emails []string
|
||||||
|
Extra map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// This input can come from anywhere, but typically comes from
|
||||||
|
// something like decoding JSON where we're not quite sure of the
|
||||||
|
// struct initially.
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"name": "Mitchell",
|
||||||
|
"age": 91,
|
||||||
|
"emails": []string{"one", "two", "three"},
|
||||||
|
"extra": map[string]string{
|
||||||
|
"twitter": "mitchellh",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Person
|
||||||
|
err := Decode(input, &result)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%#v", result)
|
||||||
|
// Output:
|
||||||
|
// mapstructure.Person{Name:"Mitchell", Age:91, Emails:[]string{"one", "two", "three"}, Extra:map[string]string{"twitter":"mitchellh"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDecode_errors() {
|
||||||
|
type Person struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
Emails []string
|
||||||
|
Extra map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// This input can come from anywhere, but typically comes from
|
||||||
|
// something like decoding JSON where we're not quite sure of the
|
||||||
|
// struct initially.
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"name": 123,
|
||||||
|
"age": "bad value",
|
||||||
|
"emails": []int{1, 2, 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Person
|
||||||
|
err := Decode(input, &result)
|
||||||
|
if err == nil {
|
||||||
|
panic("should have an error")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
// Output:
|
||||||
|
// 5 error(s) decoding:
|
||||||
|
//
|
||||||
|
// * 'Age' expected type 'int', got unconvertible type 'string', value: 'bad value'
|
||||||
|
// * 'Emails[0]' expected type 'string', got unconvertible type 'int', value: '1'
|
||||||
|
// * 'Emails[1]' expected type 'string', got unconvertible type 'int', value: '2'
|
||||||
|
// * 'Emails[2]' expected type 'string', got unconvertible type 'int', value: '3'
|
||||||
|
// * 'Name' expected type 'string', got unconvertible type 'int', value: '123'
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDecode_metadata() {
|
||||||
|
type Person struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
// This input can come from anywhere, but typically comes from
|
||||||
|
// something like decoding JSON where we're not quite sure of the
|
||||||
|
// struct initially.
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"name": "Mitchell",
|
||||||
|
"age": 91,
|
||||||
|
"email": "foo@bar.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
// For metadata, we make a more advanced DecoderConfig so we can
|
||||||
|
// more finely configure the decoder that is used. In this case, we
|
||||||
|
// just tell the decoder we want to track metadata.
|
||||||
|
var md Metadata
|
||||||
|
var result Person
|
||||||
|
config := &DecoderConfig{
|
||||||
|
Metadata: &md,
|
||||||
|
Result: &result,
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder, err := NewDecoder(config)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := decoder.Decode(input); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Unused keys: %#v", md.Unused)
|
||||||
|
// Output:
|
||||||
|
// Unused keys: []string{"email"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDecode_weaklyTypedInput() {
|
||||||
|
type Person struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
Emails []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// This input can come from anywhere, but typically comes from
|
||||||
|
// something like decoding JSON, generated by a weakly typed language
|
||||||
|
// such as PHP.
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"name": 123, // number => string
|
||||||
|
"age": "42", // string => number
|
||||||
|
"emails": map[string]interface{}{}, // empty map => empty array
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Person
|
||||||
|
config := &DecoderConfig{
|
||||||
|
WeaklyTypedInput: true,
|
||||||
|
Result: &result,
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder, err := NewDecoder(config)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = decoder.Decode(input)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%#v", result)
|
||||||
|
// Output: mapstructure.Person{Name:"123", Age:42, Emails:[]string{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDecode_tags() {
|
||||||
|
// Note that the mapstructure tags defined in the struct type
|
||||||
|
// can indicate which fields the values are mapped to.
|
||||||
|
type Person struct {
|
||||||
|
Name string `mapstructure:"person_name"`
|
||||||
|
Age int `mapstructure:"person_age"`
|
||||||
|
}
|
||||||
|
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"person_name": "Mitchell",
|
||||||
|
"person_age": 91,
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Person
|
||||||
|
err := Decode(input, &result)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%#v", result)
|
||||||
|
// Output:
|
||||||
|
// mapstructure.Person{Name:"Mitchell", Age:91}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDecode_embeddedStruct() {
|
||||||
|
// Squashing multiple embedded structs is allowed using the squash tag.
|
||||||
|
// This is demonstrated by creating a composite struct of multiple types
|
||||||
|
// and decoding into it. In this case, a person can carry with it both
|
||||||
|
// a Family and a Location, as well as their own FirstName.
|
||||||
|
type Family struct {
|
||||||
|
LastName string
|
||||||
|
}
|
||||||
|
type Location struct {
|
||||||
|
City string
|
||||||
|
}
|
||||||
|
type Person struct {
|
||||||
|
Family `mapstructure:",squash"`
|
||||||
|
Location `mapstructure:",squash"`
|
||||||
|
FirstName string
|
||||||
|
}
|
||||||
|
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"FirstName": "Mitchell",
|
||||||
|
"LastName": "Hashimoto",
|
||||||
|
"City": "San Francisco",
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Person
|
||||||
|
err := Decode(input, &result)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s %s, %s", result.FirstName, result.LastName, result.City)
|
||||||
|
// Output:
|
||||||
|
// Mitchell Hashimoto, San Francisco
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDecode_remainingData() {
|
||||||
|
// Note that the mapstructure tags defined in the struct type
|
||||||
|
// can indicate which fields the values are mapped to.
|
||||||
|
type Person struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
Other map[string]interface{} `mapstructure:",remain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"name": "Mitchell",
|
||||||
|
"age": 91,
|
||||||
|
"email": "mitchell@example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Person
|
||||||
|
err := Decode(input, &result)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%#v", result)
|
||||||
|
// Output:
|
||||||
|
// mapstructure.Person{Name:"Mitchell", Age:91, Other:map[string]interface {}{"email":"mitchell@example.com"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDecode_omitempty() {
|
||||||
|
// Add omitempty annotation to avoid map keys for empty values
|
||||||
|
type Family struct {
|
||||||
|
LastName string
|
||||||
|
}
|
||||||
|
type Location struct {
|
||||||
|
City string
|
||||||
|
}
|
||||||
|
type Person struct {
|
||||||
|
*Family `mapstructure:",omitempty"`
|
||||||
|
*Location `mapstructure:",omitempty"`
|
||||||
|
Age int
|
||||||
|
FirstName string
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &map[string]interface{}{}
|
||||||
|
input := Person{FirstName: "Somebody"}
|
||||||
|
err := Decode(input, &result)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%+v", result)
|
||||||
|
// Output:
|
||||||
|
// &map[Age:0 FirstName:Somebody]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package mapstructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecode_Ptr(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
type G struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type X struct {
|
||||||
|
Id int
|
||||||
|
Name int
|
||||||
|
}
|
||||||
|
|
||||||
|
type AG struct {
|
||||||
|
List []*G
|
||||||
|
}
|
||||||
|
|
||||||
|
type AX struct {
|
||||||
|
List []*X
|
||||||
|
}
|
||||||
|
|
||||||
|
g2 := &AG{
|
||||||
|
List: []*G{
|
||||||
|
{
|
||||||
|
Id: 11,
|
||||||
|
Name: "gg",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
x2 := AX{}
|
||||||
|
|
||||||
|
// 报错但还是会转换成功,转换后值为目标类型的 0 值
|
||||||
|
err := Decode(g2, &x2)
|
||||||
|
|
||||||
|
res := AX{
|
||||||
|
List: []*X{
|
||||||
|
{
|
||||||
|
Id: 11,
|
||||||
|
Name: 0, // 这个类型的 0 值
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Decode_Ptr jderr should not be 'nil': %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(res, x2) {
|
||||||
|
t.Errorf("result should be %#v: got %#v", res, x2)
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,26 @@
|
||||||
|
package mapstructure
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// DecodeWithTime 支持时间转字符串
|
||||||
|
// 支持
|
||||||
|
// 1. *Time.time 转 string/*string
|
||||||
|
// 2. *Time.time 转 uint/uint32/uint64/int/int32/int64,支持带指针
|
||||||
|
// 不能用 Time.time 转,它会在上层认为是一个结构体数据而直接转成map,再到hook方法
|
||||||
|
func DecodeWithTime(input, output interface{}, layout string) error {
|
||||||
|
if layout == "" {
|
||||||
|
layout = time.DateTime
|
||||||
|
}
|
||||||
|
config := &DecoderConfig{
|
||||||
|
Metadata: nil,
|
||||||
|
Result: output,
|
||||||
|
DecodeHook: ComposeDecodeHookFunc(TimeToStringHook(layout), TimeToUnixIntHook()),
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder, err := NewDecoder(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoder.Decode(input)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
package mapstructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TimeToStringHook 时间转字符串
|
||||||
|
// 支持 *Time.time 转 string/*string
|
||||||
|
// 不能用 Time.time 转,它会在上层认为是一个结构体数据而直接转成map,再到hook方法
|
||||||
|
func TimeToStringHook(layout string) DecodeHookFunc {
|
||||||
|
return func(
|
||||||
|
f reflect.Type,
|
||||||
|
t reflect.Type,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
// 判断目标类型是否为字符串
|
||||||
|
var strType string
|
||||||
|
var isStrPointer *bool // 要转换的目标类型是否为指针字符串
|
||||||
|
if t == reflect.TypeOf(strType) {
|
||||||
|
isStrPointer = new(bool)
|
||||||
|
} else if t == reflect.TypeOf(&strType) {
|
||||||
|
isStrPointer = new(bool)
|
||||||
|
*isStrPointer = true
|
||||||
|
}
|
||||||
|
if isStrPointer == nil {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断类型是否为时间
|
||||||
|
timeType := time.Time{}
|
||||||
|
if f != reflect.TypeOf(timeType) && f != reflect.TypeOf(&timeType) {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将时间转换为字符串
|
||||||
|
var output string
|
||||||
|
switch v := data.(type) {
|
||||||
|
case *time.Time:
|
||||||
|
output = v.Format(layout)
|
||||||
|
case time.Time:
|
||||||
|
output = v.Format(layout)
|
||||||
|
default:
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if *isStrPointer {
|
||||||
|
return &output, nil
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeToUnixIntHook 时间转时间戳
|
||||||
|
// 支持 *Time.time 转 uint/uint32/uint64/int/int32/int64,支持带指针
|
||||||
|
// 不能用 Time.time 转,它会在上层认为是一个结构体数据而直接转成map,再到hook方法
|
||||||
|
func TimeToUnixIntHook() DecodeHookFunc {
|
||||||
|
return func(
|
||||||
|
f reflect.Type,
|
||||||
|
t reflect.Type,
|
||||||
|
data interface{}) (interface{}, error) {
|
||||||
|
|
||||||
|
tkd := t.Kind()
|
||||||
|
if tkd != reflect.Int && tkd != reflect.Int32 && tkd != reflect.Int64 &&
|
||||||
|
tkd != reflect.Uint && tkd != reflect.Uint32 && tkd != reflect.Uint64 {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断类型是否为时间
|
||||||
|
timeType := time.Time{}
|
||||||
|
if f != reflect.TypeOf(timeType) && f != reflect.TypeOf(&timeType) {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将时间转换为字符串
|
||||||
|
var output int64
|
||||||
|
switch v := data.(type) {
|
||||||
|
case *time.Time:
|
||||||
|
output = v.Unix()
|
||||||
|
case time.Time:
|
||||||
|
output = v.Unix()
|
||||||
|
default:
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
switch tkd {
|
||||||
|
case reflect.Int:
|
||||||
|
return int(output), nil
|
||||||
|
case reflect.Int32:
|
||||||
|
return int32(output), nil
|
||||||
|
case reflect.Int64:
|
||||||
|
return output, nil
|
||||||
|
case reflect.Uint:
|
||||||
|
return uint(output), nil
|
||||||
|
case reflect.Uint32:
|
||||||
|
return uint32(output), nil
|
||||||
|
case reflect.Uint64:
|
||||||
|
return uint64(output), nil
|
||||||
|
default:
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,274 @@
|
||||||
|
package mapstructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_TimeToStringHook(t *testing.T) {
|
||||||
|
type Input struct {
|
||||||
|
Time time.Time
|
||||||
|
Id int
|
||||||
|
}
|
||||||
|
|
||||||
|
type InputTPointer struct {
|
||||||
|
Time *time.Time
|
||||||
|
Id int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Output struct {
|
||||||
|
Time string
|
||||||
|
Id int
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutputTPointer struct {
|
||||||
|
Time *string
|
||||||
|
Id int
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
target := now.Format("2006-01-02 15:04:05")
|
||||||
|
idValue := 1
|
||||||
|
tests := []struct {
|
||||||
|
input any
|
||||||
|
output any
|
||||||
|
name string
|
||||||
|
layout string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "测试Time.time转string",
|
||||||
|
layout: "2006-01-02 15:04:05",
|
||||||
|
input: InputTPointer{
|
||||||
|
Time: &now,
|
||||||
|
Id: idValue,
|
||||||
|
},
|
||||||
|
output: Output{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "测试*Time.time转*string",
|
||||||
|
layout: "2006-01-02 15:04:05",
|
||||||
|
input: InputTPointer{
|
||||||
|
Time: &now,
|
||||||
|
Id: idValue,
|
||||||
|
},
|
||||||
|
output: OutputTPointer{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
decoder, err := NewDecoder(&DecoderConfig{
|
||||||
|
DecodeHook: TimeToStringHook(tt.layout),
|
||||||
|
Result: &tt.output,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewDecoder() jderr = %v,want nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i, isOk := tt.input.(Input); isOk {
|
||||||
|
err = decoder.Decode(i)
|
||||||
|
}
|
||||||
|
if i, isOk := tt.input.(InputTPointer); isOk {
|
||||||
|
err = decoder.Decode(&i)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Decode jderr = %v,want nil", err)
|
||||||
|
}
|
||||||
|
//验证测试值
|
||||||
|
if output, isOk := tt.output.(OutputTPointer); isOk {
|
||||||
|
if *output.Time != target {
|
||||||
|
t.Errorf("Decode output time = %v,want %v", *output.Time, target)
|
||||||
|
}
|
||||||
|
if output.Id != idValue {
|
||||||
|
t.Errorf("Decode output id = %v,want %v", output.Id, idValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if output, isOk := tt.output.(Output); isOk {
|
||||||
|
if output.Time != target {
|
||||||
|
t.Errorf("Decode output time = %v,want %v", output.Time, target)
|
||||||
|
}
|
||||||
|
if output.Id != idValue {
|
||||||
|
t.Errorf("Decode output id = %v,want %v", output.Id, idValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_TimeToUnixIntHook(t *testing.T) {
|
||||||
|
type InputTPointer struct {
|
||||||
|
Time *time.Time
|
||||||
|
Id int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Output[T int | *int | int32 | *int32 | int64 | *int64 | uint | *uint] struct {
|
||||||
|
Time T
|
||||||
|
Id int
|
||||||
|
}
|
||||||
|
|
||||||
|
type test struct {
|
||||||
|
input any
|
||||||
|
output any
|
||||||
|
name string
|
||||||
|
layout string
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
target := now.Unix()
|
||||||
|
idValue := 1
|
||||||
|
tests := []test{
|
||||||
|
{
|
||||||
|
name: "测试Time.time转int",
|
||||||
|
layout: "2006-01-02 15:04:05",
|
||||||
|
input: InputTPointer{
|
||||||
|
Time: &now,
|
||||||
|
Id: idValue,
|
||||||
|
},
|
||||||
|
output: Output[int]{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "测试Time.time转*int",
|
||||||
|
layout: "2006-01-02 15:04:05",
|
||||||
|
input: InputTPointer{
|
||||||
|
Time: &now,
|
||||||
|
Id: idValue,
|
||||||
|
},
|
||||||
|
output: Output[*int]{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "测试Time.time转int32",
|
||||||
|
layout: "2006-01-02 15:04:05",
|
||||||
|
input: InputTPointer{
|
||||||
|
Time: &now,
|
||||||
|
Id: idValue,
|
||||||
|
},
|
||||||
|
output: Output[int32]{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "测试Time.time转*int32",
|
||||||
|
layout: "2006-01-02 15:04:05",
|
||||||
|
input: InputTPointer{
|
||||||
|
Time: &now,
|
||||||
|
Id: idValue,
|
||||||
|
},
|
||||||
|
output: Output[*int32]{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "测试Time.time转int64",
|
||||||
|
layout: "2006-01-02 15:04:05",
|
||||||
|
input: InputTPointer{
|
||||||
|
Time: &now,
|
||||||
|
Id: idValue,
|
||||||
|
},
|
||||||
|
output: Output[int64]{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "测试Time.time转*int64",
|
||||||
|
layout: "2006-01-02 15:04:05",
|
||||||
|
input: InputTPointer{
|
||||||
|
Time: &now,
|
||||||
|
Id: idValue,
|
||||||
|
},
|
||||||
|
output: Output[*int64]{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "测试Time.time转uint",
|
||||||
|
layout: "2006-01-02 15:04:05",
|
||||||
|
input: InputTPointer{
|
||||||
|
Time: &now,
|
||||||
|
Id: idValue,
|
||||||
|
},
|
||||||
|
output: Output[uint]{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "测试Time.time转*uint",
|
||||||
|
layout: "2006-01-02 15:04:05",
|
||||||
|
input: InputTPointer{
|
||||||
|
Time: &now,
|
||||||
|
Id: idValue,
|
||||||
|
},
|
||||||
|
output: Output[*uint]{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
decoder, err := NewDecoder(&DecoderConfig{
|
||||||
|
DecodeHook: TimeToUnixIntHook(),
|
||||||
|
Result: &tt.output,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewDecoder() jderr = %v,want nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i, isOk := tt.input.(InputTPointer); isOk {
|
||||||
|
err = decoder.Decode(i)
|
||||||
|
}
|
||||||
|
if i, isOk := tt.input.(InputTPointer); isOk {
|
||||||
|
err = decoder.Decode(&i)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Decode jderr = %v,want nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//验证测试值
|
||||||
|
switch v := tt.output.(type) {
|
||||||
|
case Output[int]:
|
||||||
|
if int64(v.Time) != target {
|
||||||
|
t.Errorf("Decode output time = %v,want %v", v.Time, target)
|
||||||
|
}
|
||||||
|
if v.Id != idValue {
|
||||||
|
t.Errorf("Decode output id = %v,want %v", v.Id, idValue)
|
||||||
|
}
|
||||||
|
case Output[*int]:
|
||||||
|
if int64(*v.Time) != target {
|
||||||
|
t.Errorf("Decode output time = %v,want %v", v.Time, target)
|
||||||
|
}
|
||||||
|
if v.Id != idValue {
|
||||||
|
t.Errorf("Decode output id = %v,want %v", v.Id, idValue)
|
||||||
|
}
|
||||||
|
case Output[int32]:
|
||||||
|
if int64(v.Time) != target {
|
||||||
|
t.Errorf("Decode output time = %v,want %v", v.Time, target)
|
||||||
|
}
|
||||||
|
if v.Id != idValue {
|
||||||
|
t.Errorf("Decode output id = %v,want %v", v.Id, idValue)
|
||||||
|
}
|
||||||
|
case Output[*int32]:
|
||||||
|
if int64(*v.Time) != target {
|
||||||
|
t.Errorf("Decode output time = %v,want %v", v.Time, target)
|
||||||
|
}
|
||||||
|
if v.Id != idValue {
|
||||||
|
t.Errorf("Decode output id = %v,want %v", v.Id, idValue)
|
||||||
|
}
|
||||||
|
case Output[int64]:
|
||||||
|
if int64(v.Time) != target {
|
||||||
|
t.Errorf("Decode output time = %v,want %v", v.Time, target)
|
||||||
|
}
|
||||||
|
if v.Id != idValue {
|
||||||
|
t.Errorf("Decode output id = %v,want %v", v.Id, idValue)
|
||||||
|
}
|
||||||
|
case Output[*int64]:
|
||||||
|
if int64(*v.Time) != target {
|
||||||
|
t.Errorf("Decode output time = %v,want %v", v.Time, target)
|
||||||
|
}
|
||||||
|
if v.Id != idValue {
|
||||||
|
t.Errorf("Decode output id = %v,want %v", v.Id, idValue)
|
||||||
|
}
|
||||||
|
case Output[uint]:
|
||||||
|
if int64(v.Time) != target {
|
||||||
|
t.Errorf("Decode output time = %v,want %v", v.Time, target)
|
||||||
|
}
|
||||||
|
if v.Id != idValue {
|
||||||
|
t.Errorf("Decode output id = %v,want %v", v.Id, idValue)
|
||||||
|
}
|
||||||
|
case Output[*uint]:
|
||||||
|
if int64(*v.Time) != target {
|
||||||
|
t.Errorf("Decode output time = %v,want %v", v.Time, target)
|
||||||
|
}
|
||||||
|
if v.Id != idValue {
|
||||||
|
t.Errorf("Decode output id = %v,want %v", v.Id, idValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,11 @@ package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/pkg/ollama"
|
"ai_scheduler/internal/pkg/ollama"
|
||||||
|
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ProviderSetClient = wire.NewSet(ollama.NewClient)
|
var ProviderSetClient = wire.NewSet(
|
||||||
|
NewRdb,
|
||||||
|
NewGormDb,
|
||||||
|
ollama.NewClient,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Rdb struct {
|
||||||
|
Rdb *redis.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var rdb *Rdb
|
||||||
|
|
||||||
|
func NewRdb(c *config.Config) *Rdb {
|
||||||
|
if rdb == nil {
|
||||||
|
//构建 redis
|
||||||
|
rdbBuild := buildRdb(c.Redis)
|
||||||
|
//退出时清理资源
|
||||||
|
rdb = &Rdb{Rdb: rdbBuild}
|
||||||
|
}
|
||||||
|
//cleanup := func() {
|
||||||
|
// if rdb != nil {
|
||||||
|
// if err := rdb.Rdb.Close(); err != nil {
|
||||||
|
// fmt.Println("关闭 redis 失败:%v", err)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// fmt.Println("关闭 data 中的连接资源已完成")
|
||||||
|
//}
|
||||||
|
return rdb
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildRdb 构建redis client
|
||||||
|
func buildRdb(c *config.Redis) *redis.Client {
|
||||||
|
|
||||||
|
rdb := redis.NewClient(&redis.Options{
|
||||||
|
Addr: c.Host,
|
||||||
|
Password: c.Pass,
|
||||||
|
ReadTimeout: time.Duration(c.Tls) * time.Second,
|
||||||
|
WriteTimeout: time.Duration(c.Tls) * time.Second,
|
||||||
|
PoolSize: int(c.PoolSize),
|
||||||
|
MinIdleConns: int(c.MaxIdle),
|
||||||
|
ConnMaxIdleTime: time.Duration(c.MaxIdleTime) * time.Second,
|
||||||
|
DB: int(c.Db),
|
||||||
|
})
|
||||||
|
return rdb
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package utils_gorm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DBConn(c *config.DB) (*gorm.DB, func()) {
|
||||||
|
mysqlConn, err := sql.Open(c.Driver, c.Source)
|
||||||
|
gormDB, err := gorm.Open(
|
||||||
|
mysql.New(mysql.Config{Conn: mysqlConn}),
|
||||||
|
)
|
||||||
|
|
||||||
|
gormDB.Logger = NewCustomLogger(gormDB)
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to connect database")
|
||||||
|
}
|
||||||
|
sqlDB, err := gormDB.DB()
|
||||||
|
|
||||||
|
// SetMaxIdleConns sets the maximum number of connections in the idle connection pool.
|
||||||
|
sqlDB.SetMaxIdleConns(int(c.MaxIdle))
|
||||||
|
|
||||||
|
// SetMaxOpenConns sets the maximum number of open connections to the database.
|
||||||
|
sqlDB.SetMaxOpenConns(int(c.MaxLifetime))
|
||||||
|
|
||||||
|
// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
|
||||||
|
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||||
|
|
||||||
|
return gormDB, func() {
|
||||||
|
if mysqlConn != nil {
|
||||||
|
fmt.Println("关闭 physicalGoodsDB")
|
||||||
|
if err := mysqlConn.Close(); err != nil {
|
||||||
|
fmt.Println("关闭 physicalGoodsDB 失败:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
package utils_gorm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomLogger struct {
|
||||||
|
gormLogger logger.Interface
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCustomLogger(db *gorm.DB) *CustomLogger {
|
||||||
|
return &CustomLogger{
|
||||||
|
gormLogger: logger.Default.LogMode(logger.Info),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomLogger) LogMode(level logger.LogLevel) logger.Interface {
|
||||||
|
newlogger := *l
|
||||||
|
newlogger.gormLogger = l.gormLogger.LogMode(level)
|
||||||
|
return &newlogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomLogger) Info(ctx context.Context, msg string, data ...interface{}) {
|
||||||
|
l.gormLogger.Info(ctx, msg, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomLogger) Warn(ctx context.Context, msg string, data ...interface{}) {
|
||||||
|
l.gormLogger.Warn(ctx, msg, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomLogger) Error(ctx context.Context, msg string, data ...interface{}) {
|
||||||
|
l.gormLogger.Error(ctx, msg, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
|
||||||
|
elapsed := time.Since(begin)
|
||||||
|
sql, _ := fc()
|
||||||
|
l.gormLogger.Trace(ctx, begin, fc, err)
|
||||||
|
operation := extractOperation(sql)
|
||||||
|
tableName := extractTableName(sql)
|
||||||
|
fmt.Println(tableName)
|
||||||
|
//// 将SQL语句保存到数据库
|
||||||
|
if operation == 0 || tableName == "sql_log" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//go l.db.Model(&SqlLog{}).Create(&SqlLog{
|
||||||
|
// OperatorID: 1,
|
||||||
|
// OperatorName: "test",
|
||||||
|
// SqlInfo: sql,
|
||||||
|
// TableNames: tableName,
|
||||||
|
// Type: operation,
|
||||||
|
//})
|
||||||
|
|
||||||
|
// 如果有需要,也可以根据执行时间(elapsed)等条件过滤或处理日志记录
|
||||||
|
if elapsed > time.Second {
|
||||||
|
//l.gormLogger.Warn(ctx, "Slow SQL (> 1s): %s", sql)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractTableName extracts the table name from a SQL query, supporting quoted table names.
|
||||||
|
func extractTableName(sql string) string {
|
||||||
|
// 使用非捕获组匹配多种SQL操作关键词
|
||||||
|
re := regexp.MustCompile(`(?i)\b(?:from|update|into|delete\s+from)\b\s+[\` + "`" + `"]?(\w+)[\` + "`" + `"]?`)
|
||||||
|
match := re.FindStringSubmatch(sql)
|
||||||
|
|
||||||
|
// 检查是否匹配成功
|
||||||
|
if len(match) > 1 {
|
||||||
|
return match[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractOperation extracts the operation type from a SQL query.
|
||||||
|
func extractOperation(sql string) int32 {
|
||||||
|
sql = strings.TrimSpace(strings.ToLower(sql))
|
||||||
|
var operation int32
|
||||||
|
if strings.HasPrefix(sql, "select") {
|
||||||
|
operation = 0
|
||||||
|
} else if strings.HasPrefix(sql, "insert") {
|
||||||
|
operation = 1
|
||||||
|
} else if strings.HasPrefix(sql, "update") {
|
||||||
|
operation = 3
|
||||||
|
} else if strings.HasPrefix(sql, "delete") {
|
||||||
|
operation = 2
|
||||||
|
}
|
||||||
|
return operation
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"ai_scheduler/internal/data/constant"
|
||||||
"ai_scheduler/internal/entitys"
|
"ai_scheduler/internal/entitys"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
|
|
@ -40,62 +41,54 @@ func (h *ChatService) ChatFail(c *websocket.Conn, content string) {
|
||||||
|
|
||||||
func (h *ChatService) Chat(c *websocket.Conn) {
|
func (h *ChatService) Chat(c *websocket.Conn) {
|
||||||
log.Println("客户端已连接")
|
log.Println("客户端已连接")
|
||||||
|
defer c.Close()
|
||||||
// 循环读取客户端消息
|
// 循环读取客户端消息
|
||||||
for {
|
for {
|
||||||
messageType, msg, err := c.ReadMessage()
|
messageType, message, err := c.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("读取错误:", err)
|
log.Println("读取错误:", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
msg, chatType := h.handleMessageToString(c, messageType, message)
|
||||||
|
if chatType == constant.ConnStatusClosed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if chatType == constant.ConnStatusIgnore {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("收到消息: %s", msg)
|
log.Printf("收到消息: %s", string(msg))
|
||||||
var req entitys.ChatSockRequest
|
var req entitys.ChatSockRequest
|
||||||
if err := json.Unmarshal(msg, &req); err != nil {
|
if err := json.Unmarshal(msg, &req); err != nil {
|
||||||
log.Println("JSON parse error:", err)
|
log.Println("JSON parse error:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
err = h.routerService.RouteWithSocket(c, &req)
|
||||||
// 回显消息给客户端
|
if err != nil {
|
||||||
if err := c.WriteMessage(messageType, msg); err != nil {
|
log.Println("处理失败:", err)
|
||||||
log.Println("写入错误:", err)
|
continue
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("客户端已断开")
|
log.Println("客户端已断开")
|
||||||
//var req entitys.ChatRequest
|
}
|
||||||
//if err := c.BodyParser(&req); err != nil {
|
|
||||||
// return errors.ParamError
|
func (h *ChatService) handleMessageToString(c *websocket.Conn, msgType int, msg any) (text []byte, chatType constant.ConnStatus) {
|
||||||
//}
|
switch msgType {
|
||||||
//
|
case websocket.TextMessage:
|
||||||
//// 转换为服务层请求
|
return msg.([]byte), constant.ConnStatusNormal
|
||||||
//serviceReq := &entitys.ChatRequest{
|
case websocket.BinaryMessage:
|
||||||
// UserInput: req.UserInput,
|
return msg.([]byte), constant.ConnStatusNormal
|
||||||
// Caller: req.Caller,
|
case websocket.CloseMessage:
|
||||||
// SessionID: req.SessionID,
|
|
||||||
// ChatRequestMeta: entitys.ChatRequestMeta{
|
return nil, constant.ConnStatusClosed
|
||||||
// Authorization: c.Request().Header(),
|
case websocket.PingMessage:
|
||||||
// },
|
// 可选:回复 Pong
|
||||||
//}
|
c.WriteMessage(websocket.PongMessage, nil)
|
||||||
//
|
return nil, constant.ConnStatusIgnore
|
||||||
//// 调用路由服务
|
case websocket.PongMessage:
|
||||||
//response, err := h.routerService.Route(c.Request.Context(), serviceReq)
|
return nil, constant.ConnStatusIgnore
|
||||||
//if err != nil {
|
default:
|
||||||
// c.JSON(http.StatusInternalServerError, ChatResponse{
|
return nil, constant.ConnStatusIgnore
|
||||||
// Status: "error",
|
}
|
||||||
// Message: err.Error(),
|
return msg.([]byte), constant.ConnStatusIgnore
|
||||||
// })
|
|
||||||
// return
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// 转换响应格式
|
|
||||||
//httpResponse := &ChatResponse{
|
|
||||||
// Message: response.Message,
|
|
||||||
// Status: response.Status,
|
|
||||||
// Data: response.Data,
|
|
||||||
// TaskCode: response.TaskCode,
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//c.JSON(http.StatusOK, httpResponse)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
package dataTemp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/pkg/mapstructure"
|
||||||
|
"ai_scheduler/utils"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/go-kratos/kratos/v2/log"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PrimaryKey struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GormDb struct {
|
||||||
|
Client *gorm.DB
|
||||||
|
}
|
||||||
|
type contextTxKey struct{}
|
||||||
|
|
||||||
|
func (d *Db) DB(ctx context.Context) *gorm.DB {
|
||||||
|
tx, ok := ctx.Value(contextTxKey{}).(*gorm.DB)
|
||||||
|
if ok {
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
return d.Db.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Db) ExecTx(ctx context.Context, f func(ctx context.Context) error) error {
|
||||||
|
return t.Db.Client.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
ctx = context.WithValue(ctx, contextTxKey{}, tx)
|
||||||
|
return f(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Db struct {
|
||||||
|
Db *GormDb
|
||||||
|
Log *log.Helper
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataTemp struct {
|
||||||
|
Db *gorm.DB
|
||||||
|
Model interface{}
|
||||||
|
Do interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDataTemp(db *utils.Db, model interface{}) *DataTemp {
|
||||||
|
return &DataTemp{Db: db.Client, Model: model}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k DataTemp) GetById(id int) (data map[string]interface{}, err error) {
|
||||||
|
err = k.Db.Model(k.Model).Where("id = ?", id).Find(&data).Error
|
||||||
|
if data == nil {
|
||||||
|
err = sql.ErrNoRows
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k DataTemp) Add(data interface{}) (id int, err error) {
|
||||||
|
var primary *PrimaryKey
|
||||||
|
add := k.Db.Model(k.Model).Create(data)
|
||||||
|
_ = mapstructure.Decode(data, &primary)
|
||||||
|
return primary.Id, add.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k DataTemp) GetList(cond *builder.Cond, pageBoIn *ReqPageBo) (list []map[string]interface{}, pageBoOut *RespPageBo, err error) {
|
||||||
|
var (
|
||||||
|
query, _ = builder.ToBoundSQL(*cond)
|
||||||
|
model = k.Db.Model(k.Model).Where(query)
|
||||||
|
total int64
|
||||||
|
)
|
||||||
|
model.Count(&total)
|
||||||
|
pageBoOut = pageBoOut.SetDataByReq(total, pageBoIn)
|
||||||
|
model.Limit(pageBoIn.GetSize()).Offset(pageBoIn.GetOffset()).Order("updated_at desc").Find(&list)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k DataTemp) GetRange(cond *builder.Cond) (list []map[string]interface{}, err error) {
|
||||||
|
var (
|
||||||
|
query, _ = builder.ToBoundSQL(*cond)
|
||||||
|
model = k.Db.Model(k.Model).Where(query)
|
||||||
|
)
|
||||||
|
err = model.Find(&list).Error
|
||||||
|
return list, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k DataTemp) GetOneBySearch(cond *builder.Cond) (data map[string]interface{}, err error) {
|
||||||
|
query, _ := builder.ToBoundSQL(*cond)
|
||||||
|
err = k.Db.Model(k.Model).Where(query).Limit(1).Find(&data).Error
|
||||||
|
if data == nil {
|
||||||
|
err = sql.ErrNoRows
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k DataTemp) GetOneBySearchToStrut(cond *builder.Cond, result interface{}) error {
|
||||||
|
query, _ := builder.ToBoundSQL(*cond)
|
||||||
|
err := k.Db.Model(k.Model).Where(query).Limit(1).Find(&result).Error
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k DataTemp) UpdateByCond(cond *builder.Cond, data interface{}) (err error) {
|
||||||
|
var (
|
||||||
|
query, _ = builder.ToBoundSQL(*cond)
|
||||||
|
model = k.Db.Model(k.Model)
|
||||||
|
)
|
||||||
|
err = model.Where(query).Updates(data).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package dataTemp
|
||||||
|
|
||||||
|
// ReqPageBo 分页请求实体
|
||||||
|
type ReqPageBo struct {
|
||||||
|
Page int //页码,从第1页开始
|
||||||
|
Limit int //分页大小
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOffset 获取便宜量
|
||||||
|
func (r *ReqPageBo) GetOffset() int {
|
||||||
|
if r == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
offset := (r.Page - 1) * r.Limit
|
||||||
|
if offset < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSize 获取分页
|
||||||
|
func (r *ReqPageBo) GetSize() int {
|
||||||
|
if r == nil {
|
||||||
|
return 20
|
||||||
|
}
|
||||||
|
return r.Limit
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNum 获取页码
|
||||||
|
func (r *ReqPageBo) GetNum() int {
|
||||||
|
if r == nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return r.Page
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package dataTemp
|
||||||
|
|
||||||
|
// RespPageBo 分页响应实体
|
||||||
|
type RespPageBo struct {
|
||||||
|
Page int //页码
|
||||||
|
Limit int //每页大小
|
||||||
|
Total int64 //总数
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDataByReq 通过req 设置响应参数
|
||||||
|
func (r *RespPageBo) SetDataByReq(total int64, reqPage *ReqPageBo) *RespPageBo {
|
||||||
|
resp := r
|
||||||
|
if r == nil {
|
||||||
|
resp = &RespPageBo{}
|
||||||
|
}
|
||||||
|
resp.Total = total
|
||||||
|
if reqPage != nil {
|
||||||
|
resp.Page = reqPage.Page
|
||||||
|
resp.Limit = reqPage.Limit
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
package errcode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SuccessMsg 自定义成功消息
|
||||||
|
var SuccessMsg = "成功"
|
||||||
|
var SuccessCode = 200
|
||||||
|
|
||||||
|
func SetSuccessMsg(msg string) {
|
||||||
|
SuccessMsg = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
type BusinessErr struct {
|
||||||
|
Code int32
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BusinessErr) Error() string {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
func (e *BusinessErr) GRPCStatus() *status.Status {
|
||||||
|
var code = codes.Code(e.Code)
|
||||||
|
return status.New(code, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomErr 自定义错误
|
||||||
|
func CustomErr(code int32, message string) *BusinessErr {
|
||||||
|
return &BusinessErr{Code: code, Message: message}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
NotFoundErr = &BusinessErr{Code: 404, Message: "资源未找到"}
|
||||||
|
ParamError = &BusinessErr{Code: 400, Message: "参数错误"}
|
||||||
|
//未经授权
|
||||||
|
NotAuth = &BusinessErr{Code: 401, Message: "未授权"}
|
||||||
|
EnCrypt = &BusinessErr{Code: 401, Message: "加密失败"}
|
||||||
|
DeCrypt = &BusinessErr{Code: 401, Message: "解密失败"}
|
||||||
|
|
||||||
|
//请求被禁止
|
||||||
|
Forbidden = &BusinessErr{Code: 403, Message: "禁止访问"}
|
||||||
|
WhiteIp = &BusinessErr{Code: 403, Message: "访问IP,不在白名单内"}
|
||||||
|
|
||||||
|
//系统错误
|
||||||
|
SystemError = &BusinessErr{Code: 500, Message: "系统错误"}
|
||||||
|
|
||||||
|
ThirtyDayQueryLimit = &BusinessErr{Code: 420, Message: "只能查询最近31天的数据"}
|
||||||
|
|
||||||
|
GoodsSameErr = &BusinessErr{Code: 404, Message: "存在相同货品编码|货品名称的商品,请检查后重试"}
|
||||||
|
|
||||||
|
HadDefaultWareHouseErr = &BusinessErr{Code: 400, Message: "该商品已存在默认仓,请检查后重试"}
|
||||||
|
|
||||||
|
HadSameSupplierRelation = &BusinessErr{Code: 400, Message: "已存在相同的供应商商品关系,请检查后重试"}
|
||||||
|
|
||||||
|
AppRsaEncryptKeyNotFound = &BusinessErr{Code: 400, Message: "密钥缺失"}
|
||||||
|
|
||||||
|
AppRsaEncryptFail = &BusinessErr{Code: 400, Message: "Rsa加密失败"}
|
||||||
|
|
||||||
|
AppRsaDecryptKeyNotFound = &BusinessErr{Code: 400, Message: "密钥缺失"}
|
||||||
|
|
||||||
|
AppRsaDecryptFail = &BusinessErr{Code: 400, Message: "Rsa解密失败"}
|
||||||
|
|
||||||
|
AppSM2EncryptKeyNotFound = &BusinessErr{Code: 400, Message: "密钥缺失"}
|
||||||
|
|
||||||
|
AppSM2EncryptFail = &BusinessErr{Code: 400, Message: "Sm2加密失败"}
|
||||||
|
|
||||||
|
AppSM2DecryptKeyNotFound = &BusinessErr{Code: 400, Message: "密钥缺失"}
|
||||||
|
|
||||||
|
AppSM2DecryptFail = &BusinessErr{Code: 400, Message: "Sm2解密失败"}
|
||||||
|
|
||||||
|
AppSM4EncryptKeyNotFound = &BusinessErr{Code: 400, Message: "密钥缺失"}
|
||||||
|
|
||||||
|
AppSM4EncryptFail = &BusinessErr{Code: 400, Message: "Sm4加密失败"}
|
||||||
|
|
||||||
|
AppSM4DecryptKeyNotFound = &BusinessErr{Code: 400, Message: "密钥缺失"}
|
||||||
|
|
||||||
|
AppSM4DecryptFail = &BusinessErr{Code: 400, Message: "Sm4解密失败"}
|
||||||
|
|
||||||
|
BalanceNotEnough = &BusinessErr{Code: 400, Message: "余额不足"}
|
||||||
|
|
||||||
|
CusStatusException = &BusinessErr{Code: 400, Message: "客户状态异常"}
|
||||||
|
|
||||||
|
HadSameCusGoods = &BusinessErr{Code: 400, Message: "已存在相同的客户商品授权,请检查后重试"}
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/pkg/utils_gorm"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Db struct {
|
||||||
|
Client *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGormDb(c *config.Config) (*Db, func()) {
|
||||||
|
transDBClient, mf := utils_gorm.DBConn(c.DB)
|
||||||
|
//directDBClient, df := directDB(c, hLog)
|
||||||
|
cleanup := func() {
|
||||||
|
mf()
|
||||||
|
//df()
|
||||||
|
}
|
||||||
|
return &Db{
|
||||||
|
Client: transDBClient,
|
||||||
|
//DirectDBClient: directDBClient,
|
||||||
|
}, cleanup
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ProviderUtils = wire.NewSet(
|
||||||
|
NewRdb,
|
||||||
|
NewGormDb,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Rdb struct {
|
||||||
|
Rdb *redis.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var rdb *Rdb
|
||||||
|
|
||||||
|
func NewRdb(c *config.Redis) *Rdb {
|
||||||
|
if rdb == nil {
|
||||||
|
//构建 redis
|
||||||
|
rdbBuild := buildRdb(c)
|
||||||
|
//退出时清理资源
|
||||||
|
rdb = &Rdb{Rdb: rdbBuild}
|
||||||
|
}
|
||||||
|
//cleanup := func() {
|
||||||
|
// if rdb != nil {
|
||||||
|
// if err := rdb.Rdb.Close(); err != nil {
|
||||||
|
// fmt.Println("关闭 redis 失败:%v", err)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// fmt.Println("关闭 data 中的连接资源已完成")
|
||||||
|
//}
|
||||||
|
return rdb
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildRdb 构建redis client
|
||||||
|
func buildRdb(c *config.Redis) *redis.Client {
|
||||||
|
|
||||||
|
rdb := redis.NewClient(&redis.Options{
|
||||||
|
Addr: c.Host,
|
||||||
|
Password: c.Pass,
|
||||||
|
ReadTimeout: time.Duration(c.Tls) * time.Second,
|
||||||
|
WriteTimeout: time.Duration(c.Tls) * time.Second,
|
||||||
|
PoolSize: int(c.PoolSize),
|
||||||
|
MinIdleConns: int(c.MaxIdle),
|
||||||
|
ConnMaxIdleTime: time.Duration(c.MaxIdleTime) * time.Second,
|
||||||
|
DB: int(c.Db),
|
||||||
|
})
|
||||||
|
return rdb
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package utils_gorm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TransDB(c *config.DB) (*gorm.DB, func()) {
|
||||||
|
mysqlConn, err := sql.Open(c.Driver, c.Source)
|
||||||
|
gormDB, err := gorm.Open(
|
||||||
|
mysql.New(mysql.Config{Conn: mysqlConn}),
|
||||||
|
)
|
||||||
|
|
||||||
|
gormDB.Logger = NewCustomLogger(gormDB)
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to connect database")
|
||||||
|
}
|
||||||
|
sqlDB, err := gormDB.DB()
|
||||||
|
|
||||||
|
// SetMaxIdleConns sets the maximum number of connections in the idle connection pool.
|
||||||
|
sqlDB.SetMaxIdleConns(int(c.MaxIdle))
|
||||||
|
|
||||||
|
// SetMaxOpenConns sets the maximum number of open connections to the database.
|
||||||
|
sqlDB.SetMaxOpenConns(int(c.MaxLifetime))
|
||||||
|
|
||||||
|
// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
|
||||||
|
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||||
|
|
||||||
|
return gormDB, func() {
|
||||||
|
if mysqlConn != nil {
|
||||||
|
fmt.Println("关闭 physicalGoodsDB")
|
||||||
|
if err := mysqlConn.Close(); err != nil {
|
||||||
|
fmt.Println("关闭 physicalGoodsDB 失败:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
package utils_gorm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomLogger struct {
|
||||||
|
gormLogger logger.Interface
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCustomLogger(db *gorm.DB) *CustomLogger {
|
||||||
|
return &CustomLogger{
|
||||||
|
gormLogger: logger.Default.LogMode(logger.Info),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomLogger) LogMode(level logger.LogLevel) logger.Interface {
|
||||||
|
newlogger := *l
|
||||||
|
newlogger.gormLogger = l.gormLogger.LogMode(level)
|
||||||
|
return &newlogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomLogger) Info(ctx context.Context, msg string, data ...interface{}) {
|
||||||
|
l.gormLogger.Info(ctx, msg, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomLogger) Warn(ctx context.Context, msg string, data ...interface{}) {
|
||||||
|
l.gormLogger.Warn(ctx, msg, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomLogger) Error(ctx context.Context, msg string, data ...interface{}) {
|
||||||
|
l.gormLogger.Error(ctx, msg, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
|
||||||
|
elapsed := time.Since(begin)
|
||||||
|
sql, _ := fc()
|
||||||
|
l.gormLogger.Trace(ctx, begin, fc, err)
|
||||||
|
operation := extractOperation(sql)
|
||||||
|
tableName := extractTableName(sql)
|
||||||
|
fmt.Println(tableName)
|
||||||
|
//// 将SQL语句保存到数据库
|
||||||
|
if operation == 0 || tableName == "sql_log" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//go l.db.Model(&SqlLog{}).Create(&SqlLog{
|
||||||
|
// OperatorID: 1,
|
||||||
|
// OperatorName: "test",
|
||||||
|
// SqlInfo: sql,
|
||||||
|
// TableNames: tableName,
|
||||||
|
// Type: operation,
|
||||||
|
//})
|
||||||
|
|
||||||
|
// 如果有需要,也可以根据执行时间(elapsed)等条件过滤或处理日志记录
|
||||||
|
if elapsed > time.Second {
|
||||||
|
//l.gormLogger.Warn(ctx, "Slow SQL (> 1s): %s", sql)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractTableName extracts the table name from a SQL query, supporting quoted table names.
|
||||||
|
func extractTableName(sql string) string {
|
||||||
|
// 使用非捕获组匹配多种SQL操作关键词
|
||||||
|
re := regexp.MustCompile(`(?i)\b(?:from|update|into|delete\s+from)\b\s+[\` + "`" + `"]?(\w+)[\` + "`" + `"]?`)
|
||||||
|
match := re.FindStringSubmatch(sql)
|
||||||
|
|
||||||
|
// 检查是否匹配成功
|
||||||
|
if len(match) > 1 {
|
||||||
|
return match[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractOperation extracts the operation type from a SQL query.
|
||||||
|
func extractOperation(sql string) int32 {
|
||||||
|
sql = strings.TrimSpace(strings.ToLower(sql))
|
||||||
|
var operation int32
|
||||||
|
if strings.HasPrefix(sql, "select") {
|
||||||
|
operation = 0
|
||||||
|
} else if strings.HasPrefix(sql, "insert") {
|
||||||
|
operation = 1
|
||||||
|
} else if strings.HasPrefix(sql, "update") {
|
||||||
|
operation = 3
|
||||||
|
} else if strings.HasPrefix(sql, "delete") {
|
||||||
|
operation = 2
|
||||||
|
}
|
||||||
|
return operation
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package utils_sys_cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/emirpasic/gods/maps/hashmap"
|
||||||
|
"github.com/emirpasic/gods/maps/treemap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCacheHashMap() *hashmap.Map {
|
||||||
|
return hashmap.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCacheTreeMap() *treemap.Map {
|
||||||
|
return treemap.NewWithIntComparator()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-kratos/kratos/v2/log"
|
||||||
|
"google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
baseconf "trans_hub/base_conf"
|
||||||
|
"trans_hub/pkg"
|
||||||
|
"trans_hub/pkg/mapstructure"
|
||||||
|
)
|
||||||
|
|
||||||
|
const SPACE = "public"
|
||||||
|
const PORT = 8848
|
||||||
|
const User = ""
|
||||||
|
const Pass = ""
|
||||||
|
const IP = "192.168.110.93"
|
||||||
|
const Group = "DEFAULT_GROUP"
|
||||||
|
const DataId = "PG_BASE_CONFIG"
|
||||||
|
|
||||||
|
func TestConfig(t *testing.T) {
|
||||||
|
type Nacos struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"`
|
||||||
|
Port uint64 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
|
||||||
|
}
|
||||||
|
type Conf struct {
|
||||||
|
Nacos *Nacos `protobuf:"bytes,8,opt,name=nacos,proto3" json:"nacos,omitempty"`
|
||||||
|
}
|
||||||
|
var c Conf
|
||||||
|
nc := &baseconf.Nacos{Ip: IP, Port: PORT, Space: SPACE, User: User, Password: Pass}
|
||||||
|
|
||||||
|
var s = ServerConfig(nc, Group, DataId)
|
||||||
|
err := mapstructure.Decode(s, &c)
|
||||||
|
t.Log(s, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMod(t *testing.T) {
|
||||||
|
dir := pkg.GetRootPath()
|
||||||
|
// 读取目录内容
|
||||||
|
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !d.IsDir() && filepath.Ext(path) == ".yaml" {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var result map[string]interface{}
|
||||||
|
err = yaml.Unmarshal(data, &result) // 解析YAML到map中,使用gopkg.v3的yaml包或其他你选择的版本(例如encoding/yaml)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("File: %s\nContent: %+v\n", path, result)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYaml(t *testing.T) {
|
||||||
|
t.Log(GetBaseYaml())
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue