Compare commits
18 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
342d39a1a0 | |
|
|
8add85cea7 | |
|
|
95623f3802 | |
|
|
45262d8f88 | |
|
|
f958579981 | |
|
|
737fd041a0 | |
|
|
969322a912 | |
|
|
f9fae10c76 | |
|
|
f130e18b8f | |
|
|
5a37aea56f | |
|
|
6bfe26bbf7 | |
|
|
c0629b4038 | |
|
|
bb0f3de20a | |
|
|
c50714e8a0 | |
|
|
942eff81fb | |
|
|
845afe6dac | |
|
|
4c300374cb | |
|
|
5e22f2c2c0 |
|
|
@ -0,0 +1,60 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"git.magicany.cc/black1552/gf-common/log"
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
Type gorm.Dialector
|
||||
Db *gorm.DB
|
||||
err error
|
||||
sqlDb *sql.DB
|
||||
dns = gfile.Join(gfile.Pwd(), "db", "database.db")
|
||||
)
|
||||
|
||||
func init() {
|
||||
if g.IsEmpty(dns) {
|
||||
log.Error("gormDns未配置", "请检查配置文件")
|
||||
return
|
||||
}
|
||||
sqliteInit()
|
||||
Db, err = gorm.Open(Type, &gorm.Config{
|
||||
SkipDefaultTransaction: true,
|
||||
// 命名策略:保持与模型一致,避免字段/表名转换问题
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
SingularTable: true, // 表名禁用复数形式(例如 User 对应 user 表,而非 users)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("数据库连接失败: ", err)
|
||||
return
|
||||
}
|
||||
sqlDb, err = Db.DB()
|
||||
if err != nil {
|
||||
log.Error("获取sqlDb失败", err)
|
||||
return
|
||||
}
|
||||
if err = sqlDb.Ping(); err != nil {
|
||||
log.Error("数据库未正常连接", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func sqliteInit() {
|
||||
if !gfile.Exists(dns) {
|
||||
_, err = gfile.Create(dns)
|
||||
if err != nil {
|
||||
log.Error("创建数据库文件失败: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
Type = sqlite.Open(fmt.Sprintf("%s?cache=shared&mode=rwc&_busy_timeout=10000&_fk=1&_journal=WAL&_sync=FULL", dns))
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
package db
|
||||
44
go.mod
44
go.mod
|
|
@ -1,14 +1,15 @@
|
|||
module git.magicany.cc/black1552/gf-common
|
||||
|
||||
go 1.24.3
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/duke-git/lancet/v2 v2.3.7
|
||||
github.com/eclipse/paho.mqtt.golang v1.5.1
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.3
|
||||
github.com/gogf/gf/contrib/registry/etcd/v2 v2.9.3
|
||||
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.9.3
|
||||
github.com/gogf/gf/v2 v2.9.3
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.10.0
|
||||
github.com/gogf/gf/contrib/registry/etcd/v2 v2.10.0
|
||||
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.10.0
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
github.com/xuri/excelize/v2 v2.9.1
|
||||
|
|
@ -24,13 +25,15 @@ require (
|
|||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.2 // indirect
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.9.3 // indirect
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.10.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
|
|
@ -41,9 +44,12 @@ require (
|
|||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/olekukonko/errors v1.1.0 // indirect
|
||||
github.com/olekukonko/ll v0.0.9 // indirect
|
||||
github.com/olekukonko/tablewriter v1.0.9 // indirect
|
||||
github.com/olekukonko/tablewriter v1.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
|
|
@ -54,21 +60,25 @@ require (
|
|||
go.etcd.io/etcd/client/pkg/v3 v3.5.17 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.17 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a // indirect
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
google.golang.org/protobuf v1.36.7 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.68.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.46.1 // indirect
|
||||
)
|
||||
|
|
|
|||
125
go.sum
125
go.sum
|
|
@ -15,14 +15,20 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/duke-git/lancet/v2 v2.3.7 h1:nnNBA9KyoqwbPm4nFmEFVIbXeAmpqf6IDCH45+HHHNs=
|
||||
github.com/duke-git/lancet/v2 v2.3.7/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=
|
||||
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
|
|
@ -31,28 +37,32 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
|
|||
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
|
||||
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.3 h1:P4jrnp+Vmh3kDeaH/kyHPI6rfoMmQD+sPJa716aMbS0=
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.3/go.mod h1:yEhfx78wgpxUJhH9C9bWJ7I3JLcVCzUg11A4ORYTKeg=
|
||||
github.com/gogf/gf/contrib/registry/etcd/v2 v2.9.3 h1:4ztKAHfwtddPRwxlVRRfLdJMzp42Z+9K5tFHrPPZl1Q=
|
||||
github.com/gogf/gf/contrib/registry/etcd/v2 v2.9.3/go.mod h1:ey99pcs/hSwShOLWjd4mUUmq4S9fLy7elf7SslUAs20=
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.9.3 h1:7f+55KmwJzW0Kmam+N3VrLMlMkCTc5LTPDaZeElCLd0=
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.9.3/go.mod h1:kOYsqBlbf6pElgfLyIEFN6FpXnSSdgkV4w+7+w/78do=
|
||||
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.9.3 h1:HoUu3a7t0iLI0/GbVP46rPHbrfBlKa6rfHsnCvry4+4=
|
||||
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.9.3/go.mod h1:F5v+4GumRPIFZsAiz6H5QNoOiPM7cluMCvGF0zbzsGE=
|
||||
github.com/gogf/gf/v2 v2.9.3 h1:qjN4s55FfUzxZ1AE8vUHNDX3V0eIOUGXhF2DjRTVZQ4=
|
||||
github.com/gogf/gf/v2 v2.9.3/go.mod h1:w6rcfD13SmO7FKI80k9LSLiSMGqpMYp50Nfkrrc2sEE=
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.10.0 h1:UvqxwinkelKxwdwnKUfdy51/ls4RL7MCeJqAZOVAy0I=
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.10.0/go.mod h1:6v7oGBF9wv59WERJIOJxXmLhkUcxwON3tPYW3AZ7wbY=
|
||||
github.com/gogf/gf/contrib/registry/etcd/v2 v2.10.0 h1:yOKc0YroBwCwyBHezJ1M6ISFfrLl7ZnRKsCS1dhF1DA=
|
||||
github.com/gogf/gf/contrib/registry/etcd/v2 v2.10.0/go.mod h1:ezkHf1r7YxkFYis7Y1807D1tr+3nXolfL8IsY816Cuw=
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.10.0 h1:yy93PGhz2CE3PpF2JSGa2MXJWd4JNHYC5l5NEFCFRV4=
|
||||
github.com/gogf/gf/contrib/registry/file/v2 v2.10.0/go.mod h1:ZzGWiTbQ9nAmDynrmzaRJlqYHD/F3MeMwCvHrkv/LP4=
|
||||
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.10.0 h1:MWHgX/h/62+Oo1Jbeq8DrXbEWSOZxskpBBm3cFAV4/Q=
|
||||
github.com/gogf/gf/contrib/rpc/grpcx/v2 v2.10.0/go.mod h1:zGZdjS08IqiSGhKfy69/qgcYKUYUL/VsOx5fuT++TVE=
|
||||
github.com/gogf/gf/v2 v2.10.0 h1:rzDROlyqGMe/eM6dCalSR8dZOuMIdLhmxKSH1DGhbFs=
|
||||
github.com/gogf/gf/v2 v2.10.0/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
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/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
|
||||
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
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=
|
||||
|
|
@ -74,18 +84,23 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
|||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
||||
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
||||
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||
github.com/olekukonko/tablewriter v1.0.9 h1:XGwRsYLC2bY7bNd93Dk51bcPZksWZmLYuaTHR0FqfL8=
|
||||
github.com/olekukonko/tablewriter v1.0.9/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
|
||||
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
|
|
@ -94,13 +109,13 @@ github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTK
|
|||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.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/tiendc/go-deepcopy v1.6.0 h1:0UtfV/imoCwlLxVsyfUd4hNHnB3drXsfle+wzSCA5Wo=
|
||||
github.com/tiendc/go-deepcopy v1.6.0/go.mod h1:toXoeQoUqXOOS/X4sKuiAoSk6elIdqc0pN7MTgOOo2I=
|
||||
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
||||
|
|
@ -120,16 +135,16 @@ go.etcd.io/etcd/client/v3 v3.5.17 h1:o48sINNeWz5+pjy/Z0+HKpj/xSnBkuVhVvXkjEXbqZY
|
|||
go.etcd.io/etcd/client/v3 v3.5.17/go.mod h1:j2d4eXTHWkT2ClBgnnEPm/Wuu7jsqku41v9DZ3OtjQo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
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.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
|
|
@ -144,14 +159,16 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a h1:4iLhBPcpqFmylhnkbY3W0ONLUYYkDAW9xMFLfxgsvCw=
|
||||
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
||||
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
|
@ -164,8 +181,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
@ -173,19 +190,21 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
@ -198,8 +217,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:
|
|||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
|
@ -217,3 +236,31 @@ 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.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.30.2 h1:4yPaaq9dXYXZ2V8s1UgrC3KIj580l2N4ClrLwnbv2so=
|
||||
modernc.org/ccgo/v4 v4.30.2/go.mod h1:yZMnhWEdW0qw3EtCndG1+ldRrVGS+bIwyWmAWzS0XEw=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.68.0 h1:PJ5ikFOV5pwpW+VqCK1hKJuEWsonkIJhhIXyuF/91pQ=
|
||||
modernc.org/libc v1.68.0/go.mod h1:NnKCYeoYgsEqnY3PgvNgAeaJnso968ygU8Z0DxjoEc0=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
||||
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
package log
|
||||
181
log/log.go
181
log/log.go
|
|
@ -5,8 +5,11 @@ import (
|
|||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
|
@ -14,46 +17,184 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
logPath string
|
||||
sysLog *log.Logger
|
||||
logPath string
|
||||
sysLog *log.Logger
|
||||
filePath string
|
||||
currentDate string // 当前日志文件对应的日期
|
||||
fileLogger *lumberjack.Logger
|
||||
)
|
||||
|
||||
func init() {
|
||||
const (
|
||||
Reset = "\033[0m"
|
||||
Red = "\033[31m"
|
||||
Green = "\033[32m"
|
||||
Yellow = "\033[33m"
|
||||
Blue = "\033[34m"
|
||||
Purple = "\033[35m"
|
||||
Cyan = "\033[36m"
|
||||
)
|
||||
|
||||
// 正则表达式匹配 ANSI 颜色码
|
||||
var ansiColorRegex = regexp.MustCompile(`\x1b\[[0-9;]*m`)
|
||||
|
||||
// stripAnsiColors 去除字符串中的 ANSI 颜色码
|
||||
func stripAnsiColors(s string) string {
|
||||
return ansiColorRegex.ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
// logWriter 自定义 writer,用于分别处理控制台和文件输出
|
||||
type logWriter struct {
|
||||
console io.Writer
|
||||
file io.Writer
|
||||
}
|
||||
|
||||
func (w *logWriter) Write(p []byte) (n int, err error) {
|
||||
// 控制台输出保留颜色
|
||||
_, err = w.console.Write(p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// 文件输出去除颜色码
|
||||
colorless := stripAnsiColors(string(p))
|
||||
_, err = w.file.Write([]byte(colorless))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// cleanOldLogs 删除指定天数之前的日志文件(包括主文件和备份文件)
|
||||
func cleanOldLogs(days int) {
|
||||
if !gfile.Exists(logPath) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取所有日志文件
|
||||
files, err := gfile.DirNames(logPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
for _, file := range files {
|
||||
path := filepath.Join(logPath, file)
|
||||
if gfile.IsDir(path) {
|
||||
continue
|
||||
}
|
||||
|
||||
var dateStr string
|
||||
var matched bool
|
||||
|
||||
// 匹配主日志文件格式:log-YYYY-MM-DD.log
|
||||
if strings.HasPrefix(file, "log-") && strings.HasSuffix(file, ".log") {
|
||||
// 检查是否是主文件(没有备份时间戳)
|
||||
// 主文件格式:log-2026-04-25.log
|
||||
// 备份文件格式:log-2026-04-25-2026-04-25T10-30-45.123.log
|
||||
parts := strings.Split(strings.TrimSuffix(file, ".log"), "-")
|
||||
if len(parts) == 4 {
|
||||
// 主文件:log-YYYY-MM-DD
|
||||
dateStr = parts[1] + "-" + parts[2] + "-" + parts[3]
|
||||
matched = true
|
||||
} else if len(parts) > 4 {
|
||||
// 备份文件:log-YYYY-MM-DD-YYYY-MM-DDTHH-MM-SS.mmm
|
||||
// 提取主日期部分(第一个日期)
|
||||
dateStr = parts[1] + "-" + parts[2] + "-" + parts[3]
|
||||
matched = true
|
||||
}
|
||||
}
|
||||
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
|
||||
// 解析日期
|
||||
fileTime, err := time.Parse("2006-01-02", dateStr)
|
||||
if err != nil {
|
||||
continue // 日期格式不正确,跳过
|
||||
}
|
||||
|
||||
// 计算文件年龄
|
||||
tage := now.Sub(fileTime)
|
||||
if tage.Hours() > float64(days*24) {
|
||||
// 超过指定天数,删除文件
|
||||
err = os.Remove(path)
|
||||
if err == nil {
|
||||
Info(fmt.Sprintf("已删除过期日志文件:%s", file))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkAndRotateLogFile 检查是否需要切换日志文件(跨天时)
|
||||
func checkAndRotateLogFile() {
|
||||
date := gtime.Date()
|
||||
if currentDate != date {
|
||||
// 日期变化,需要重新初始化
|
||||
currentDate = date
|
||||
filePath = gfile.Join(logPath, fmt.Sprintf("log-%s.log", currentDate))
|
||||
fileLogger = &lumberjack.Logger{
|
||||
Filename: filePath,
|
||||
MaxSize: 2, // 单个文件最大 10MB
|
||||
MaxBackups: 5, // 最多保留 5 个备份
|
||||
MaxAge: 30, // 保留 30 天
|
||||
Compress: false, // 启用压缩
|
||||
}
|
||||
// 创建新的 writer
|
||||
multiWriter := &logWriter{
|
||||
console: os.Stdout,
|
||||
file: fileLogger,
|
||||
}
|
||||
sysLog = log.New(multiWriter, "", 0)
|
||||
|
||||
// 清理 30 天前的旧日志
|
||||
cleanOldLogs(30)
|
||||
}
|
||||
}
|
||||
|
||||
func Init() {
|
||||
if sysLog != nil {
|
||||
checkAndRotateLogFile() // 检查是否需要切换文件
|
||||
return
|
||||
}
|
||||
logPath = gfile.Join(gfile.Pwd(), "logs")
|
||||
filePath := gfile.Join(logPath, fmt.Sprintf("log-%s.log", gtime.Date()))
|
||||
fileLogger := &lumberjack.Logger{
|
||||
currentDate = gtime.Date()
|
||||
filePath = gfile.Join(logPath, fmt.Sprintf("log-%s.log", currentDate))
|
||||
fileLogger = &lumberjack.Logger{
|
||||
Filename: filePath,
|
||||
MaxSize: 1, // 单个文件最大 10MB
|
||||
MaxSize: 2, // 单个文件最大 10MB
|
||||
MaxBackups: 5, // 最多保留 5 个备份
|
||||
MaxAge: 30, // 保留 30 天
|
||||
Compress: false, // 启用压缩
|
||||
}
|
||||
// 创建 MultiWriter 实现同时输出到文件和终端
|
||||
multiWriter := io.MultiWriter(fileLogger, os.Stdout)
|
||||
sysLog = log.New(multiWriter, "", log.LstdFlags)
|
||||
// 使用自定义 writer 实现控制台带颜色、文件无颜色的输出
|
||||
multiWriter := &logWriter{
|
||||
console: os.Stdout,
|
||||
file: fileLogger,
|
||||
}
|
||||
sysLog = log.New(multiWriter, "", 0)
|
||||
|
||||
// 启动时清理 30 天前的旧日志
|
||||
cleanOldLogs(30)
|
||||
}
|
||||
|
||||
// Info 打印普通信息日志
|
||||
func Info(v ...any) {
|
||||
sysLog.SetPrefix("[INFO] ")
|
||||
Init()
|
||||
sysLog.SetPrefix(fmt.Sprintf("[%s] %s[INFO]%s ", time.Now().Format("2006-01-02 15:04:05"), Green, Reset))
|
||||
sysLog.Println(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
// Error 打印错误日志
|
||||
func Error(v ...any) {
|
||||
sysLog.SetPrefix("[ERROR] ")
|
||||
Init()
|
||||
sysLog.SetPrefix(fmt.Sprintf("[%s] %s[ERROR]%s ", time.Now().Format("2006-01-02 15:04:05"), Red, Reset))
|
||||
msg := fmt.Sprint(v...)
|
||||
sysLog.Println(msg, strings.TrimSpace(string(debug.Stack())))
|
||||
}
|
||||
|
||||
// Warn 打印警告日志
|
||||
func Warn(v ...any) {
|
||||
sysLog.SetPrefix("[WARN] ")
|
||||
Init()
|
||||
sysLog.SetPrefix(fmt.Sprintf("[%s] %s[WARN]%s ", time.Now().Format("2006-01-02 15:04:05"), Yellow, Reset))
|
||||
sysLog.Println(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
// Debug 打印调试日志
|
||||
func Debug(v ...any) {
|
||||
sysLog.SetPrefix("[DEBUG] ")
|
||||
Init()
|
||||
sysLog.SetPrefix(fmt.Sprintf("[%s] %s[DEBUG]%s ", time.Now().Format("2006-01-02 15:04:05"), Blue, Reset))
|
||||
sysLog.Println(fmt.Sprint(v...))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package pool
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ConnType 连接类型
|
||||
type ConnType string
|
||||
|
||||
const (
|
||||
ConnTypeWebSocket ConnType = "websocket"
|
||||
ConnTypeTCP ConnType = "tcp"
|
||||
)
|
||||
|
||||
// ConnectionInfo 连接信息
|
||||
type ConnectionInfo struct {
|
||||
ID string `json:"id" gorm:"primaryKey"`
|
||||
Type ConnType `json:"type" gorm:"index"`
|
||||
Address string `json:"address"`
|
||||
IsActive bool `json:"isActive" gorm:"index"`
|
||||
LastUsed time.Time `json:"lastUsed"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
// 额外的连接数据,根据不同类型存储不同的信息
|
||||
Data map[string]interface{} `json:"data" gorm:"-"`
|
||||
}
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
package pool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.magicany.cc/black1552/gf-common/db"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SQLitePool SQLite连接池
|
||||
type SQLitePool struct {
|
||||
db *gorm.DB
|
||||
mutex sync.RWMutex
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
// 内存缓存,提高并发性能
|
||||
cache map[string]*ConnectionInfo
|
||||
}
|
||||
|
||||
// NewSQLitePool 创建SQLite连接池
|
||||
func NewSQLitePool() (*SQLitePool, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// 检查数据库连接是否正常
|
||||
if db.Db == nil {
|
||||
return nil, fmt.Errorf("database connection is not initialized")
|
||||
}
|
||||
|
||||
// 自动迁移ConnectionInfo模型
|
||||
err := db.Db.AutoMigrate(&ConnectionInfo{})
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, fmt.Errorf("failed to migrate connection info model: %w", err)
|
||||
}
|
||||
|
||||
return &SQLitePool{
|
||||
db: db.Db,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
cache: make(map[string]*ConnectionInfo),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close 关闭连接池
|
||||
func (p *SQLitePool) Close() error {
|
||||
p.cancel()
|
||||
// SQLite连接由db包管理,不需要在这里关闭
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add 添加连接
|
||||
func (p *SQLitePool) Add(conn *ConnectionInfo) error {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
// 存储到SQLite
|
||||
result := p.db.Create(conn)
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("failed to store connection: %w", result.Error)
|
||||
}
|
||||
|
||||
// 更新内存缓存
|
||||
p.cache[conn.ID] = conn
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get 获取连接
|
||||
func (p *SQLitePool) Get(connID string) (*ConnectionInfo, error) {
|
||||
p.mutex.RLock()
|
||||
// 先从内存缓存获取
|
||||
if conn, ok := p.cache[connID]; ok {
|
||||
p.mutex.RUnlock()
|
||||
return conn, nil
|
||||
}
|
||||
p.mutex.RUnlock()
|
||||
|
||||
// 从SQLite获取
|
||||
var connInfo ConnectionInfo
|
||||
result := p.db.First(&connInfo, "id = ?", connID)
|
||||
if result.Error != nil {
|
||||
if result.Error == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get connection: %w", result.Error)
|
||||
}
|
||||
|
||||
// 更新内存缓存
|
||||
p.mutex.Lock()
|
||||
p.cache[connID] = &connInfo
|
||||
p.mutex.Unlock()
|
||||
|
||||
return &connInfo, nil
|
||||
}
|
||||
|
||||
// Remove 移除连接
|
||||
func (p *SQLitePool) Remove(connID string) error {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
// 从SQLite删除
|
||||
result := p.db.Delete(&ConnectionInfo{}, "id = ?", connID)
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("failed to remove connection: %w", result.Error)
|
||||
}
|
||||
|
||||
// 从内存缓存删除
|
||||
delete(p.cache, connID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update 更新连接信息
|
||||
func (p *SQLitePool) Update(conn *ConnectionInfo) error {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
// 存储到SQLite
|
||||
result := p.db.Save(conn)
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("failed to update connection: %w", result.Error)
|
||||
}
|
||||
|
||||
// 更新内存缓存
|
||||
p.cache[conn.ID] = conn
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAll 获取所有连接
|
||||
func (p *SQLitePool) GetAll() ([]*ConnectionInfo, error) {
|
||||
p.mutex.RLock()
|
||||
// 如果内存缓存不为空,直接返回缓存
|
||||
if len(p.cache) > 0 {
|
||||
conns := make([]*ConnectionInfo, 0, len(p.cache))
|
||||
for _, conn := range p.cache {
|
||||
conns = append(conns, conn)
|
||||
}
|
||||
p.mutex.RUnlock()
|
||||
return conns, nil
|
||||
}
|
||||
p.mutex.RUnlock()
|
||||
|
||||
// 从SQLite获取所有连接
|
||||
var conns []*ConnectionInfo
|
||||
result := p.db.Find(&conns)
|
||||
if result.Error != nil {
|
||||
return nil, fmt.Errorf("failed to get all connections: %w", result.Error)
|
||||
}
|
||||
|
||||
// 更新内存缓存
|
||||
p.mutex.Lock()
|
||||
for _, conn := range conns {
|
||||
p.cache[conn.ID] = conn
|
||||
}
|
||||
p.mutex.Unlock()
|
||||
|
||||
return conns, nil
|
||||
}
|
||||
|
||||
// GetByType 根据类型获取连接
|
||||
func (p *SQLitePool) GetByType(connType ConnType) ([]*ConnectionInfo, error) {
|
||||
allConns, err := p.GetAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var filtered []*ConnectionInfo
|
||||
for _, conn := range allConns {
|
||||
if conn.Type == connType {
|
||||
filtered = append(filtered, conn)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
// Count 获取连接数量
|
||||
func (p *SQLitePool) Count() (int, error) {
|
||||
p.mutex.RLock()
|
||||
// 如果内存缓存不为空,直接返回缓存大小
|
||||
if len(p.cache) > 0 {
|
||||
count := len(p.cache)
|
||||
p.mutex.RUnlock()
|
||||
return count, nil
|
||||
}
|
||||
p.mutex.RUnlock()
|
||||
|
||||
// 从SQLite统计数量
|
||||
var count int64
|
||||
result := p.db.Model(&ConnectionInfo{}).Count(&count)
|
||||
if result.Error != nil {
|
||||
return 0, fmt.Errorf("failed to count connections: %w", result.Error)
|
||||
}
|
||||
|
||||
return int(count), nil
|
||||
}
|
||||
|
||||
// GetAllConnIDs 获取所有在线连接的ID列表
|
||||
func (p *SQLitePool) GetAllConnIDs() ([]string, error) {
|
||||
p.mutex.RLock()
|
||||
// 如果内存缓存不为空,从缓存中提取在线连接的ID
|
||||
if len(p.cache) > 0 {
|
||||
ids := make([]string, 0, len(p.cache))
|
||||
for id, conn := range p.cache {
|
||||
if conn.IsActive {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
p.mutex.RUnlock()
|
||||
return ids, nil
|
||||
}
|
||||
p.mutex.RUnlock()
|
||||
|
||||
// 从SQLite获取所有在线连接的ID
|
||||
var conns []*ConnectionInfo
|
||||
result := p.db.Where("is_active = ?", true).Find(&conns)
|
||||
if result.Error != nil {
|
||||
return nil, fmt.Errorf("failed to get all connection IDs: %w", result.Error)
|
||||
}
|
||||
|
||||
ids := make([]string, 0, len(conns))
|
||||
for _, conn := range conns {
|
||||
ids = append(ids, conn.ID)
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// CleanupInactive 清理不活跃的连接
|
||||
func (p *SQLitePool) CleanupInactive(duration time.Duration) error {
|
||||
allConns, err := p.GetAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
for _, conn := range allConns {
|
||||
if !conn.IsActive || now.Sub(conn.LastUsed) > duration {
|
||||
if err := p.Remove(conn.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"git.magicany.cc/black1552/gf-common/log"
|
||||
"github.com/gogf/gf/v2/net/gclient"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// hasProtocol 检查字符串是否包含协议前缀
|
||||
func hasProtocol(s string) bool {
|
||||
return strings.HasPrefix(s, "http://") ||
|
||||
strings.HasPrefix(s, "https://") ||
|
||||
strings.HasPrefix(s, "ws://") ||
|
||||
strings.HasPrefix(s, "wss://")
|
||||
}
|
||||
|
||||
// BuildRequest 反向代理请求到指定主机
|
||||
// 自动支持所有 HTTP 方法及 WebSocket 连接
|
||||
func BuildRequest(r *ghttp.Request, host string) {
|
||||
if gstr.Contains(r.RequestURI, "/ws") {
|
||||
proxyWebSocket(r, host)
|
||||
return
|
||||
}
|
||||
client := gclient.New()
|
||||
// 构建目标URL而不是直接复制RequestURI
|
||||
targetURL := host + r.URL.Path
|
||||
if r.URL.RawQuery != "" {
|
||||
targetURL += "?" + r.URL.RawQuery
|
||||
}
|
||||
// 复制请求头
|
||||
for key, values := range r.Header {
|
||||
for _, value := range values {
|
||||
client.SetHeader(key, value)
|
||||
}
|
||||
}
|
||||
response, err := client.DoRequest(gctx.New(), r.Method, targetURL, r.GetBody())
|
||||
if err != nil {
|
||||
log.Error(gctx.New(), "request error:", err)
|
||||
panic(fmt.Sprintf("request error: %v", err))
|
||||
}
|
||||
defer response.Body.Close()
|
||||
// 读取响应体
|
||||
respBody, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
log.Error(gctx.New(), "read response body error:", err)
|
||||
panic(fmt.Sprintf("read response body error: %v", err))
|
||||
}
|
||||
|
||||
// 复制响应头
|
||||
for key, values := range response.Header {
|
||||
for _, value := range values {
|
||||
r.Response.Header().Add(key, value)
|
||||
}
|
||||
}
|
||||
// 设置响应状态码并写入响应体
|
||||
r.Response.Status = response.StatusCode
|
||||
r.Response.Write(respBody)
|
||||
}
|
||||
|
||||
// proxyWebSocket 处理 WebSocket 连接的代理
|
||||
func proxyWebSocket(r *ghttp.Request, targetHost string) {
|
||||
// 解析目标主机 URL
|
||||
targetURL, err := url.Parse(targetHost)
|
||||
if err != nil {
|
||||
log.Error(gctx.New(), "parse target host error:", err)
|
||||
r.Response.WriteStatus(http.StatusInternalServerError)
|
||||
r.Response.Write([]byte("Invalid target host"))
|
||||
return
|
||||
}
|
||||
|
||||
// 创建反向代理
|
||||
proxy := httputil.NewSingleHostReverseProxy(targetURL)
|
||||
|
||||
// 修改请求 URL,保留原始路径和查询参数
|
||||
r.URL.Scheme = targetURL.Scheme
|
||||
r.URL.Host = targetURL.Host
|
||||
log.Info(gctx.New(), r.GetBodyString())
|
||||
// 处理 WebSocket 连接
|
||||
proxy.ServeHTTP(r.Response.Writer, r.Request)
|
||||
}
|
||||
|
|
@ -19,7 +19,10 @@ func NewWs() *Manager {
|
|||
}
|
||||
|
||||
// 2. 创建管理器
|
||||
m := NewManager(customConfig)
|
||||
m, err := NewManager(customConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create manager: %v", err)
|
||||
}
|
||||
|
||||
// 3. 覆盖业务回调(核心:自定义消息处理逻辑)
|
||||
// 连接建立回调
|
||||
|
|
@ -71,3 +74,35 @@ func main() {
|
|||
log.Println("WebSocket服务启动:http://localhost:8080/ws")
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
}
|
||||
|
||||
// TestWebSocket 测试WebSocket连接
|
||||
func TestWebSocket() {
|
||||
log.Println("=== 测试WebSocket连接 ===")
|
||||
log.Println("1. 创建WebSocket管理器")
|
||||
m, err := NewManager(DefaultConfig())
|
||||
if err != nil {
|
||||
log.Fatalf("创建管理器失败:%v", err)
|
||||
}
|
||||
log.Println("2. 管理器创建成功")
|
||||
log.Println("3. 获取在线连接数")
|
||||
count, err := m.sqlitePool.Count()
|
||||
if err != nil {
|
||||
log.Printf("获取在线连接数失败:%v", err)
|
||||
} else {
|
||||
log.Printf("当前在线连接数:%d", count)
|
||||
}
|
||||
log.Println("4. 获取所有在线连接ID")
|
||||
connIDs, err := m.GetAllConnIDs()
|
||||
if err != nil {
|
||||
log.Printf("获取在线连接ID失败:%v", err)
|
||||
} else {
|
||||
log.Printf("在线连接ID:%v", connIDs)
|
||||
}
|
||||
log.Println("5. 关闭管理器")
|
||||
if err := m.Close(); err != nil {
|
||||
log.Printf("关闭管理器失败:%v", err)
|
||||
} else {
|
||||
log.Println("管理器关闭成功")
|
||||
}
|
||||
log.Println("=== WebSocket测试完成 ===")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"git.magicany.cc/black1552/gf-common/pool"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
|
@ -92,7 +93,8 @@ type Connection struct {
|
|||
type Manager struct {
|
||||
config *Config // 配置
|
||||
upgrader *websocket.Upgrader // HTTP升级器
|
||||
connections map[string]*Connection // 所有在线连接(connID -> Connection)
|
||||
connections map[string]*Connection // 内存中的连接(connID -> Connection)
|
||||
sqlitePool *pool.SQLitePool // SQLite连接池
|
||||
mutex sync.RWMutex // 读写锁(保护connections)
|
||||
// 业务回调:收到消息时触发(用户自定义处理逻辑)
|
||||
OnMessage func(connID string, msgType int, data any)
|
||||
|
|
@ -148,16 +150,16 @@ func (c *Config) Merge(other *Config) *Config {
|
|||
}
|
||||
|
||||
// NewManager 创建连接管理器
|
||||
func NewManager(config *Config) *Manager {
|
||||
func NewManager(config *Config) (*Manager, error) {
|
||||
defaultConfig := DefaultConfig()
|
||||
finalConfig := defaultConfig.Merge(config)
|
||||
// 初始化升级器
|
||||
upgrader := &websocket.Upgrader{
|
||||
ReadBufferSize: config.ReadBufferSize,
|
||||
WriteBufferSize: config.WriteBufferSize,
|
||||
ReadBufferSize: finalConfig.ReadBufferSize,
|
||||
WriteBufferSize: finalConfig.WriteBufferSize,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
// 跨域检查
|
||||
if config.AllowAllOrigins {
|
||||
if finalConfig.AllowAllOrigins {
|
||||
return true
|
||||
}
|
||||
origin := r.Header.Get("Origin")
|
||||
|
|
@ -170,10 +172,17 @@ func NewManager(config *Config) *Manager {
|
|||
},
|
||||
}
|
||||
|
||||
// 初始化SQLite连接池
|
||||
sqlitePool, err := pool.NewSQLitePool()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create sqlite pool: %w", err)
|
||||
}
|
||||
|
||||
return &Manager{
|
||||
config: finalConfig,
|
||||
upgrader: upgrader,
|
||||
connections: make(map[string]*Connection),
|
||||
sqlitePool: sqlitePool,
|
||||
mutex: sync.RWMutex{},
|
||||
// 默认回调(用户可覆盖)
|
||||
OnMessage: func(connID string, msgType int, data any) {
|
||||
|
|
@ -185,7 +194,7 @@ func NewManager(config *Config) *Manager {
|
|||
OnDisconnect: func(connID string, err error) {
|
||||
log.Printf("[默认回调] 连接[%s]已关闭:%v", connID, err)
|
||||
},
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Upgrade HTTP升级为WebSocket连接
|
||||
|
|
@ -237,6 +246,24 @@ func (m *Manager) Upgrade(w http.ResponseWriter, r *http.Request, connID string)
|
|||
m.connections[connID] = wsConn
|
||||
m.mutex.Unlock()
|
||||
|
||||
// 存储到SQLite
|
||||
connInfo := &pool.ConnectionInfo{
|
||||
ID: connID,
|
||||
Type: pool.ConnTypeWebSocket,
|
||||
Address: r.RemoteAddr,
|
||||
IsActive: true,
|
||||
LastUsed: time.Now(),
|
||||
CreatedAt: time.Now(),
|
||||
Data: map[string]interface{}{
|
||||
"origin": r.Header.Get("Origin"),
|
||||
"userAgent": r.Header.Get("User-Agent"),
|
||||
},
|
||||
}
|
||||
if err := m.sqlitePool.Add(connInfo); err != nil {
|
||||
log.Printf("[错误] 存储连接到SQLite失败:%v", err)
|
||||
// 不影响连接建立,仅记录错误
|
||||
}
|
||||
|
||||
// 触发连接建立回调
|
||||
m.OnConnect(connID)
|
||||
|
||||
|
|
@ -282,6 +309,18 @@ func (c *Connection) ReadPump() {
|
|||
return
|
||||
}
|
||||
|
||||
// 更新最后使用时间
|
||||
now := time.Now()
|
||||
// 从SQLite获取连接信息并更新
|
||||
connInfo, err := c.manager.sqlitePool.Get(c.connID)
|
||||
if err == nil && connInfo != nil {
|
||||
connInfo.LastUsed = now
|
||||
if err := c.manager.sqlitePool.Update(connInfo); err != nil {
|
||||
log.Printf("[错误] 更新SQLite连接信息失败:%v", err)
|
||||
// 不影响消息处理,仅记录错误
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试解析JSON格式的心跳消息(精准判断,替代包含判断)
|
||||
isHeartbeat := false
|
||||
// 先尝试解析为JSON对象
|
||||
|
|
@ -369,6 +408,19 @@ func (c *Connection) Send(data []byte) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("发送消息失败:%w", err)
|
||||
}
|
||||
|
||||
// 更新最后使用时间
|
||||
now := time.Now()
|
||||
// 从SQLite获取连接信息并更新
|
||||
connInfo, err := c.manager.sqlitePool.Get(c.connID)
|
||||
if err == nil && connInfo != nil {
|
||||
connInfo.LastUsed = now
|
||||
if err := c.manager.sqlitePool.Update(connInfo); err != nil {
|
||||
log.Printf("[错误] 更新SQLite连接信息失败:%v", err)
|
||||
// 不影响消息发送,仅记录错误
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -394,6 +446,12 @@ func (c *Connection) Close(err error) {
|
|||
delete(c.manager.connections, c.connID)
|
||||
c.manager.mutex.Unlock()
|
||||
|
||||
// 从SQLite移除
|
||||
if err := c.manager.sqlitePool.Remove(c.connID); err != nil {
|
||||
log.Printf("[错误] 从SQLite移除连接失败:%v", err)
|
||||
// 不影响连接关闭,仅记录错误
|
||||
}
|
||||
|
||||
// 触发断开回调
|
||||
c.manager.OnDisconnect(c.connID, err)
|
||||
|
||||
|
|
@ -462,12 +520,18 @@ func (m *Manager) GetAllConn() map[string]*Connection {
|
|||
return connCopy
|
||||
}
|
||||
|
||||
// GetConn 获取指定连接
|
||||
func (m *Manager) GetConn(connID string) *Connection {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
return m.connections[connID]
|
||||
}
|
||||
|
||||
// GetAllConnIDs 获取所有在线连接的ID列表
|
||||
func (m *Manager) GetAllConnIDs() ([]string, error) {
|
||||
return m.sqlitePool.GetAllConnIDs()
|
||||
}
|
||||
|
||||
// CloseAll 关闭所有连接
|
||||
func (m *Manager) CloseAll() {
|
||||
m.mutex.RLock()
|
||||
|
|
@ -486,3 +550,14 @@ func (m *Manager) CloseAll() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close 关闭管理器,清理资源
|
||||
func (m *Manager) Close() error {
|
||||
// 关闭所有连接
|
||||
m.CloseAll()
|
||||
// 关闭SQLite连接池
|
||||
if m.sqlitePool != nil {
|
||||
return m.sqlitePool.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,11 @@ func Example() {
|
|||
}
|
||||
|
||||
// 创建TCP服务器
|
||||
server := NewTCPServer("0.0.0.0:8888", config)
|
||||
server, err := NewTCPServer("0.0.0.0:8888", config)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create server: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 设置消息处理函数
|
||||
server.SetMessageHandler(func(conn *TcpConnection, msg *TcpMessage) error {
|
||||
|
|
@ -45,3 +49,48 @@ func Example() {
|
|||
|
||||
fmt.Println("TCP server stopped.")
|
||||
}
|
||||
|
||||
// TestTCP 测试TCP连接
|
||||
func TestTCP() {
|
||||
fmt.Println("=== 测试TCP连接 ===")
|
||||
fmt.Println("1. 创建TCP服务器配置")
|
||||
config := &TcpPoolConfig{
|
||||
BufferSize: 2048,
|
||||
MaxConnections: 100000,
|
||||
ConnectTimeout: time.Second * 5,
|
||||
ReadTimeout: time.Second * 30,
|
||||
WriteTimeout: time.Second * 10,
|
||||
MaxIdleTime: time.Minute * 5,
|
||||
}
|
||||
fmt.Println("2. 创建TCP服务器")
|
||||
server, err := NewTCPServer("0.0.0.0:8888", config)
|
||||
if err != nil {
|
||||
fmt.Printf("创建服务器失败:%v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("3. 服务器创建成功")
|
||||
fmt.Println("4. 获取在线连接数")
|
||||
count := server.Connection.Count()
|
||||
fmt.Printf("当前在线连接数:%d\n", count)
|
||||
fmt.Println("5. 获取所有在线连接ID")
|
||||
connIDs, err := server.GetAllConnIDs()
|
||||
if err != nil {
|
||||
fmt.Printf("获取在线连接ID失败:%v\n", err)
|
||||
} else {
|
||||
fmt.Printf("在线连接ID:%v\n", connIDs)
|
||||
}
|
||||
fmt.Println("6. 启动服务器")
|
||||
if err := server.Start(); err != nil {
|
||||
fmt.Printf("启动服务器失败:%v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("7. 服务器启动成功,运行2秒后停止")
|
||||
time.Sleep(time.Second * 2)
|
||||
fmt.Println("8. 停止服务器")
|
||||
if err := server.Stop(); err != nil {
|
||||
fmt.Printf("停止服务器失败:%v\n", err)
|
||||
} else {
|
||||
fmt.Println("服务器停止成功")
|
||||
}
|
||||
fmt.Println("=== TCP测试完成 ===")
|
||||
}
|
||||
|
|
|
|||
163
tcp/tcp.go
163
tcp/tcp.go
|
|
@ -2,10 +2,12 @@ package tcp
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.magicany.cc/black1552/gf-common/pool"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
|
|
@ -32,18 +34,26 @@ type TCPServer struct {
|
|||
// ConnectionPool 连接池结构
|
||||
type ConnectionPool struct {
|
||||
connections map[string]*TcpConnection
|
||||
sqlitePool *pool.SQLitePool
|
||||
mutex sync.RWMutex
|
||||
config *TcpPoolConfig
|
||||
logger *glog.Logger
|
||||
}
|
||||
|
||||
// NewTCPServer 创建一个新的TCP服务器
|
||||
func NewTCPServer(address string, config *TcpPoolConfig) *TCPServer {
|
||||
func NewTCPServer(address string, config *TcpPoolConfig) (*TCPServer, error) {
|
||||
logger := g.Log(address)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// 初始化SQLite连接池
|
||||
sqlitePool, err := pool.NewSQLitePool()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create sqlite pool: %w", err)
|
||||
}
|
||||
|
||||
pool := &ConnectionPool{
|
||||
connections: make(map[string]*TcpConnection),
|
||||
sqlitePool: sqlitePool,
|
||||
config: config,
|
||||
logger: logger,
|
||||
}
|
||||
|
|
@ -58,7 +68,7 @@ func NewTCPServer(address string, config *TcpPoolConfig) *TCPServer {
|
|||
}
|
||||
|
||||
server.Listener = gtcp.NewServer(address, server.handleConnection)
|
||||
return server
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// SetMessageHandler 设置消息处理函数
|
||||
|
|
@ -86,6 +96,11 @@ func (s *TCPServer) Stop() error {
|
|||
s.Listener.Close()
|
||||
s.wg.Wait()
|
||||
s.Connection.Clear()
|
||||
// 关闭SQLite连接池
|
||||
if err := s.Connection.sqlitePool.Close(); err != nil {
|
||||
s.Logger.Error(s.ctx, fmt.Sprintf("Failed to close SQLite pool: %v", err))
|
||||
// 不影响服务器停止,仅记录错误
|
||||
}
|
||||
s.Logger.Info(s.ctx, "TCP server stopped")
|
||||
return nil
|
||||
}
|
||||
|
|
@ -109,6 +124,23 @@ func (s *TCPServer) handleConnection(conn *gtcp.Conn) {
|
|||
s.Connection.Add(tcpConn)
|
||||
s.Logger.Info(s.ctx, fmt.Sprintf("New connection established: %s", connID))
|
||||
|
||||
// 存储到SQLite
|
||||
connInfo := &pool.ConnectionInfo{
|
||||
ID: connID,
|
||||
Type: pool.ConnTypeTCP,
|
||||
Address: conn.RemoteAddr().String(),
|
||||
IsActive: true,
|
||||
LastUsed: time.Now(),
|
||||
CreatedAt: time.Now(),
|
||||
Data: map[string]interface{}{
|
||||
"localAddress": conn.LocalAddr().String(),
|
||||
},
|
||||
}
|
||||
if err := s.Connection.sqlitePool.Add(connInfo); err != nil {
|
||||
s.Logger.Error(s.ctx, fmt.Sprintf("Failed to store connection to SQLite: %v", err))
|
||||
// 不影响连接建立,仅记录错误
|
||||
}
|
||||
|
||||
// 启动消息接收协程
|
||||
go s.receiveMessages(tcpConn)
|
||||
}
|
||||
|
|
@ -121,6 +153,11 @@ func (s *TCPServer) receiveMessages(conn *TcpConnection) {
|
|||
}
|
||||
s.Connection.Remove(conn.Id)
|
||||
conn.Server.Close()
|
||||
// 从SQLite移除
|
||||
if err := s.Connection.sqlitePool.Remove(conn.Id); err != nil {
|
||||
s.Logger.Error(s.ctx, fmt.Sprintf("Failed to remove connection from SQLite: %v", err))
|
||||
// 不影响连接关闭,仅记录错误
|
||||
}
|
||||
s.Logger.Info(s.ctx, fmt.Sprintf("Connection closed: %s", conn.Id))
|
||||
}()
|
||||
|
||||
|
|
@ -142,37 +179,79 @@ func (s *TCPServer) receiveMessages(conn *TcpConnection) {
|
|||
|
||||
if n > 0 {
|
||||
// 更新最后使用时间
|
||||
now := time.Now()
|
||||
conn.Mutex.Lock()
|
||||
conn.LastUsed = time.Now()
|
||||
conn.LastUsed = now
|
||||
// 将读取的数据添加到连接的缓冲区
|
||||
conn.buffer = append(conn.buffer, buffer[:n]...)
|
||||
conn.Mutex.Unlock()
|
||||
|
||||
// 处理消息
|
||||
data := make([]byte, n)
|
||||
copy(data, buffer[:n])
|
||||
|
||||
msg := &TcpMessage{
|
||||
Id: fmt.Sprintf("msg_%d", gtime.TimestampNano()),
|
||||
ConnId: conn.Id,
|
||||
Data: data,
|
||||
Timestamp: time.Now(),
|
||||
IsSend: false,
|
||||
// 更新SQLite中的连接信息
|
||||
connInfo, err := s.Connection.sqlitePool.Get(conn.Id)
|
||||
if err == nil && connInfo != nil {
|
||||
connInfo.LastUsed = now
|
||||
if err := s.Connection.sqlitePool.Update(connInfo); err != nil {
|
||||
s.Logger.Error(s.ctx, fmt.Sprintf("Failed to update connection in SQLite: %v", err))
|
||||
// 不影响消息处理,仅记录错误
|
||||
}
|
||||
}
|
||||
|
||||
// 使用协程池处理消息,避免阻塞
|
||||
grpool.AddWithRecover(s.ctx, func(ctx context.Context) {
|
||||
if s.MessageHandler != nil {
|
||||
if err := s.MessageHandler(conn, msg); err != nil {
|
||||
s.Logger.Error(s.ctx, fmt.Sprintf("Message handling error: %v", err))
|
||||
}
|
||||
}
|
||||
}, func(ctx context.Context, err error) {
|
||||
s.Logger.Error(ctx, fmt.Sprintf("Message handling error: %v", err))
|
||||
})
|
||||
// 解析消息帧
|
||||
s.parseMessageFrames(conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseMessageFrames 解析消息帧
|
||||
func (s *TCPServer) parseMessageFrames(conn *TcpConnection) {
|
||||
conn.Mutex.Lock()
|
||||
defer conn.Mutex.Unlock()
|
||||
|
||||
for {
|
||||
// 检查缓冲区是否有足够的数据来读取长度前缀
|
||||
if len(conn.buffer) < messageLengthPrefixSize {
|
||||
// 数据不足,等待下一次读取
|
||||
return
|
||||
}
|
||||
|
||||
// 读取长度前缀
|
||||
length := binary.BigEndian.Uint32(conn.buffer[:messageLengthPrefixSize])
|
||||
|
||||
// 检查缓冲区是否有足够的数据来读取完整的消息
|
||||
if len(conn.buffer) < messageLengthPrefixSize+int(length) {
|
||||
// 数据不足,等待下一次读取
|
||||
return
|
||||
}
|
||||
|
||||
// 提取消息数据
|
||||
data := conn.buffer[messageLengthPrefixSize : messageLengthPrefixSize+int(length)]
|
||||
|
||||
// 移除已处理的消息数据
|
||||
conn.buffer = conn.buffer[messageLengthPrefixSize+int(length):]
|
||||
|
||||
// 创建消息对象
|
||||
msg := &TcpMessage{
|
||||
Id: fmt.Sprintf("msg_%d", gtime.TimestampNano()),
|
||||
ConnId: conn.Id,
|
||||
Data: data,
|
||||
Timestamp: time.Now(),
|
||||
IsSend: false,
|
||||
}
|
||||
|
||||
// 使用协程池处理消息,避免阻塞
|
||||
grpool.AddWithRecover(s.ctx, func(ctx context.Context) {
|
||||
if s.MessageHandler != nil {
|
||||
if err := s.MessageHandler(conn, msg); err != nil {
|
||||
s.Logger.Error(s.ctx, fmt.Sprintf("Message handling error: %v", err))
|
||||
}
|
||||
}
|
||||
}, func(ctx context.Context, err error) {
|
||||
s.Logger.Error(ctx, fmt.Sprintf("Message handling error: %v", err))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// SendTo 发送消息到指定连接
|
||||
func (s *TCPServer) SendTo(connID string, data []byte) error {
|
||||
conn := s.Connection.Get(connID)
|
||||
|
|
@ -202,14 +281,33 @@ func (s *TCPServer) sendMessage(conn *TcpConnection, data []byte) error {
|
|||
// 设置写入超时
|
||||
conn.Server.SetWriteDeadline(time.Now().Add(s.Config.WriteTimeout))
|
||||
|
||||
// 创建消息帧:4字节长度前缀 + 消息数据
|
||||
frame := make([]byte, messageLengthPrefixSize+len(data))
|
||||
// 写入长度前缀(大端序)
|
||||
binary.BigEndian.PutUint32(frame[:messageLengthPrefixSize], uint32(len(data)))
|
||||
// 写入消息数据
|
||||
copy(frame[messageLengthPrefixSize:], data)
|
||||
|
||||
// 发送数据
|
||||
_, err := conn.Server.Write(data)
|
||||
_, err := conn.Server.Write(frame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新最后使用时间
|
||||
conn.LastUsed = time.Now()
|
||||
now := time.Now()
|
||||
conn.LastUsed = now
|
||||
|
||||
// 更新SQLite中的连接信息
|
||||
connInfo, err := s.Connection.sqlitePool.Get(conn.Id)
|
||||
if err == nil && connInfo != nil {
|
||||
connInfo.LastUsed = now
|
||||
if err := s.Connection.sqlitePool.Update(connInfo); err != nil {
|
||||
s.Logger.Error(s.ctx, fmt.Sprintf("Failed to update connection in SQLite: %v", err))
|
||||
// 不影响消息发送,仅记录错误
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -224,11 +322,21 @@ func (s *TCPServer) Kick(connID string) error {
|
|||
conn.Server.Close()
|
||||
// 从连接池移除
|
||||
s.Connection.Remove(connID)
|
||||
// 从SQLite移除
|
||||
if err := s.Connection.sqlitePool.Remove(connID); err != nil {
|
||||
s.Logger.Error(s.ctx, fmt.Sprintf("Failed to remove connection from SQLite: %v", err))
|
||||
// 不影响连接关闭,仅记录错误
|
||||
}
|
||||
|
||||
s.Logger.Info(s.ctx, fmt.Sprintf("Kicked connection: %s", connID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllConnIDs 获取所有在线连接的ID列表
|
||||
func (s *TCPServer) GetAllConnIDs() ([]string, error) {
|
||||
return s.Connection.GetAllConnIDs()
|
||||
}
|
||||
|
||||
// Add 添加连接到连接池
|
||||
func (p *ConnectionPool) Add(conn *TcpConnection) {
|
||||
p.mutex.Lock()
|
||||
|
|
@ -278,3 +386,8 @@ func (p *ConnectionPool) Count() int {
|
|||
defer p.mutex.RUnlock()
|
||||
return len(p.connections)
|
||||
}
|
||||
|
||||
// GetAllConnIDs 获取所有在线连接的ID列表
|
||||
func (p *ConnectionPool) GetAllConnIDs() ([]string, error) {
|
||||
return p.sqlitePool.GetAllConnIDs()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@ import (
|
|||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
)
|
||||
|
||||
// 消息帧格式:4字节长度前缀 + 消息数据
|
||||
const (
|
||||
messageLengthPrefixSize = 4 // 消息长度前缀大小(4字节)
|
||||
)
|
||||
|
||||
// TcpPoolConfig TCP连接池配置
|
||||
type TcpPoolConfig struct {
|
||||
BufferSize int `json:"bufferSize"` // 缓冲区大小
|
||||
|
|
@ -26,6 +31,7 @@ type TcpConnection struct {
|
|||
LastUsed time.Time `json:"lastUsed"` // 最后使用时间
|
||||
CreatedAt time.Time `json:"createdAt"` // 创建时间
|
||||
Mutex sync.RWMutex `json:"-"` // 读写锁
|
||||
buffer []byte `json:"-"` // 用于存储未处理的字节数据
|
||||
}
|
||||
|
||||
// TcpMessage TCP消息结构
|
||||
|
|
|
|||
21
utils/sql.go
21
utils/sql.go
|
|
@ -3,6 +3,7 @@ package utils
|
|||
import (
|
||||
"context"
|
||||
|
||||
"git.magicany.cc/black1552/gf-common/log"
|
||||
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
"github.com/gogf/gf/v2/crypto/gmd5"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
|
|
@ -51,51 +52,51 @@ func NewClient[R any](request any, url string, header map[string]string) *SClien
|
|||
return s
|
||||
}
|
||||
func (w *SClient[R]) Post(ctx context.Context) (res *R, err error) {
|
||||
g.Log().Infof(ctx, "请求Url:%s,请求头:%v,请求方法:%s,请求内容:%s", w.url, w.header, "post", w.request)
|
||||
log.Info("请求Url:%s,请求头:%v,请求方法:%s,请求内容:%s", w.url, w.header, "post", w.request)
|
||||
resp := w.client.PostVar(ctx, w.url, w.request)
|
||||
err = gconv.Struct(resp, &res)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "解析响应体异常:%s", err)
|
||||
log.Error("请求异常:", err)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
func (w *SClient[R]) Get(ctx context.Context) (res *R, err error) {
|
||||
g.Log().Infof(ctx, "请求Url:%s,请求头:%v,请求方法:%s,请求内容:%s", w.url, w.header, "get", w.request)
|
||||
log.Info("请求Url:%s,请求头:%v,请求方法:%s,请求内容:%s", w.url, w.header, "get", w.request)
|
||||
resp := w.client.GetVar(ctx, w.url, w.request)
|
||||
err = gconv.Struct(resp, &res)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "解析响应体异常:%s", err)
|
||||
log.Error("解析响应体异常:", err)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
func (w *SClient[R]) Put(ctx context.Context) (res *R, err error) {
|
||||
g.Log().Infof(ctx, "请求Url:%s,请求头:%v,请求方法:%s,请求内容:%s", w.url, w.header, "put", w.request)
|
||||
log.Info("请求Url:%s,请求头:%v,请求方法:%s,请求内容:%s", w.url, w.header, "put", w.request)
|
||||
resp := w.client.PutVar(ctx, w.url, w.request)
|
||||
err = gconv.Struct(resp, &res)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "解析响应体异常:%s", err)
|
||||
log.Error("解析响应体异常:", err)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
func (w *SClient[R]) Delete(ctx context.Context) (res *R, err error) {
|
||||
g.Log().Infof(ctx, "请求Url:%s,请求头:%v,请求方法:%s,请求内容:%s", w.url, w.header, "delete", w.request)
|
||||
log.Info("请求Url:%s,请求头:%v,请求方法:%s,请求内容:%s", w.url, w.header, "delete", w.request)
|
||||
resp := w.client.DeleteVar(ctx, w.url, w.request)
|
||||
err = gconv.Struct(resp, &res)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "解析响应体异常:%s", err)
|
||||
log.Error("解析响应体异常:", err)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
func (w *SClient[R]) Patch(ctx context.Context) (res *R, err error) {
|
||||
g.Log().Infof(ctx, "请求Url:%s,请求头:%v,请求方法:%s,请求内容:%s", w.url, w.header, "patch", w.request)
|
||||
log.Info("请求Url:%s,请求头:%v,请求方法:%s,请求内容:%s", w.url, w.header, "patch", w.request)
|
||||
resp := w.client.PatchVar(ctx, w.url, w.request)
|
||||
err = gconv.Struct(resp, &res)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "解析响应体异常:%s", err)
|
||||
log.Error("解析响应体异常:", err)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
|
|
|
|||
Loading…
Reference in New Issue