结构修改
This commit is contained in:
parent
abb52eb1d3
commit
2bb054940a
|
|
@ -6,10 +6,13 @@ package main
|
|||
import (
|
||||
"ai_scheduler/internal/biz"
|
||||
"ai_scheduler/internal/config"
|
||||
"ai_scheduler/internal/data/impl"
|
||||
"ai_scheduler/internal/pkg"
|
||||
"ai_scheduler/internal/server"
|
||||
"ai_scheduler/internal/services"
|
||||
"ai_scheduler/internal/tools"
|
||||
"ai_scheduler/utils"
|
||||
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
|
@ -22,6 +25,8 @@ func InitializeApp(*config.Config, log.AllLogger) (*server.Servers, func(), erro
|
|||
pkg.ProviderSetClient,
|
||||
services.ProviderSetServices,
|
||||
biz.ProviderSetBiz,
|
||||
impl.ProviderImpl,
|
||||
utils.ProviderUtils,
|
||||
))
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,3 +9,20 @@ ollama:
|
|||
timeout: "120s"
|
||||
level: "info"
|
||||
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
|
||||
|
||||
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/websocket/v2 v2.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/google/wire v0.7.0
|
||||
github.com/ollama/ollama v0.11.10
|
||||
github.com/redis/go-redis/v9 v9.14.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 (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fasthttp/websocket v1.5.3 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // 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/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/uuid v1.6.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/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/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // 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/rivo/uniseg v0.2.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/cast v1.5.1 // 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/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/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
go.uber.org/atomic 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/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/text v0.23.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // 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.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
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/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/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
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/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/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/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-20200629203442-efcf912fb354/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.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/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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
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/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
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/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-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
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/go-kratos/kratos/v2 v2.9.1 h1:EGif6/S/aK/RCR5clIbyhioTNyoSrii3FC118jG40Z0=
|
||||
github.com/go-kratos/kratos/v2 v2.9.1/go.mod h1:a1MQLjMhIh7R0kcJS9SzJYR43BRI7EPzzN0J1Ksu2bA=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
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/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.2/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 v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
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/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=
|
||||
|
|
@ -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/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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
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.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
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/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/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/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
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/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
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/go.mod h1:9+1//yWPsDE2u+l1a5mpaKrYw4VdnSsRU3ioq5BvMms=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
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.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
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/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
|
||||
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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
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/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/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
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.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
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/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
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=
|
||||
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-20190510104115-cbcb75029529/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-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.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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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-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.5.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/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-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/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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
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.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.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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
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-20190106161140-3f1c8253044a/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-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=
|
||||
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/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
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
|
||||
|
||||
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/tools"
|
||||
"ai_scheduler/tmpl/dataTemp"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/websocket/v2"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// AiRouterService 智能路由服务
|
||||
type AiRouterService struct {
|
||||
aiClient entitys.AIClient
|
||||
toolManager *tools.Manager
|
||||
sessionImpl *impl.SessionImpl
|
||||
conf *config.Config
|
||||
}
|
||||
|
||||
// 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{
|
||||
aiClient: aiClient,
|
||||
toolManager: toolManager,
|
||||
sessionImpl: sessionImpl,
|
||||
conf: conf,
|
||||
}
|
||||
}
|
||||
|
||||
// Route 执行智能路由
|
||||
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{
|
||||
// // {
|
||||
// // Role: "system",
|
||||
// // Content: r.buildSystemPrompt(),
|
||||
// // },
|
||||
// {
|
||||
// Role: "assistant",
|
||||
// Content: r.buildIntentPrompt(req.UserInput),
|
||||
// },
|
||||
// {
|
||||
// Role: "user",
|
||||
// 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))
|
||||
|
||||
//return finalResponse, nil
|
||||
return nil, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildSystemPrompt 构建系统提示词
|
||||
func (r *AiRouterService) buildSystemPrompt() string {
|
||||
prompt := `你是一个智能路由系统,你的任务是根据用户输入判断用户的意图,并且执行对应的任务。`
|
||||
func (r *AiRouterService) buildSystemPrompt(prompt string) string {
|
||||
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
|
||||
}
|
||||
|
||||
// buildIntentPrompt 构建意图识别提示词
|
||||
func (r *AiRouterService) buildIntentPrompt(userInput string) string {
|
||||
prompt := `请分析以下用户输入,判断用户的意图类型。
|
||||
|
||||
用户输入:{user_input}
|
||||
|
||||
意图类型说明:
|
||||
1. order_diagnosis - 订单诊断:用户想要查询、诊断或了解订单相关信息
|
||||
2. knowledge_qa - 知识问答:用户想要进行一般性问答或获取知识信息
|
||||
|
||||
- 当用户意图不够清晰且不匹配 knowledge_qa 以外意图时,使用knowledge_qa
|
||||
- 当用户意图非常不清晰时使用 unknown
|
||||
|
||||
请只返回以下格式的JSON:
|
||||
{
|
||||
"intent": "order_diagnosis" | "knowledge_qa" | "unknown",
|
||||
"confidence": 0.0-1.0,
|
||||
"reasoning": "判断理由"
|
||||
}
|
||||
prompt := `##任务
|
||||
分析用户输入,判断用户的意图类型,没有使用Markdown格式的json格式回复
|
||||
##意图类型
|
||||
1. product_diagnosis - 商品诊断:用户想要查询、诊断或了解商品相关信息
|
||||
2. order_diagnosis - 订单诊断:用户想要查询、诊断或了解订单相关信息
|
||||
3. knowledge_qa - 知识问答:用户想要进行一般性问答或获取知识信息
|
||||
##判断规则
|
||||
1.当用户意图不够清晰且不匹配 knowledge_qa 以外意图时,使用knowledge_qa
|
||||
2.当用户意图非常不清晰时使用 unknown
|
||||
##格式要求
|
||||
1.返回以下格式的JSON:
|
||||
{ "intent": "product_diagnosis" | "order_diagnosis" | "knowledge_qa" | "unknown", "confidence": 0.0-1.0,"reasoning": "判断理由"}
|
||||
2.严格返回字符串格式,禁用markdown格式返回
|
||||
3.只返回json字符串,不包含任何其他解释性文字
|
||||
## 用户当前的问题是:
|
||||
{user_input}
|
||||
`
|
||||
|
||||
prompt = strings.ReplaceAll(prompt, "{user_input}", userInput)
|
||||
|
|
|
|||
|
|
@ -11,8 +11,16 @@ import (
|
|||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Ollama OllamaConfig `mapstructure:"ollama"`
|
||||
Sys SysConfig `mapstructure:"sys"`
|
||||
Tools ToolsConfig `mapstructure:"tools"`
|
||||
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 服务器配置
|
||||
|
|
@ -28,6 +36,27 @@ type OllamaConfig struct {
|
|||
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 工具配置
|
||||
type ToolsConfig struct {
|
||||
Weather ToolConfig `mapstructure:"weather"`
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
package constant
|
||||
|
||||
const ()
|
||||
type ConnStatus int8
|
||||
|
||||
const (
|
||||
ConnStatusClosed ConnStatus = iota
|
||||
ConnStatusNormal
|
||||
ConnStatusIgnore
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ var (
|
|||
SystemError = &BusinessErr{code: "0005", message: "系统错误"}
|
||||
|
||||
SupplierNotFound = &BusinessErr{code: "0006", message: "供应商不存在"}
|
||||
SupplierApiError = &BusinessErr{code: "0007", message: "第三方供应商接口报错"}
|
||||
|
||||
InvalidParam = &BusinessErr{code: InvalidParamCode, message: "无效参数"}
|
||||
SessionNotFound = &BusinessErr{code: "0007", message: "未找到会话信息"}
|
||||
AuthNotFound = &BusinessErr{code: "0008", message: "身份验证失败"}
|
||||
KeyNotFound = &BusinessErr{code: "0009", message: "身份验证失败"}
|
||||
SysNotFound = &BusinessErr{code: "0010", message: "未找到系统信息"}
|
||||
InvalidParam = &BusinessErr{code: InvalidParamCode, message: "无效参数"}
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -41,7 +43,3 @@ func NewBusinessErr(code string, message string) *BusinessErr {
|
|||
func (e *BusinessErr) Wrap(err error) *BusinessErr {
|
||||
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"
|
||||
)
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/gofiber/websocket/v2"
|
||||
)
|
||||
|
||||
// ChatRequest 聊天请求
|
||||
|
|
@ -80,4 +82,5 @@ type Message struct {
|
|||
// RouterService 路由服务接口
|
||||
type RouterService interface {
|
||||
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 (
|
||||
"ai_scheduler/internal/pkg/ollama"
|
||||
|
||||
"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
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/data/constant"
|
||||
"ai_scheduler/internal/entitys"
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
|
@ -40,62 +41,54 @@ func (h *ChatService) ChatFail(c *websocket.Conn, content string) {
|
|||
|
||||
func (h *ChatService) Chat(c *websocket.Conn) {
|
||||
log.Println("客户端已连接")
|
||||
|
||||
defer c.Close()
|
||||
// 循环读取客户端消息
|
||||
for {
|
||||
messageType, msg, err := c.ReadMessage()
|
||||
messageType, message, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("读取错误:", err)
|
||||
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
|
||||
if err := json.Unmarshal(msg, &req); err != nil {
|
||||
log.Println("JSON parse error:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 回显消息给客户端
|
||||
if err := c.WriteMessage(messageType, msg); err != nil {
|
||||
log.Println("写入错误:", err)
|
||||
break
|
||||
err = h.routerService.RouteWithSocket(c, &req)
|
||||
if err != nil {
|
||||
log.Println("处理失败:", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("客户端已断开")
|
||||
//var req entitys.ChatRequest
|
||||
//if err := c.BodyParser(&req); err != nil {
|
||||
// return errors.ParamError
|
||||
//}
|
||||
//
|
||||
//// 转换为服务层请求
|
||||
//serviceReq := &entitys.ChatRequest{
|
||||
// UserInput: req.UserInput,
|
||||
// Caller: req.Caller,
|
||||
// SessionID: req.SessionID,
|
||||
// ChatRequestMeta: entitys.ChatRequestMeta{
|
||||
// Authorization: c.Request().Header(),
|
||||
// },
|
||||
//}
|
||||
//
|
||||
//// 调用路由服务
|
||||
//response, err := h.routerService.Route(c.Request.Context(), serviceReq)
|
||||
//if err != nil {
|
||||
// c.JSON(http.StatusInternalServerError, ChatResponse{
|
||||
// Status: "error",
|
||||
// Message: err.Error(),
|
||||
// })
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//// 转换响应格式
|
||||
//httpResponse := &ChatResponse{
|
||||
// Message: response.Message,
|
||||
// Status: response.Status,
|
||||
// Data: response.Data,
|
||||
// TaskCode: response.TaskCode,
|
||||
//}
|
||||
//
|
||||
//c.JSON(http.StatusOK, httpResponse)
|
||||
}
|
||||
|
||||
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
|
||||
case websocket.BinaryMessage:
|
||||
return msg.([]byte), constant.ConnStatusNormal
|
||||
case websocket.CloseMessage:
|
||||
|
||||
return nil, constant.ConnStatusClosed
|
||||
case websocket.PingMessage:
|
||||
// 可选:回复 Pong
|
||||
c.WriteMessage(websocket.PongMessage, nil)
|
||||
return nil, constant.ConnStatusIgnore
|
||||
case websocket.PongMessage:
|
||||
return nil, constant.ConnStatusIgnore
|
||||
default:
|
||||
return nil, constant.ConnStatusIgnore
|
||||
}
|
||||
return msg.([]byte), constant.ConnStatusIgnore
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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