结构修改

This commit is contained in:
renzhiyuan 2025-09-16 21:31:47 +08:00
parent abb52eb1d3
commit 2bb054940a
53 changed files with 7851 additions and 575 deletions

View File

@ -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,
))
}

View File

@ -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&

19
gen.sh Executable file
View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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)

View File

@ -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"`

View File

@ -1,3 +1,9 @@
package constant
const ()
type ConnStatus int8
const (
ConnStatusClosed ConnStatus = iota
ConnStatusNormal
ConnStatusIgnore
)

View File

@ -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}
}

View File

@ -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))}
}

View File

@ -4,4 +4,4 @@ import (
"github.com/google/wire"
)
var ProviderImpl = wire.NewSet(NewOrderImpl)
var ProviderImpl = wire.NewSet(NewSessionImpl)

View File

@ -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))}
}

View File

@ -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))}
}

View File

@ -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))}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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",
},
})
}
}
}

View File

@ -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,
)
}
}

View File

@ -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()
}
}

View File

@ -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, "")
}
}
}

1
internal/pkg/func.go Normal file
View File

@ -0,0 +1 @@
package pkg

25
internal/pkg/gorm.go Normal file
View File

@ -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
}

View File

@ -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
}
}

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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]
}

View File

@ -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

View File

@ -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)
}

View File

@ -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
}
}
}

View File

@ -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 = %vwant 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 = %vwant nil", err)
}
//验证测试值
if output, isOk := tt.output.(OutputTPointer); isOk {
if *output.Time != target {
t.Errorf("Decode output time = %vwant %v", *output.Time, target)
}
if output.Id != idValue {
t.Errorf("Decode output id = %vwant %v", output.Id, idValue)
}
}
if output, isOk := tt.output.(Output); isOk {
if output.Time != target {
t.Errorf("Decode output time = %vwant %v", output.Time, target)
}
if output.Id != idValue {
t.Errorf("Decode output id = %vwant %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 = %vwant 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 = %vwant nil", err)
}
//验证测试值
switch v := tt.output.(type) {
case Output[int]:
if int64(v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
case Output[*int]:
if int64(*v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
case Output[int32]:
if int64(v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
case Output[*int32]:
if int64(*v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
case Output[int64]:
if int64(v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
case Output[*int64]:
if int64(*v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
case Output[uint]:
if int64(v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
case Output[*uint]:
if int64(*v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
}
})
}
}

View File

@ -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,
)

48
internal/pkg/rds.go Normal file
View File

@ -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
}

View File

@ -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)
}
}
}
}

View File

@ -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
}

View File

@ -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
}

113
tmpl/dataTemp/queryTempl.go Normal file
View File

@ -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
}

35
tmpl/dataTemp/req_page.go Normal file
View File

@ -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
}

View File

@ -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
}

86
tmpl/errcode/common.go Normal file
View File

@ -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: "已存在相同的客户商品授权,请检查后重试"}
)

25
utils/gorm.go Normal file
View File

@ -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
}

10
utils/provider_set.go Normal file
View File

@ -0,0 +1,10 @@
package utils
import (
"github.com/google/wire"
)
var ProviderUtils = wire.NewSet(
NewRdb,
NewGormDb,
)

48
utils/rds.go Normal file
View File

@ -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
}

41
utils/utils_gorm/gorm.go Normal file
View File

@ -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)
}
}
}
}

View File

@ -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
}

View File

@ -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()
}

72
utils/utils_test.go Normal file
View File

@ -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())
}