Browse Source

feat(globallimit): 新增全局限制编辑功能并优化相关逻辑

- 新增 GlobalLimitEditRequest 结构体用于处理编辑请求- 添加 GetGlobalLimitAllExpired 和 GetGlobalLimitAllHostId 方法- 实现 EditGlobalLimitBySnail 方法以支持特定场景下的编辑操作
- 优化 AddGlobalLimit 和 EditGlobalLimit 方法的逻辑- 移除未使用的 GetGatewayGroupAllIds 方法
fusu 1 month ago
parent
commit
a07c5d4cd1

+ 26 - 0
api/v1/globalLimit.go

@@ -5,6 +5,14 @@ type GlobalLimitRequest struct {
 	HostId  int    `json:"host_id" form:"host_id" binding:"required"`
 	Comment string `json:"comment" form:"comment" binding:"required"`
 }
+type GlobalLimitEditRequest struct {
+	Uid     int    `json:"uid" form:"uid" binding:"required"`
+	HostId  int    `json:"host_id" form:"host_id" binding:"required"`
+	RuleId  int    `json:"rule_id" form:"rule_id" binding:"required"`
+	Comment string `json:"comment" form:"comment" binding:"required"`
+	ExpiredAt int64 `json:"expired_at" form:"expired_at" binding:"required"`
+}
+
 type GlobalLimitSendRequest struct {
 	Tag           string `json:"tag" form:"tag" binding:"required"`
 	Bps           string `form:"bps" json:"bps" default:"0"`
@@ -38,4 +46,22 @@ type AccessRules struct {
 type AccessRuleRules struct {
 	Rule   string `json:"rule" form:"rule"`
 	Action string `json:"action" form:"action" default:"deny"`
+}
+
+type GlobalLimitExpired struct {
+	HostId int `json:"host_id" form:"host_id" gorm:"column:host_id"`
+	RuleId int `json:"rule_id" form:"rule_id" gorm:"column:rule_id"`
+	Comment string `json:"comment" form:"comment" gorm:"column:comment"`
+}
+
+type GlobalLimitExpiredByHost struct {
+	Id int `json:"id" form:"id"`
+	Nextduedate int64 `json:"nextduedate" form:"nextduedate"`
+	Uid int64 `json:"uid" form:"uid"`
+}
+
+
+type GlobalLimitExpiredBySnail struct {
+	HostId int `json:"host_id" form:"host_id" gorm:"column:waf_common_limit_id"`
+	ExpiredAt int64 `json:"expired_at" form:"expired_at" gorm:"column:expired_at"`
 }

+ 4 - 4
config/prod.yml

@@ -15,10 +15,10 @@ data:
       driver: mysql
       dsn: 183_136_132_25:xGrNJphcmGcXiajE@tcp(183.136.132.25:3306)/183_136_132_25?charset=utf8mb4&parseTime=True&loc=Local
       logLevel: "warn"
-#    second:
-#      driver: mysql
-#      dsn: second_db_user:password@tcp(second-db-host:3306)/second_db_name?charset=utf8mb4&parseTime=True&loc=Local
-#      logLevel: "warn"
+    second:
+      driver: mysql
+      dsn: root:Mgrj9hMF3QQ3atX5hFIo@tcp(115.238.186.121:3306)/0panel?charset=utf8mb4&parseTime=True&loc=Local
+      logLevel: "info"
   #    user:
   #      driver: sqlite
   #      dsn: storage/nunu-test.db?_busy_timeout=5000

+ 5 - 5
go.mod

@@ -18,9 +18,9 @@ require (
 	github.com/google/wire v0.5.0
 	github.com/jinzhu/copier v0.4.0
 	github.com/mcuadros/go-defaults v1.2.0
+	github.com/qiniu/qmgo v1.1.9
 	github.com/redis/go-redis/v9 v9.0.5
 	github.com/sony/sonyflake v1.1.0
-	github.com/sourcegraph/conc v0.3.0
 	github.com/spf13/cast v1.5.1
 	github.com/spf13/viper v1.8.1
 	github.com/stretchr/testify v1.8.4
@@ -28,12 +28,15 @@ require (
 	github.com/swaggo/gin-swagger v1.6.0
 	github.com/swaggo/swag v1.16.4
 	github.com/tidwall/gjson v1.18.0
+	go.mongodb.org/mongo-driver v1.17.4
 	go.uber.org/zap v1.26.0
 	golang.org/x/crypto v0.39.0
+	golang.org/x/sync v0.15.0
 	google.golang.org/grpc v1.55.1
 	gorm.io/driver/mysql v1.5.7
 	gorm.io/driver/postgres v1.5.11
-	gorm.io/gorm v1.25.12
+	gorm.io/gorm v1.30.0
+	gorm.io/plugin/dbresolver v1.6.0
 )
 
 require (
@@ -96,7 +99,6 @@ require (
 	github.com/pelletier/go-toml v1.9.5 // indirect
 	github.com/pelletier/go-toml/v2 v2.0.9 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/qiniu/qmgo v1.1.9 // indirect
 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
 	github.com/robfig/cron/v3 v3.0.1 // indirect
 	github.com/sanity-io/litter v1.5.5 // indirect
@@ -121,13 +123,11 @@ require (
 	github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
 	github.com/yudai/gojsondiff v1.0.0 // indirect
 	github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
-	go.mongodb.org/mongo-driver v1.17.4 // indirect
 	go.uber.org/atomic v1.11.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	golang.org/x/arch v0.3.0 // indirect
 	golang.org/x/exp v0.0.0-20221208152030-732eee02a75a // indirect
 	golang.org/x/net v0.41.0 // indirect
-	golang.org/x/sync v0.15.0 // indirect
 	golang.org/x/sys v0.33.0 // indirect
 	golang.org/x/text v0.26.0 // indirect
 	golang.org/x/tools v0.33.0 // indirect

+ 6 - 24
go.sum

@@ -158,8 +158,6 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
 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.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
-github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
-github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
 github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
 github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
 github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
@@ -206,7 +204,6 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
-github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
 github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -314,10 +311,7 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
-github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
 github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
-github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
-github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
 github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
 github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@@ -335,8 +329,6 @@ 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.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
-github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
-github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
 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.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
@@ -422,8 +414,6 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/sony/sonyflake v1.1.0 h1:wnrEcL3aOkWmPlhScLEGAXKkLAIslnBteNUq4Bw6MM4=
 github.com/sony/sonyflake v1.1.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
-github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
-github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
 github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
 github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
 github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
@@ -439,6 +429,7 @@ github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
 github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -552,8 +543,6 @@ golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
 golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
-golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
-golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
 golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
 golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -597,9 +586,9 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
 golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
+golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -647,8 +636,6 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
-golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
-golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
 golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
 golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -681,8 +668,6 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
-golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
 golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -744,8 +729,6 @@ golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
-golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
 golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
@@ -776,8 +759,6 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
-golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
-golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
 golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
 golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -841,7 +822,6 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
-golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
 golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
 golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
 golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
@@ -984,8 +964,10 @@ gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkD
 gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
 gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
 gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
-gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
-gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
+gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
+gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
+gorm.io/plugin/dbresolver v1.6.0 h1:XvKDeOtTn1EIX6s4SrKpEH82q0gXVemhYjbYZFGFVcw=
+gorm.io/plugin/dbresolver v1.6.0/go.mod h1:tctw63jdrOezFR9HmrKnPkmig3m5Edem9fdxk9bQSzM=
 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=

+ 1 - 0
internal/handler/globallimit.go

@@ -67,3 +67,4 @@ func (h *GlobalLimitHandler) DeleteGlobalLimit(ctx *gin.Context) {
 	}
 	v1.HandleSuccess(ctx, nil)
 }
+

+ 2 - 0
internal/model/globallimit.go

@@ -6,12 +6,14 @@ type GlobalLimit struct {
 	Id              int `gorm:"primary"`
 	HostId          int
 	RuleId          int
+	Uid             int
 	TcpLimitRuleId  int
 	UdpLimitRuleId  int
 	WebLimitRuleId  int
 	GatewayGroupId  int
 	GlobalLimitName string
 	Comment         string
+	ExpiredAt       int64
 	createdAt       time.Time
 	updatedAt       time.Time
 }

+ 0 - 8
internal/repository/gatewaygroup.go

@@ -15,7 +15,6 @@ type GatewayGroupRepository interface {
 	DeleteGatewayGroup(ctx context.Context, req *model.GatewayGroup) error
 	GetGatewayGroupWhereHostIdNull(ctx context.Context,operator int, count int) (int, error)
 	GetGatewayGroupByHostId(ctx context.Context, hostId int64) (*[]model.GatewayGroup, error)
-	GetGatewayGroupAllIds(ctx context.Context) ([]int, error)
 }
 
 func NewGatewayGroupRepository(
@@ -87,10 +86,3 @@ func (r *gatewayGroupRepository) GetGatewayGroupByHostId(ctx context.Context, ho
 	return &res, nil
 }
 
-func (r *gatewayGroupRepository) GetGatewayGroupAllIds(ctx context.Context) ([]int, error) {
-	var res []int
-	if err := r.DB(ctx).Model(&model.GatewayGroup{}).Pluck("host_id", &res).Error; err != nil {
-		return nil, err
-	}
-	return res, nil
-}

+ 31 - 0
internal/repository/globallimit.go

@@ -2,7 +2,9 @@ package repository
 
 import (
 	"context"
+	v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
 	"github.com/go-nunu/nunu-layout-advanced/internal/model"
+	"time"
 )
 
 type GlobalLimitRepository interface {
@@ -12,6 +14,8 @@ type GlobalLimitRepository interface {
 	DeleteGlobalLimitByHostId(ctx context.Context, hostId int64) error
 	IsGlobalLimitExistByHostId(ctx context.Context, hostId int64) (bool, error)
 	GetGlobalLimitByHostId(ctx context.Context, hostId int64) (*model.GlobalLimit, error)
+	GetGlobalLimitAllExpired(ctx context.Context,ids []int) ([]v1.GlobalLimitExpiredByHost, error)
+	GetGlobalLimitAllHostId(ctx context.Context) ([]v1.GlobalLimitExpired, error)
 }
 
 func NewGlobalLimitRepository(
@@ -72,3 +76,30 @@ func (r *globalLimitRepository) GetGlobalLimitByHostId(ctx context.Context, host
 	return &globalLimit, nil
 
 }
+
+
+func (r *globalLimitRepository) GetGlobalLimitAllExpired(ctx context.Context,ids []int) ([]v1.GlobalLimitExpiredByHost, error) {
+	var res []v1.GlobalLimitExpiredByHost
+	threeDaysDuration := 30 * 24 * time.Hour
+	targetTime := time.Now().Add(threeDaysDuration)
+	targetTimestamp := targetTime.Unix()
+	if err := r.DB(ctx).Table("shd_host").
+		Where("id IN (?)", ids).
+		Where("nextduedate < ?", targetTimestamp).
+		Select("id", "uid", "nextduedate").
+		Find(&res).
+		Error; err != nil {
+		return nil, err
+	}
+	return res, nil
+}
+
+func (r *globalLimitRepository) GetGlobalLimitAllHostId(ctx context.Context) ([]v1.GlobalLimitExpired, error) {
+	var res []v1.GlobalLimitExpired
+	if err := r.DB(ctx).Model(&model.GlobalLimit{}).
+		Select("host_id", "rule_id","comment").
+		Find(&res).Error; err != nil {
+		return nil, err
+	}
+	return res, nil
+}

+ 118 - 9
internal/repository/repository.go

@@ -13,6 +13,7 @@ import (
 	"gorm.io/driver/postgres"
 	"gorm.io/gorm"
 	gormlogger "gorm.io/gorm/logger"
+	"gorm.io/plugin/dbresolver"
 	"time"
 )
 
@@ -44,6 +45,8 @@ func NewRepository(
 
 type Transaction interface {
 	Transaction(ctx context.Context, fn func(ctx context.Context) error) error
+	// 在特定数据库上执行事务
+	TransactionWithDB(ctx context.Context, dbName string, fn func(ctx context.Context) error) error
 }
 
 func NewTransaction(r *Repository) Transaction {
@@ -62,10 +65,43 @@ func (r *Repository) DB(ctx context.Context) *gorm.DB {
 	return r.db.WithContext(ctx)
 }
 
+// DBWithName 使用特定名称的数据库连接
+func (r *Repository) DBWithName(ctx context.Context, dbName string) *gorm.DB {
+	// 先检查上下文中是否已存在事务
+	v := ctx.Value(ctxTxKey)
+	if v != nil {
+		if tx, ok := v.(*gorm.DB); ok {
+			// 如果事务中已经指定了数据库,则直接返回
+			return tx
+		}
+	}
+	
+	// 使用指定名称的数据库连接
+	if dbName != "" {
+		return r.db.Clauses(dbresolver.Use(dbName)).WithContext(ctx)
+	}
+	return r.db.WithContext(ctx)
+}
+
 func (r *Repository) Transaction(ctx context.Context, fn func(ctx context.Context) error) error {
 	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
-		ctx = context.WithValue(ctx, ctxTxKey, tx)
-		return fn(ctx)
+		ctxWithTx := context.WithValue(ctx, ctxTxKey, tx)
+		return fn(ctxWithTx)
+	})
+}
+
+// TransactionWithDB 在特定数据库上执行事务
+func (r *Repository) TransactionWithDB(ctx context.Context, dbName string, fn func(ctx context.Context) error) error {
+	// 使用特定的数据库连接
+	db := r.db
+	if dbName != "" {
+		db = db.Clauses(dbresolver.Use(dbName))
+	}
+	
+	return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
+		// tx已经是针对特定数据库的事务句柄,无需再次指定数据库
+		ctxWithTx := context.WithValue(ctx, ctxTxKey, tx)
+		return fn(ctxWithTx)
 	})
 }
 
@@ -75,11 +111,26 @@ func NewDB(conf *viper.Viper, l *log.Logger) *gorm.DB {
 		err error
 	)
 
-	driver := conf.GetString("data.db.user.driver")
-	dsn := conf.GetString("data.db.user.dsn")
+	// 获取主数据库键名
+	primaryDBKey := conf.GetString("data.primary_db_key")
+	if primaryDBKey == "" {
+		// 默认使用user作为主数据库键名(向后兼容)
+		primaryDBKey = "user"
+	}
+
+	// 从配置中获取主数据库配置
+	driver := conf.GetString(fmt.Sprintf("data.db.%s.driver", primaryDBKey))
+	if driver == "" {
+		panic("主数据库驱动配置不能为空")
+	}
+	
+	dsn := conf.GetString(fmt.Sprintf("data.db.%s.dsn", primaryDBKey))
+	if dsn == "" {
+		panic("主数据库连接字符串不能为空")
+	}
 
 	// 读取日志级别配置
-	logLevelStr := conf.GetString("data.db.user.logLevel")
+	logLevelStr := conf.GetString(fmt.Sprintf("data.db.%s.logLevel", primaryDBKey))
 	var logLevel gormlogger.LogLevel
 
 	switch logLevelStr {
@@ -102,7 +153,7 @@ func NewDB(conf *viper.Viper, l *log.Logger) *gorm.DB {
 
 	logger := zapgorm2.New(l.Logger).LogMode(logLevel)
 
-	// GORM doc: https://gorm.io/docs/connecting_to_the_database.html
+	// 连接主数据库
 	switch driver {
 	case "mysql":
 		db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
@@ -120,14 +171,72 @@ func NewDB(conf *viper.Viper, l *log.Logger) *gorm.DB {
 			Logger: logger,
 		})
 	default:
-		panic("unknown db driver")
+		panic("不支持的数据库驱动类型: " + driver)
 	}
 
 	if err != nil {
-		panic(err)
+		panic(fmt.Sprintf("连接主数据库失败: %s", err.Error()))
+	}
+
+	// 创建 dbresolver 实例
+	resolver := dbresolver.Register(dbresolver.Config{})
+
+	// 获取所有配置的数据库列表
+	databases := conf.GetStringMap("data.db")
+
+	// 遍历所有数据库配置(跳过主数据库,因为已经连接)
+	for dbKey, _ := range databases {
+		// 跳过主数据库(已经直接连接了)
+		if dbKey == primaryDBKey {
+			continue
+		}
+
+		// 检查该键是否确实是一个数据库配置对象
+		dbDriver := conf.GetString(fmt.Sprintf("data.db.%s.driver", dbKey))
+		dbDSN := conf.GetString(fmt.Sprintf("data.db.%s.dsn", dbKey))
+
+		if dbDriver != "" && dbDSN != "" {
+			// 构建数据库连接器
+			var dialector gorm.Dialector
+			switch dbDriver {
+			case "mysql":
+				dialector = mysql.Open(dbDSN)
+			case "postgres":
+				dialector = postgres.New(postgres.Config{
+					DSN:                  dbDSN,
+					PreferSimpleProtocol: true,
+				})
+			case "sqlite":
+				dialector = sqlite.Open(dbDSN)
+			default:
+				l.Warn(fmt.Sprintf("跳过不支持的数据库驱动类型: %s (dbKey: %s)", dbDriver, dbKey))
+				continue
+			}
+
+			// 注册到resolver
+			resolver.Register(dbresolver.Config{
+				Sources:  []gorm.Dialector{dialector},
+				Replicas: []gorm.Dialector{dialector},
+				Policy:   dbresolver.RandomPolicy{},
+			}, dbKey) // 使用配置键作为数据库名称
+
+			l.Info(fmt.Sprintf("成功配置数据库连接: %s", dbKey))
+		}
+	}
+
+	// 设置连接池参数
+	resolver.SetConnMaxIdleTime(time.Hour).
+		SetConnMaxLifetime(24 * time.Hour).
+		SetMaxIdleConns(10).
+		SetMaxOpenConns(100)
+
+	// 应用配置好的 dbresolver 到 db
+	err = db.Use(resolver)
+	if err != nil {
+		panic(fmt.Sprintf("应用数据库连接配置失败: %s", err.Error()))
 	}
 
-	// Connection Pool config
+	// 主数据库连接池配置
 	sqlDB, err := db.DB()
 	if err != nil {
 		panic(err)

+ 77 - 8
internal/service/globallimit.go

@@ -10,6 +10,7 @@ import (
 	"github.com/spf13/viper"
 	"golang.org/x/sync/errgroup"
 	"strconv"
+	"time"
 )
 
 type GlobalLimitService interface {
@@ -17,6 +18,7 @@ type GlobalLimitService interface {
 	AddGlobalLimit(ctx context.Context, req v1.GlobalLimitRequest) error
 	EditGlobalLimit(ctx context.Context, req v1.GlobalLimitRequest) error
 	DeleteGlobalLimit(ctx context.Context, req v1.GlobalLimitRequest) error
+	EditGlobalLimitBySnail(ctx context.Context, req v1.GlobalLimitEditRequest) error
 }
 
 func NewGlobalLimitService(
@@ -71,13 +73,6 @@ type globalLimitService struct {
 }
 
 func (s *globalLimitService) GlobalLimitRequire(ctx context.Context, req v1.GlobalLimitRequest) (res v1.GlobalLimitRequireResponse, err error) {
-	isExist, err := s.globalLimitRepository.IsGlobalLimitExistByHostId(ctx, int64(req.HostId))
-	if err != nil {
-		return v1.GlobalLimitRequireResponse{}, err
-	}
-	if isExist {
-		return v1.GlobalLimitRequireResponse{}, fmt.Errorf("配置限制已存在")
-	}
 	res.ExpiredAt, err = s.duedate.NextDueDate(ctx, req.Uid, req.HostId)
 	if err != nil {
 		return v1.GlobalLimitRequireResponse{}, err
@@ -104,6 +99,13 @@ func (s *globalLimitService) GetGlobalLimit(ctx context.Context, id int64) (*mod
 }
 
 func (s *globalLimitService) AddGlobalLimit(ctx context.Context, req v1.GlobalLimitRequest) error {
+	isExist, err := s.globalLimitRepository.IsGlobalLimitExistByHostId(ctx, int64(req.HostId))
+	if err != nil {
+		return err
+	}
+	if isExist {
+		return fmt.Errorf("配置限制已存在")
+	}
 	require, err := s.GlobalLimitRequire(ctx, req)
 	if err != nil {
 		return err
@@ -202,16 +204,22 @@ func (s *globalLimitService) AddGlobalLimit(ctx context.Context, req v1.GlobalLi
 	if err := g.Wait(); err != nil {
 		return err
 	}
-
+	t, err := time.Parse("2006-01-02 15:04:05", require.ExpiredAt)
+	if err != nil {
+		return err
+	}
+	expiredAt := t.Unix()
 	err = s.globalLimitRepository.AddGlobalLimit(ctx, &model.GlobalLimit{
 		HostId:          req.HostId,
 		RuleId:          cast.ToInt(ruleId),
+		Uid:             req.Uid,
 		GlobalLimitName: require.GlobalLimitName,
 		Comment:         req.Comment,
 		TcpLimitRuleId:  tcpLimitRuleId,
 		UdpLimitRuleId:  udpLimitRuleId,
 		WebLimitRuleId:  webLimitRuleId,
 		GatewayGroupId:  gatewayGroupId,
+		ExpiredAt:       expiredAt,
 	})
 	if err != nil {
 		return err
@@ -224,15 +232,76 @@ func (s *globalLimitService) AddGlobalLimit(ctx context.Context, req v1.GlobalLi
 }
 
 func (s *globalLimitService) EditGlobalLimit(ctx context.Context, req v1.GlobalLimitRequest) error {
+	require, err := s.GlobalLimitRequire(ctx, req)
+	if err != nil {
+		return err
+	}
+	formData := map[string]interface{}{
+		"tag":             require.GlobalLimitName,
+		"bps":             require.Bps,
+		"max_bytes_month": require.MaxBytesMonth,
+		"expired_at":      require.ExpiredAt,
+	}
+	data, err :=  s.globalLimitRepository.GetGlobalLimitByHostId(ctx, int64(req.HostId))
+	if err != nil {
+		return err
+	}
+	respBody, err := s.required.SendForm(ctx, "admin/info/waf_common_limit/edit?&__goadmin_edit_pk="+strconv.Itoa(data.RuleId), "admin/edit/waf_common_limit", formData)
+	if err != nil {
+		return err
+	}
+	res, err := s.parser.ParseAlert(string(respBody))
+	if err != nil {
+		return err
+	}
+	if res != "" {
+		return fmt.Errorf(res)
+	}
+	t, err := time.Parse("2006-01-02 15:04:05", require.ExpiredAt)
+	if err != nil {
+		return err
+	}
+	expiredAt := t.Unix()
 	if err := s.globalLimitRepository.UpdateGlobalLimitByHostId(ctx, &model.GlobalLimit{
 		HostId:  req.HostId,
 		Comment: req.Comment,
+		ExpiredAt: expiredAt,
 	}); err != nil {
 		return err
 	}
 	return nil
 }
 
+func (s *globalLimitService) EditGlobalLimitBySnail(ctx context.Context, req v1.GlobalLimitEditRequest) error {
+	configCount, err := s.host.GetGlobalLimitConfig(ctx, req.HostId)
+	if err != nil {
+		return fmt.Errorf("获取配置限制失败: %w", err)
+	}
+	data, err := s.globalLimitRepository.GetGlobalLimitByHostId(ctx, int64(req.HostId))
+	if err != nil {
+		return err
+	}
+	t := time.Unix(req.ExpiredAt, 0)
+	expiredAt := t.Format("2006-01-02 15:04:05")
+	formData := map[string]interface{}{
+		"tag":             data.GlobalLimitName,
+		"bps":             configCount.Bps,
+		"max_bytes_month": configCount.MaxBytesMonth,
+		"expired_at":      expiredAt,
+	}
+
+	respBody, err := s.required.SendForm(ctx, "admin/info/waf_common_limit/edit?&__goadmin_edit_pk="+strconv.Itoa(req.RuleId), "admin/edit/waf_common_limit", formData)
+
+	if err != nil {
+		return err
+	}
+	if respBody == nil {
+		return nil
+	}
+	return nil
+
+}
+
 func (s *globalLimitService) DeleteGlobalLimit(ctx context.Context, req v1.GlobalLimitRequest) error {
 	if err := s.globalLimitRepository.DeleteGlobalLimitByHostId(ctx, int64(req.HostId)); err != nil {
 		return err