认证授权主要流程实现
This commit is contained in:
31
README.md
31
README.md
@@ -1,16 +1,39 @@
|
|||||||
## todo
|
## todo
|
||||||
|
|
||||||
购买使用流程
|
核心流程:
|
||||||
|
|
||||||
- [ ] 注册
|
- [x] 注册与登录
|
||||||
- [ ] 登录
|
- [ ] 对接短信接口
|
||||||
|
- [ ] 人机风险分级验证
|
||||||
|
- [ ] jwt 签发
|
||||||
|
- [ ] 鉴权
|
||||||
- [ ] 实名认证
|
- [ ] 实名认证
|
||||||
- [ ] 充值余额
|
- [ ] 充值余额
|
||||||
- [ ] 选择套餐
|
- [ ] 选择套餐
|
||||||
- [ ] 提取 IP
|
- [ ] 提取 IP
|
||||||
- [ ] 连接
|
- [ ] 连接
|
||||||
|
|
||||||
确认登录方式,决定表结构
|
中间件:
|
||||||
|
|
||||||
|
- [ ] CORS
|
||||||
|
- [ ] Limiter
|
||||||
|
- [ ] Compress
|
||||||
|
|
||||||
|
环境变量配置默认会话配置
|
||||||
|
|
||||||
|
oauth token 验证授权范围
|
||||||
|
|
||||||
|
保存 session 到数据库
|
||||||
|
|
||||||
|
账单数据表结构修改
|
||||||
|
|
||||||
|
captcha 自定义生成流程,弃用 store
|
||||||
|
|
||||||
|
短信发送日志
|
||||||
|
|
||||||
|
captcha_id 关联用户本机信息,实现验证码设备绑定(或者其他方式)
|
||||||
|
|
||||||
|
退出时主动断开数据库缓存等连接
|
||||||
|
|
||||||
固有字段统一放在最开始
|
固有字段统一放在最开始
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
g := gen.NewGenerator(gen.Config{
|
|
||||||
OutPath: "web/queries",
|
|
||||||
ModelPkgPath: "models",
|
|
||||||
Mode: gen.WithDefaultQuery | gen.WithoutContext,
|
|
||||||
})
|
|
||||||
|
|
||||||
db, _ := gorm.Open(
|
db, _ := gorm.Open(
|
||||||
postgres.Open("host=localhost user=test password=test dbname=app port=5432 sslmode=disable TimeZone=Asia/Shanghai"),
|
postgres.Open("host=localhost user=test password=test dbname=app port=5432 sslmode=disable TimeZone=Asia/Shanghai"),
|
||||||
&gorm.Config{
|
&gorm.Config{
|
||||||
@@ -22,6 +16,12 @@ func main() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
g := gen.NewGenerator(gen.Config{
|
||||||
|
OutPath: "web/queries",
|
||||||
|
ModelPkgPath: "models",
|
||||||
|
Mode: gen.WithDefaultQuery | gen.WithoutContext,
|
||||||
|
})
|
||||||
g.UseDB(db)
|
g.UseDB(db)
|
||||||
|
|
||||||
models := g.GenerateAllTable()
|
models := g.GenerateAllTable()
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import (
|
|||||||
"platform/init/env"
|
"platform/init/env"
|
||||||
"platform/init/logs"
|
"platform/init/logs"
|
||||||
"platform/init/orm"
|
"platform/init/orm"
|
||||||
|
"platform/init/rds"
|
||||||
"platform/web"
|
"platform/web"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
logger := slog.Default()
|
|
||||||
|
|
||||||
// 退出信号
|
// 退出信号
|
||||||
shutdown := make(chan os.Signal, 1)
|
shutdown := make(chan os.Signal, 1)
|
||||||
@@ -22,14 +22,14 @@ func main() {
|
|||||||
env.Init()
|
env.Init()
|
||||||
logs.Init()
|
logs.Init()
|
||||||
orm.Init()
|
orm.Init()
|
||||||
|
rds.Init()
|
||||||
|
|
||||||
// web 服务
|
// web 服务
|
||||||
app, err := web.New(&web.Config{
|
app, err := web.New(&web.Config{
|
||||||
Logger: logger,
|
|
||||||
Listen: ":8080",
|
Listen: ":8080",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to create server", slog.Any("error", err))
|
slog.Error("Failed to create server", slog.Any("err", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ func main() {
|
|||||||
go func() {
|
go func() {
|
||||||
err = app.Run()
|
err = app.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to run server", slog.Any("error", err))
|
slog.Error("Failed to run server", slog.Any("err", err))
|
||||||
errCh <- err
|
errCh <- err
|
||||||
}
|
}
|
||||||
errCh <- nil
|
errCh <- nil
|
||||||
@@ -48,19 +48,19 @@ func main() {
|
|||||||
exit := false
|
exit := false
|
||||||
select {
|
select {
|
||||||
case <-shutdown:
|
case <-shutdown:
|
||||||
logger.Info("Received shutdown signal")
|
slog.Info("Received shutdown signal")
|
||||||
app.Stop()
|
app.Stop()
|
||||||
exit = true
|
exit = true
|
||||||
case err := <-errCh:
|
case err := <-errCh:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Server error", slog.Any("error", err))
|
slog.Error("Server error", slog.Any("err", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if exit {
|
if exit {
|
||||||
err := <-errCh
|
err := <-errCh
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Server error", slog.Any("error", err))
|
slog.Error("Server error", slog.Any("err", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
go.mod
17
go.mod
@@ -4,17 +4,22 @@ go 1.24.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gofiber/fiber/v2 v2.52.6
|
github.com/gofiber/fiber/v2 v2.52.6
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/lmittmann/tint v1.0.7
|
github.com/lmittmann/tint v1.0.7
|
||||||
|
github.com/redis/go-redis/v9 v9.3.0
|
||||||
|
golang.org/x/crypto v0.17.0
|
||||||
gorm.io/driver/postgres v1.5.11
|
gorm.io/driver/postgres v1.5.11
|
||||||
gorm.io/gen v0.3.26
|
gorm.io/gen v0.3.26
|
||||||
gorm.io/gorm v1.25.12
|
gorm.io/gorm v1.25.12
|
||||||
|
gorm.io/plugin/dbresolver v1.5.3
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
||||||
@@ -29,14 +34,12 @@ require (
|
|||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
golang.org/x/crypto v0.17.0 // indirect
|
golang.org/x/mod v0.17.0 // indirect
|
||||||
golang.org/x/mod v0.14.0 // indirect
|
golang.org/x/sync v0.12.0 // indirect
|
||||||
golang.org/x/sync v0.6.0 // indirect
|
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
golang.org/x/tools v0.17.0 // indirect
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||||
gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c // indirect
|
gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c // indirect
|
||||||
gorm.io/driver/mysql v1.5.7 // indirect
|
gorm.io/driver/mysql v1.5.7 // indirect
|
||||||
gorm.io/hints v1.1.0 // indirect
|
gorm.io/hints v1.1.0 // indirect
|
||||||
gorm.io/plugin/dbresolver v1.5.3 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
25
go.sum
25
go.sum
@@ -1,8 +1,16 @@
|
|||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
||||||
@@ -46,6 +54,8 @@ github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLg
|
|||||||
github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
|
github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0=
|
||||||
|
github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@@ -66,8 +76,8 @@ golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
|||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
@@ -75,8 +85,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -94,14 +104,15 @@ 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.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
105
init/env/env.go
vendored
105
init/env/env.go
vendored
@@ -3,40 +3,20 @@ package env
|
|||||||
import (
|
import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
"github.com/gofiber/fiber/v2/log"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// region app
|
||||||
|
|
||||||
var (
|
var (
|
||||||
AppName = "platform"
|
AppName = "platform"
|
||||||
AppPort = "8080"
|
AppPort = "8080"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func loadApp() {
|
||||||
DbHost = "localhost"
|
|
||||||
DbPort = "3306"
|
|
||||||
DbName string
|
|
||||||
DbUserName string
|
|
||||||
DbPassword string
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
LogLevel = slog.LevelDebug
|
|
||||||
)
|
|
||||||
|
|
||||||
func Init() {
|
|
||||||
err := godotenv.Load()
|
|
||||||
if err != nil {
|
|
||||||
log.Debug("❓ 没有本地环境变量")
|
|
||||||
} else {
|
|
||||||
log.Debug("✔ 加载本地环境变量")
|
|
||||||
}
|
|
||||||
|
|
||||||
check()
|
|
||||||
}
|
|
||||||
|
|
||||||
func check() {
|
|
||||||
_AppName := os.Getenv("APP_NAME")
|
_AppName := os.Getenv("APP_NAME")
|
||||||
if _AppName != "" {
|
if _AppName != "" {
|
||||||
AppName = _AppName
|
AppName = _AppName
|
||||||
@@ -46,7 +26,21 @@ func check() {
|
|||||||
if _AppPort != "" {
|
if _AppPort != "" {
|
||||||
AppPort = _AppPort
|
AppPort = _AppPort
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region db
|
||||||
|
|
||||||
|
var (
|
||||||
|
DbHost = "localhost"
|
||||||
|
DbPort = "5432"
|
||||||
|
DbName string
|
||||||
|
DbUserName string
|
||||||
|
DbPassword string
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadDb() {
|
||||||
_DbHost := os.Getenv("DB_HOST")
|
_DbHost := os.Getenv("DB_HOST")
|
||||||
if _DbHost != "" {
|
if _DbHost != "" {
|
||||||
DbHost = _DbHost
|
DbHost = _DbHost
|
||||||
@@ -77,7 +71,54 @@ func check() {
|
|||||||
} else {
|
} else {
|
||||||
panic("环境变量 DB_PASSWORD 的值为空")
|
panic("环境变量 DB_PASSWORD 的值为空")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region redis
|
||||||
|
|
||||||
|
var (
|
||||||
|
RedisHost = "localhost"
|
||||||
|
RedisPort = "6379"
|
||||||
|
RedisDb = 0
|
||||||
|
RedisPass = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadRedis() {
|
||||||
|
_RedisHost := os.Getenv("REDIS_HOST")
|
||||||
|
if _RedisHost != "" {
|
||||||
|
RedisHost = _RedisHost
|
||||||
|
}
|
||||||
|
|
||||||
|
_RedisPort := os.Getenv("REDIS_PORT")
|
||||||
|
if _RedisPort != "" {
|
||||||
|
RedisPort = _RedisPort
|
||||||
|
}
|
||||||
|
|
||||||
|
_RedisDb := os.Getenv("REDIS_DB")
|
||||||
|
if _RedisDb != "" {
|
||||||
|
atoi, err := strconv.Atoi(_RedisDb)
|
||||||
|
if err != nil {
|
||||||
|
panic("环境变量 REDIS_DB 的值不是数字")
|
||||||
|
}
|
||||||
|
RedisDb = atoi
|
||||||
|
}
|
||||||
|
|
||||||
|
_RedisPass := os.Getenv("REDIS_PASS")
|
||||||
|
if _RedisPass != "" {
|
||||||
|
RedisPass = _RedisPass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region log
|
||||||
|
|
||||||
|
var (
|
||||||
|
LogLevel = slog.LevelDebug
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadLog() {
|
||||||
_LogLevel := os.Getenv("LOG_LEVEL")
|
_LogLevel := os.Getenv("LOG_LEVEL")
|
||||||
switch _LogLevel {
|
switch _LogLevel {
|
||||||
case "debug":
|
case "debug":
|
||||||
@@ -90,3 +131,19 @@ func check() {
|
|||||||
LogLevel = slog.LevelError
|
LogLevel = slog.LevelError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
err := godotenv.Load()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("❓ 没有本地环境变量")
|
||||||
|
} else {
|
||||||
|
log.Debug("✔ 加载本地环境变量")
|
||||||
|
}
|
||||||
|
|
||||||
|
loadApp()
|
||||||
|
loadDb()
|
||||||
|
loadRedis()
|
||||||
|
loadLog()
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,14 +9,18 @@ import (
|
|||||||
"github.com/lmittmann/tint"
|
"github.com/lmittmann/tint"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Default *slog.Logger
|
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
Default = slog.New(
|
slog.SetDefault(slog.New(
|
||||||
tint.NewHandler(os.Stdout, &tint.Options{
|
tint.NewHandler(os.Stdout, &tint.Options{
|
||||||
Level: env.LogLevel,
|
Level: env.LogLevel,
|
||||||
TimeFormat: time.Kitchen,
|
TimeFormat: time.Kitchen,
|
||||||
}),
|
ReplaceAttr: func(_ []string, attr slog.Attr) slog.Attr {
|
||||||
)
|
err, ok := attr.Value.Any().(error)
|
||||||
slog.SetDefault(Default)
|
if ok {
|
||||||
|
return tint.Err(err)
|
||||||
|
}
|
||||||
|
return attr
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,40 +4,39 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"platform/init/env"
|
"platform/init/env"
|
||||||
"platform/init/logs"
|
"platform/web/queries"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/schema"
|
"gorm.io/gorm/schema"
|
||||||
)
|
)
|
||||||
import "gorm.io/driver/postgres"
|
import "gorm.io/driver/postgres"
|
||||||
|
|
||||||
var DB *gorm.DB
|
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
logger := logs.Default
|
|
||||||
|
|
||||||
|
// 连接数据库
|
||||||
dsn := fmt.Sprintf(
|
dsn := fmt.Sprintf(
|
||||||
"host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Shanghai",
|
"host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Shanghai",
|
||||||
env.DbName, env.DbUserName, env.DbPassword, env.DbName, env.DbPort,
|
env.DbHost, env.DbUserName, env.DbPassword, env.DbName, env.DbPort,
|
||||||
)
|
)
|
||||||
|
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
||||||
open, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
|
||||||
NamingStrategy: schema.NamingStrategy{
|
NamingStrategy: schema.NamingStrategy{
|
||||||
SingularTable: true,
|
SingularTable: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("gorm 打开数据库失败", slog.Any("err", err))
|
slog.Error("gorm 初始化数据库失败:", slog.Any("err", err))
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sql, err := open.DB()
|
// 连接池
|
||||||
|
conn, err := db.DB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("gorm open db error: ", slog.Any("err", err))
|
slog.Error("gorm 初始化数据库失败:", slog.Any("err", err))
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
sql.SetMaxIdleConns(10)
|
conn.SetMaxIdleConns(10)
|
||||||
sql.SetMaxOpenConns(100)
|
conn.SetMaxOpenConns(100)
|
||||||
|
|
||||||
DB = open
|
// 初始化查询工具
|
||||||
|
queries.SetDefault(db)
|
||||||
}
|
}
|
||||||
|
|||||||
18
init/rds/rds.go
Normal file
18
init/rds/rds.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package rds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"platform/init/env"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Client *redis.Client
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
Client = redis.NewClient(&redis.Options{
|
||||||
|
Addr: net.JoinHostPort(env.RedisHost, env.RedisPort),
|
||||||
|
DB: env.RedisDb,
|
||||||
|
Password: env.RedisPass,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
name: server-dev
|
name: server-dev
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:17
|
image: postgres:17
|
||||||
restart: always
|
restart: always
|
||||||
@@ -12,6 +13,11 @@ services:
|
|||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
redis:
|
||||||
|
image: redis:7.4
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
-- 清空数据表
|
||||||
|
do
|
||||||
|
$$
|
||||||
|
declare
|
||||||
|
r record;
|
||||||
|
begin
|
||||||
|
for r in (
|
||||||
|
select
|
||||||
|
tablename
|
||||||
|
from
|
||||||
|
pg_tables
|
||||||
|
where
|
||||||
|
schemaname = 'public'
|
||||||
|
) loop
|
||||||
|
execute 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE';
|
||||||
|
end loop;
|
||||||
|
end
|
||||||
|
$$;
|
||||||
|
|
||||||
-- ====================
|
-- ====================
|
||||||
-- region 管理员信息
|
-- region 管理员信息
|
||||||
-- ====================
|
-- ====================
|
||||||
@@ -14,7 +33,7 @@ create table admin (
|
|||||||
email varchar(255),
|
email varchar(255),
|
||||||
status int not null default 1,
|
status int not null default 1,
|
||||||
last_login timestamp,
|
last_login timestamp,
|
||||||
last_login_addr varchar(45),
|
last_login_host varchar(45),
|
||||||
last_login_agent varchar(255),
|
last_login_agent varchar(255),
|
||||||
created_at timestamp default current_timestamp,
|
created_at timestamp default current_timestamp,
|
||||||
updated_at timestamp default current_timestamp,
|
updated_at timestamp default current_timestamp,
|
||||||
@@ -33,7 +52,7 @@ comment on column admin.phone is '手机号码';
|
|||||||
comment on column admin.email is '邮箱';
|
comment on column admin.email is '邮箱';
|
||||||
comment on column admin.status is '状态:1-正常,0-禁用';
|
comment on column admin.status is '状态:1-正常,0-禁用';
|
||||||
comment on column admin.last_login is '最后登录时间';
|
comment on column admin.last_login is '最后登录时间';
|
||||||
comment on column admin.last_login_addr is '最后登录地址';
|
comment on column admin.last_login_host is '最后登录地址';
|
||||||
comment on column admin.last_login_agent is '最后登录代理';
|
comment on column admin.last_login_agent is '最后登录代理';
|
||||||
comment on column admin.created_at is '创建时间';
|
comment on column admin.created_at is '创建时间';
|
||||||
comment on column admin.updated_at is '更新时间';
|
comment on column admin.updated_at is '更新时间';
|
||||||
@@ -90,7 +109,7 @@ create table "user" (
|
|||||||
contact_qq varchar(255),
|
contact_qq varchar(255),
|
||||||
contact_wechat varchar(255),
|
contact_wechat varchar(255),
|
||||||
last_login timestamp,
|
last_login timestamp,
|
||||||
last_login_addr varchar(45),
|
last_login_host varchar(45),
|
||||||
last_login_agent varchar(255),
|
last_login_agent varchar(255),
|
||||||
created_at timestamp default current_timestamp,
|
created_at timestamp default current_timestamp,
|
||||||
updated_at timestamp default current_timestamp,
|
updated_at timestamp default current_timestamp,
|
||||||
@@ -103,26 +122,26 @@ create index user_status_index on "user" (status);
|
|||||||
|
|
||||||
-- user表字段注释
|
-- user表字段注释
|
||||||
comment on table "user" is '用户表';
|
comment on table "user" is '用户表';
|
||||||
comment on column user.id is '用户ID';
|
comment on column "user".id is '用户ID';
|
||||||
comment on column user.admin_id is '管理员ID';
|
comment on column "user".admin_id is '管理员ID';
|
||||||
comment on column user.password is '用户密码';
|
comment on column "user".password is '用户密码';
|
||||||
comment on column user.username is '用户名';
|
comment on column "user".username is '用户名';
|
||||||
comment on column user.phone is '手机号码';
|
comment on column "user".phone is '手机号码';
|
||||||
comment on column user.name is '真实姓名';
|
comment on column "user".name is '真实姓名';
|
||||||
comment on column user.avatar is '头像URL';
|
comment on column "user".avatar is '头像URL';
|
||||||
comment on column user.status is '用户状态:1-正常,0-禁用';
|
comment on column "user".status is '用户状态:1-正常,0-禁用';
|
||||||
comment on column user.balance is '账户余额';
|
comment on column "user".balance is '账户余额';
|
||||||
comment on column user.id_type is '认证类型:0-未认证,1-个人认证,2-企业认证';
|
comment on column "user".id_type is '认证类型:0-未认证,1-个人认证,2-企业认证';
|
||||||
comment on column user.id_no is '身份证号或营业执照号';
|
comment on column "user".id_no is '身份证号或营业执照号';
|
||||||
comment on column user.id_token is '身份验证标识';
|
comment on column "user".id_token is '身份验证标识';
|
||||||
comment on column user.contact_qq is 'QQ联系方式';
|
comment on column "user".contact_qq is 'QQ联系方式';
|
||||||
comment on column user.contact_wechat is '微信联系方式';
|
comment on column "user".contact_wechat is '微信联系方式';
|
||||||
comment on column user.last_login is '最后登录时间';
|
comment on column "user".last_login is '最后登录时间';
|
||||||
comment on column user.last_login_addr is '最后登录地址';
|
comment on column "user".last_login_host is '最后登录地址';
|
||||||
comment on column user.last_login_agent is '最后登录代理';
|
comment on column "user".last_login_agent is '最后登录代理';
|
||||||
comment on column user.created_at is '创建时间';
|
comment on column "user".created_at is '创建时间';
|
||||||
comment on column user.updated_at is '更新时间';
|
comment on column "user".updated_at is '更新时间';
|
||||||
comment on column user.deleted_at is '删除时间';
|
comment on column "user".deleted_at is '删除时间';
|
||||||
|
|
||||||
-- user_role
|
-- user_role
|
||||||
drop table if exists user_role cascade;
|
drop table if exists user_role cascade;
|
||||||
@@ -150,6 +169,51 @@ comment on column user_role.deleted_at is '删除时间';
|
|||||||
|
|
||||||
-- endregion
|
-- endregion
|
||||||
|
|
||||||
|
-- ====================
|
||||||
|
-- region 客户端信息
|
||||||
|
-- ====================
|
||||||
|
|
||||||
|
drop table if exists client cascade;
|
||||||
|
create table client (
|
||||||
|
id serial primary key,
|
||||||
|
client_id varchar(255) not null unique,
|
||||||
|
client_secret varchar(255) not null,
|
||||||
|
redirect_uri varchar(255),
|
||||||
|
grant_code bool not null default false,
|
||||||
|
grant_client bool not null default false,
|
||||||
|
grant_refresh bool not null default false,
|
||||||
|
spec int not null,
|
||||||
|
name varchar(255) not null,
|
||||||
|
version int not null,
|
||||||
|
status int not null default 1,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
updated_at timestamp default current_timestamp,
|
||||||
|
deleted_at timestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
create index client_client_id_index on client (client_id);
|
||||||
|
create index client_name_index on client (name);
|
||||||
|
create index client_status_index on client (status);
|
||||||
|
|
||||||
|
-- client表字段注释
|
||||||
|
comment on table client is '客户端表';
|
||||||
|
comment on column client.id is '客户端ID';
|
||||||
|
comment on column client.client_id is 'OAuth2客户端标识符';
|
||||||
|
comment on column client.client_secret is 'OAuth2客户端密钥';
|
||||||
|
comment on column client.redirect_uri is 'OAuth2 重定向URI';
|
||||||
|
comment on column client.grant_code is '允许授权码授予';
|
||||||
|
comment on column client.grant_client is '允许客户端凭证授予';
|
||||||
|
comment on column client.grant_refresh is '允许刷新令牌授予';
|
||||||
|
comment on column client.spec is '安全规范:0-web,1-native,2-browser';
|
||||||
|
comment on column client.name is '名称';
|
||||||
|
comment on column client.version is '版本';
|
||||||
|
comment on column client.status is '状态:1-正常,0-禁用';
|
||||||
|
comment on column client.created_at is '创建时间';
|
||||||
|
comment on column client.updated_at is '更新时间';
|
||||||
|
comment on column client.deleted_at is '删除时间';
|
||||||
|
|
||||||
|
-- endregion
|
||||||
|
|
||||||
-- ====================
|
-- ====================
|
||||||
-- region 权限信息
|
-- region 权限信息
|
||||||
-- ====================
|
-- ====================
|
||||||
@@ -168,6 +232,7 @@ create table permission (
|
|||||||
deleted_at timestamp
|
deleted_at timestamp
|
||||||
);
|
);
|
||||||
create index permission_parent_id_index on permission (parent_id);
|
create index permission_parent_id_index on permission (parent_id);
|
||||||
|
create index permission_name_index on permission (name);
|
||||||
|
|
||||||
-- permission表字段注释
|
-- permission表字段注释
|
||||||
comment on table permission is '权限表';
|
comment on table permission is '权限表';
|
||||||
@@ -283,6 +348,32 @@ comment on column admin_role_permission_link.created_at is '创建时间';
|
|||||||
comment on column admin_role_permission_link.updated_at is '更新时间';
|
comment on column admin_role_permission_link.updated_at is '更新时间';
|
||||||
comment on column admin_role_permission_link.deleted_at is '删除时间';
|
comment on column admin_role_permission_link.deleted_at is '删除时间';
|
||||||
|
|
||||||
|
-- client_permission_link
|
||||||
|
drop table if exists client_permission_link cascade;
|
||||||
|
create table client_permission_link (
|
||||||
|
id serial primary key,
|
||||||
|
client_id int not null references client (id)
|
||||||
|
on update cascade
|
||||||
|
on delete cascade,
|
||||||
|
permission_id int not null references permission (id)
|
||||||
|
on update cascade
|
||||||
|
on delete cascade,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
updated_at timestamp default current_timestamp,
|
||||||
|
deleted_at timestamp
|
||||||
|
);
|
||||||
|
create index client_permission_link_client_id_index on client_permission_link (client_id);
|
||||||
|
create index client_permission_link_permission_id_index on client_permission_link (permission_id);
|
||||||
|
|
||||||
|
-- client_permission_link表字段注释
|
||||||
|
comment on table client_permission_link is '客户端权限关联表';
|
||||||
|
comment on column client_permission_link.id is '关联ID';
|
||||||
|
comment on column client_permission_link.client_id is '客户端ID';
|
||||||
|
comment on column client_permission_link.permission_id is '权限ID';
|
||||||
|
comment on column client_permission_link.created_at is '创建时间';
|
||||||
|
comment on column client_permission_link.updated_at is '更新时间';
|
||||||
|
comment on column client_permission_link.deleted_at is '删除时间';
|
||||||
|
|
||||||
-- endregion
|
-- endregion
|
||||||
|
|
||||||
-- ====================
|
-- ====================
|
||||||
@@ -324,19 +415,19 @@ create table whitelist (
|
|||||||
user_id int not null references "user" (id)
|
user_id int not null references "user" (id)
|
||||||
on update cascade
|
on update cascade
|
||||||
on delete cascade,
|
on delete cascade,
|
||||||
address varchar(45) not null,
|
host varchar(45) not null,
|
||||||
created_at timestamp default current_timestamp,
|
created_at timestamp default current_timestamp,
|
||||||
updated_at timestamp default current_timestamp,
|
updated_at timestamp default current_timestamp,
|
||||||
deleted_at timestamp
|
deleted_at timestamp
|
||||||
);
|
);
|
||||||
create index whitelist_user_id_index on whitelist (user_id);
|
create index whitelist_user_id_index on whitelist (user_id);
|
||||||
create index whitelist_address_index on whitelist (address);
|
create index whitelist_host_index on whitelist (host);
|
||||||
|
|
||||||
-- whitelist表字段注释
|
-- whitelist表字段注释
|
||||||
comment on table whitelist is '白名单表';
|
comment on table whitelist is '白名单表';
|
||||||
comment on column whitelist.id is '白名单ID';
|
comment on column whitelist.id is '白名单ID';
|
||||||
comment on column whitelist.user_id is '用户ID';
|
comment on column whitelist.user_id is '用户ID';
|
||||||
comment on column whitelist.address is 'IP地址';
|
comment on column whitelist.host is 'IP地址';
|
||||||
comment on column whitelist.created_at is '创建时间';
|
comment on column whitelist.created_at is '创建时间';
|
||||||
comment on column whitelist.updated_at is '更新时间';
|
comment on column whitelist.updated_at is '更新时间';
|
||||||
comment on column whitelist.deleted_at is '删除时间';
|
comment on column whitelist.deleted_at is '删除时间';
|
||||||
@@ -351,7 +442,7 @@ create table channel (
|
|||||||
node_id int references node (id) --
|
node_id int references node (id) --
|
||||||
on update cascade --
|
on update cascade --
|
||||||
on delete set null,
|
on delete set null,
|
||||||
user_addr varchar(255) not null,
|
user_host varchar(255) not null,
|
||||||
node_port int,
|
node_port int,
|
||||||
auth_ip bool not null default false,
|
auth_ip bool not null default false,
|
||||||
auth_pass bool not null default false,
|
auth_pass bool not null default false,
|
||||||
@@ -365,7 +456,7 @@ create table channel (
|
|||||||
);
|
);
|
||||||
create index channel_user_id_index on channel (user_id);
|
create index channel_user_id_index on channel (user_id);
|
||||||
create index channel_node_id_index on channel (node_id);
|
create index channel_node_id_index on channel (node_id);
|
||||||
create index channel_user_addr_index on channel (user_addr);
|
create index channel_user_host_index on channel (user_host);
|
||||||
create index channel_node_port_index on channel (node_port);
|
create index channel_node_port_index on channel (node_port);
|
||||||
create index channel_expiration_index on channel (expiration);
|
create index channel_expiration_index on channel (expiration);
|
||||||
|
|
||||||
@@ -374,7 +465,7 @@ comment on table channel is '通道表';
|
|||||||
comment on column channel.id is '通道ID';
|
comment on column channel.id is '通道ID';
|
||||||
comment on column channel.user_id is '用户ID';
|
comment on column channel.user_id is '用户ID';
|
||||||
comment on column channel.node_id is '节点ID';
|
comment on column channel.node_id is '节点ID';
|
||||||
comment on column channel.user_addr is '用户地址';
|
comment on column channel.user_host is '用户地址';
|
||||||
comment on column channel.node_port is '节点端口';
|
comment on column channel.node_port is '节点端口';
|
||||||
comment on column channel.auth_ip is 'IP认证';
|
comment on column channel.auth_ip is 'IP认证';
|
||||||
comment on column channel.auth_pass is '密码认证';
|
comment on column channel.auth_pass is '密码认证';
|
||||||
|
|||||||
47
web/auth.go
47
web/auth.go
@@ -1 +1,48 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"platform/web/common"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"platform/web/services"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Protect 创建针对单个路由的鉴权中间件
|
||||||
|
func Protect(permissions ...string) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
// 获取令牌
|
||||||
|
var header = c.Get("Authorization")
|
||||||
|
var token = strings.TrimPrefix(header, "Bearer ")
|
||||||
|
if token == "" {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(common.ErrResp{
|
||||||
|
Error: true,
|
||||||
|
Message: "没有权限",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证令牌
|
||||||
|
auth, err := services.Session.Find(c.Context(), token)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(common.ErrResp{
|
||||||
|
Error: true,
|
||||||
|
Message: "没有权限",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查权限
|
||||||
|
if len(permissions) > 0 && !auth.AnyPermission(permissions...) {
|
||||||
|
return c.Status(fiber.StatusForbidden).JSON(common.ErrResp{
|
||||||
|
Error: true,
|
||||||
|
Message: "拒绝访问",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将认证信息存储在上下文中
|
||||||
|
c.Locals("auth", auth)
|
||||||
|
c.Locals("access_token", token) // 存储原始令牌,便于后续操作
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
7
web/common/types.go
Normal file
7
web/common/types.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
// ErrResp 定义通用错误响应格式
|
||||||
|
type ErrResp struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Error bool `json:"error"`
|
||||||
|
}
|
||||||
19
web/error.go
Normal file
19
web/error.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ErrorHandler(c *fiber.Ctx, err error) error {
|
||||||
|
code := fiber.StatusInternalServerError
|
||||||
|
message := "服务器异常"
|
||||||
|
var e *fiber.Error
|
||||||
|
if errors.As(err, &e) {
|
||||||
|
code = e.Code
|
||||||
|
message = e.Message
|
||||||
|
}
|
||||||
|
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
|
||||||
|
return c.Status(code).SendString(message)
|
||||||
|
}
|
||||||
52
web/handlers/client.go
Normal file
52
web/handlers/client.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"platform/web/models"
|
||||||
|
q "platform/web/queries"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateClientReq struct {
|
||||||
|
ClientID string `query:"client_id"`
|
||||||
|
ClientSecret string `query:"client_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateClient(c *fiber.Ctx) error {
|
||||||
|
// 验证请求参数
|
||||||
|
req := new(CreateClientReq)
|
||||||
|
if err := c.QueryParser(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if req.ClientID == "" {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "client_id不能为空")
|
||||||
|
}
|
||||||
|
if req.ClientSecret == "" {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "client_secret不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建客户端
|
||||||
|
hashedSecret, err := bcrypt.GenerateFromPassword([]byte(req.ClientSecret), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client := &models.Client{
|
||||||
|
ClientID: req.ClientID,
|
||||||
|
ClientSecret: string(hashedSecret),
|
||||||
|
Name: "默认客户端 - " + time.Now().String(),
|
||||||
|
Spec: 0,
|
||||||
|
GrantCode: true,
|
||||||
|
GrantClient: true,
|
||||||
|
GrantRefresh: true,
|
||||||
|
Version: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = q.Client.Create(client)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(client)
|
||||||
|
}
|
||||||
107
web/handlers/login.go
Normal file
107
web/handlers/login.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"platform/web/models"
|
||||||
|
q "platform/web/queries"
|
||||||
|
"platform/web/services"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoginReq struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Remember bool `json:"remember"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginResp struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
Expires int64 `json:"expires"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Login(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
// 验证请求参数
|
||||||
|
req := new(LoginReq)
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if req.Username == "" {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "手机号不能为空")
|
||||||
|
}
|
||||||
|
if req.Password == "" {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "验证码不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
return loginByPhone(c, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loginByPhone(c *fiber.Ctx, req *LoginReq) error {
|
||||||
|
|
||||||
|
// 验证验证码
|
||||||
|
ok, err := services.Verifier.VerifySms(c.Context(), req.Username, req.Password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "验证码错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找用户 todo 获取权限信息
|
||||||
|
var tx = q.Q.Begin()
|
||||||
|
|
||||||
|
var user *models.User
|
||||||
|
user, err = tx.User.
|
||||||
|
Where(tx.User.Phone.Eq(req.Username)).
|
||||||
|
Take()
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果用户不存在,初始化用户 todo 保存默认权限信息
|
||||||
|
if user == nil {
|
||||||
|
user = &models.User{
|
||||||
|
Phone: req.Username,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户的登录时间
|
||||||
|
user.LastLogin = time.Now()
|
||||||
|
user.LastLoginHost = c.IP()
|
||||||
|
user.LastLoginAgent = c.Get("User-Agent")
|
||||||
|
if err := tx.User.Omit(q.User.AdminID).Save(user); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存到会话
|
||||||
|
auth := services.AuthContext{
|
||||||
|
Permissions: map[string]struct{}{
|
||||||
|
"user": {},
|
||||||
|
},
|
||||||
|
Payload: services.Payload{
|
||||||
|
Type: services.PayloadUser,
|
||||||
|
Id: user.ID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
duration := time.Hour * 24
|
||||||
|
if req.Remember {
|
||||||
|
duration *= 7
|
||||||
|
}
|
||||||
|
token, err := services.Session.Create(c.Context(), auth)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(LoginResp{
|
||||||
|
Token: token.AccessToken,
|
||||||
|
Expires: token.AccessTokenExpires.Unix(),
|
||||||
|
})
|
||||||
|
}
|
||||||
243
web/handlers/oauth.go
Normal file
243
web/handlers/oauth.go
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"platform/web/models"
|
||||||
|
q "platform/web/queries"
|
||||||
|
"platform/web/services"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// region Token
|
||||||
|
|
||||||
|
type TokenReq struct {
|
||||||
|
ClientID string `json:"client_id" form:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret" form:"client_secret"`
|
||||||
|
GrantType TokenGrantType `json:"grant_type" form:"grant_type"`
|
||||||
|
Code string `json:"code" form:"code"`
|
||||||
|
RedirectURI string `json:"redirect_uri" form:"redirect_uri"`
|
||||||
|
CodeVerifier string `json:"code_verifier" form:"code_verifier"`
|
||||||
|
RefreshToken string `json:"refresh_token" form:"refresh_token"`
|
||||||
|
Scope string `json:"scope" form:"scope"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenResp struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token,omitempty"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenErrResp struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
Description string `json:"error_description,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenGrantType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AuthorizationCode = TokenGrantType("authorization_code")
|
||||||
|
ClientCredentials = TokenGrantType("client_credentials")
|
||||||
|
RefreshToken = TokenGrantType("refresh_token")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token 处理 OAuth2.0 授权请求
|
||||||
|
func Token(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
// 验证请求参数
|
||||||
|
req := new(TokenReq)
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return sendError(c, services.ErrOauthInvalidRequest, "无法解析请求参数")
|
||||||
|
}
|
||||||
|
if req.GrantType == "" {
|
||||||
|
return sendError(c, services.ErrOauthInvalidRequest, "缺少必要参数:grant_type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基于授权类型处理请求
|
||||||
|
switch req.GrantType {
|
||||||
|
|
||||||
|
case AuthorizationCode:
|
||||||
|
return authorizationCode(c, req)
|
||||||
|
|
||||||
|
case ClientCredentials:
|
||||||
|
return clientCredentials(c, req)
|
||||||
|
|
||||||
|
case RefreshToken:
|
||||||
|
return refreshToken(c, req)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return sendError(c, services.ErrOauthUnsupportedGrantType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 授权码
|
||||||
|
func authorizationCode(c *fiber.Ctx, req *TokenReq) error {
|
||||||
|
if req.Code == "" {
|
||||||
|
return sendError(c, services.ErrOauthInvalidRequest, "缺少必要参数:code")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := protect(c, services.GrantTypeAuthorizationCode, req.ClientID, req.ClientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return sendError(c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := services.Auth.OauthAuthorizationCode(c.Context(), client, req.Code, req.RedirectURI, req.CodeVerifier)
|
||||||
|
if err != nil {
|
||||||
|
return sendError(c, err.(services.AuthServiceOauthError))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendSuccess(c, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端凭证
|
||||||
|
func clientCredentials(c *fiber.Ctx, req *TokenReq) error {
|
||||||
|
client, err := protect(c, services.GrantTypeClientCredentials, req.ClientID, req.ClientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return sendError(c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := strings.Split(req.Scope, ",")
|
||||||
|
token, err := services.Auth.OauthClientCredentials(c.Context(), client, scope)
|
||||||
|
if err != nil {
|
||||||
|
return sendError(c, err.(services.AuthServiceOauthError))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendSuccess(c, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新令牌
|
||||||
|
func refreshToken(c *fiber.Ctx, req *TokenReq) error {
|
||||||
|
if req.RefreshToken == "" {
|
||||||
|
return sendError(c, services.ErrOauthInvalidRequest, "缺少必要参数:refresh_token")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := protect(c, services.GrantTypeRefreshToken, req.ClientID, req.ClientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return sendError(c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := strings.Split(req.Scope, ",")
|
||||||
|
token, err := services.Auth.OauthRefreshToken(c.Context(), client, req.RefreshToken, scope)
|
||||||
|
if err != nil {
|
||||||
|
return sendError(c, err.(services.AuthServiceOauthError))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendSuccess(c, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查客户端凭证
|
||||||
|
func protect(c *fiber.Ctx, grant services.GrantType, clientId, clientSecret string) (*models.Client, error) {
|
||||||
|
header := c.Get("Authorization")
|
||||||
|
if header != "" {
|
||||||
|
basic := strings.TrimPrefix(header, "Basic ")
|
||||||
|
if basic != "" {
|
||||||
|
base, err := base64.URLEncoding.DecodeString(basic)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(string(base), ":", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
clientId = parts[0]
|
||||||
|
clientSecret = parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找客户端
|
||||||
|
if clientId == "" {
|
||||||
|
return nil, services.ErrOauthInvalidRequest
|
||||||
|
}
|
||||||
|
client, err := q.Client.Where(q.Client.ClientID.Eq(clientId)).Take()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, services.ErrOauthInvalidClient
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证客户端状态
|
||||||
|
if client.Status != 1 {
|
||||||
|
return nil, services.ErrOauthUnauthorizedClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证授权类型
|
||||||
|
switch grant {
|
||||||
|
case services.GrantTypeAuthorizationCode:
|
||||||
|
if !client.GrantCode {
|
||||||
|
return nil, services.ErrOauthUnauthorizedClient
|
||||||
|
}
|
||||||
|
case services.GrantTypeClientCredentials:
|
||||||
|
if !client.GrantClient || client.Spec != 0 {
|
||||||
|
return nil, services.ErrOauthUnauthorizedClient
|
||||||
|
}
|
||||||
|
case services.GrantTypeRefreshToken:
|
||||||
|
if !client.GrantRefresh {
|
||||||
|
return nil, services.ErrOauthUnauthorizedClient
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果客户端是 confidential,验证 client_secret,失败返回错误
|
||||||
|
if client.Spec == 0 {
|
||||||
|
if clientSecret == "" {
|
||||||
|
return nil, services.ErrOauthInvalidRequest
|
||||||
|
}
|
||||||
|
if bcrypt.CompareHashAndPassword([]byte(client.ClientSecret), []byte(clientSecret)) != nil {
|
||||||
|
return nil, services.ErrOauthInvalidClient
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送成功响应
|
||||||
|
func sendSuccess(c *fiber.Ctx, details *services.TokenDetails) error {
|
||||||
|
return c.JSON(TokenResp{
|
||||||
|
AccessToken: details.AccessToken,
|
||||||
|
TokenType: "Bearer",
|
||||||
|
ExpiresIn: int(time.Until(details.AccessTokenExpires).Seconds()),
|
||||||
|
RefreshToken: details.RefreshToken,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送错误响应
|
||||||
|
func sendError(c *fiber.Ctx, err error, description ...string) error {
|
||||||
|
var sErr services.AuthServiceOauthError
|
||||||
|
if errors.As(err, &sErr) {
|
||||||
|
status := fiber.StatusBadRequest
|
||||||
|
var desc string
|
||||||
|
switch {
|
||||||
|
case errors.Is(sErr, services.ErrOauthInvalidRequest):
|
||||||
|
desc = "无效的请求"
|
||||||
|
case errors.Is(sErr, services.ErrOauthInvalidClient):
|
||||||
|
status = fiber.StatusUnauthorized
|
||||||
|
desc = "无效的客户端凭证"
|
||||||
|
case errors.Is(sErr, services.ErrOauthInvalidGrant):
|
||||||
|
desc = "无效的授权凭证"
|
||||||
|
case errors.Is(sErr, services.ErrOauthInvalidScope):
|
||||||
|
desc = "无效的授权范围"
|
||||||
|
case errors.Is(sErr, services.ErrOauthUnauthorizedClient):
|
||||||
|
desc = "未授权的客户端"
|
||||||
|
case errors.Is(sErr, services.ErrOauthUnsupportedGrantType):
|
||||||
|
desc = "不支持的授权类型"
|
||||||
|
}
|
||||||
|
if len(description) > 0 {
|
||||||
|
desc = description[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(status).JSON(TokenErrResp{
|
||||||
|
Error: string(sErr),
|
||||||
|
Description: desc,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
44
web/handlers/verifier.go
Normal file
44
web/handlers/verifier.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"platform/web/services"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VerifierReq struct {
|
||||||
|
Purpose services.VerifierSmsPurpose `json:"purpose"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SmsCode(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
// 解析请求参数
|
||||||
|
req := new(VerifierReq)
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
match, err := regexp.MatchString(`^1[3-9]\d{9}$`, req.Phone)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "手机号格式错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送身份验证码
|
||||||
|
err = services.Verifier.SendSms(c.Context(), req.Phone, req.Purpose)
|
||||||
|
if err != nil {
|
||||||
|
var sErr services.VerifierServiceSendLimitErr
|
||||||
|
if errors.As(err, &sErr) {
|
||||||
|
return fiber.NewError(fiber.StatusTooManyRequests, strconv.Itoa(int(sErr)))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送成功
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ type Admin struct {
|
|||||||
Email string `gorm:"column:email;comment:邮箱" json:"email"` // 邮箱
|
Email string `gorm:"column:email;comment:邮箱" json:"email"` // 邮箱
|
||||||
Status int32 `gorm:"column:status;not null;default:1;comment:状态:1-正常,0-禁用" json:"status"` // 状态:1-正常,0-禁用
|
Status int32 `gorm:"column:status;not null;default:1;comment:状态:1-正常,0-禁用" json:"status"` // 状态:1-正常,0-禁用
|
||||||
LastLogin time.Time `gorm:"column:last_login;comment:最后登录时间" json:"last_login"` // 最后登录时间
|
LastLogin time.Time `gorm:"column:last_login;comment:最后登录时间" json:"last_login"` // 最后登录时间
|
||||||
LastLoginAddr string `gorm:"column:last_login_addr;comment:最后登录地址" json:"last_login_addr"` // 最后登录地址
|
LastLoginHost string `gorm:"column:last_login_host;comment:最后登录地址" json:"last_login_host"` // 最后登录地址
|
||||||
LastLoginAgent string `gorm:"column:last_login_agent;comment:最后登录代理" json:"last_login_agent"` // 最后登录代理
|
LastLoginAgent string `gorm:"column:last_login_agent;comment:最后登录代理" json:"last_login_agent"` // 最后登录代理
|
||||||
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
|
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
|
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type Channel struct {
|
|||||||
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:通道ID" json:"id"` // 通道ID
|
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:通道ID" json:"id"` // 通道ID
|
||||||
UserID int32 `gorm:"column:user_id;not null;comment:用户ID" json:"user_id"` // 用户ID
|
UserID int32 `gorm:"column:user_id;not null;comment:用户ID" json:"user_id"` // 用户ID
|
||||||
NodeID int32 `gorm:"column:node_id;comment:节点ID" json:"node_id"` // 节点ID
|
NodeID int32 `gorm:"column:node_id;comment:节点ID" json:"node_id"` // 节点ID
|
||||||
UserAddr string `gorm:"column:user_addr;not null;comment:用户地址" json:"user_addr"` // 用户地址
|
UserHost string `gorm:"column:user_host;not null;comment:用户地址" json:"user_host"` // 用户地址
|
||||||
NodePort int32 `gorm:"column:node_port;comment:节点端口" json:"node_port"` // 节点端口
|
NodePort int32 `gorm:"column:node_port;comment:节点端口" json:"node_port"` // 节点端口
|
||||||
AuthIP bool `gorm:"column:auth_ip;not null;comment:IP认证" json:"auth_ip"` // IP认证
|
AuthIP bool `gorm:"column:auth_ip;not null;comment:IP认证" json:"auth_ip"` // IP认证
|
||||||
AuthPass bool `gorm:"column:auth_pass;not null;comment:密码认证" json:"auth_pass"` // 密码认证
|
AuthPass bool `gorm:"column:auth_pass;not null;comment:密码认证" json:"auth_pass"` // 密码认证
|
||||||
|
|||||||
36
web/models/client.gen.go
Normal file
36
web/models/client.gen.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TableNameClient = "client"
|
||||||
|
|
||||||
|
// Client mapped from table <client>
|
||||||
|
type Client struct {
|
||||||
|
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:客户端ID" json:"id"` // 客户端ID
|
||||||
|
ClientID string `gorm:"column:client_id;not null;comment:OAuth2客户端标识符" json:"client_id"` // OAuth2客户端标识符
|
||||||
|
ClientSecret string `gorm:"column:client_secret;not null;comment:OAuth2客户端密钥" json:"client_secret"` // OAuth2客户端密钥
|
||||||
|
RedirectURI string `gorm:"column:redirect_uri;comment:OAuth2 重定向URI" json:"redirect_uri"` // OAuth2 重定向URI
|
||||||
|
GrantCode bool `gorm:"column:grant_code;not null;comment:允许授权码授予" json:"grant_code"` // 允许授权码授予
|
||||||
|
GrantClient bool `gorm:"column:grant_client;not null;comment:允许客户端凭证授予" json:"grant_client"` // 允许客户端凭证授予
|
||||||
|
GrantRefresh bool `gorm:"column:grant_refresh;not null;comment:允许刷新令牌授予" json:"grant_refresh"` // 允许刷新令牌授予
|
||||||
|
Spec int32 `gorm:"column:spec;not null;comment:安全规范:0-web,1-native,2-browser" json:"spec"` // 安全规范:0-web,1-native,2-browser
|
||||||
|
Name string `gorm:"column:name;not null;comment:名称" json:"name"` // 名称
|
||||||
|
Version int32 `gorm:"column:version;not null;comment:版本" json:"version"` // 版本
|
||||||
|
Status int32 `gorm:"column:status;not null;default:1;comment:状态:1-正常,0-禁用" json:"status"` // 状态:1-正常,0-禁用
|
||||||
|
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
|
||||||
|
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName Client's table name
|
||||||
|
func (*Client) TableName() string {
|
||||||
|
return TableNameClient
|
||||||
|
}
|
||||||
28
web/models/client_permission_link.gen.go
Normal file
28
web/models/client_permission_link.gen.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TableNameClientPermissionLink = "client_permission_link"
|
||||||
|
|
||||||
|
// ClientPermissionLink mapped from table <client_permission_link>
|
||||||
|
type ClientPermissionLink struct {
|
||||||
|
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:关联ID" json:"id"` // 关联ID
|
||||||
|
ClientID int32 `gorm:"column:client_id;not null;comment:客户端ID" json:"client_id"` // 客户端ID
|
||||||
|
PermissionID int32 `gorm:"column:permission_id;not null;comment:权限ID" json:"permission_id"` // 权限ID
|
||||||
|
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
|
||||||
|
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName ClientPermissionLink's table name
|
||||||
|
func (*ClientPermissionLink) TableName() string {
|
||||||
|
return TableNameClientPermissionLink
|
||||||
|
}
|
||||||
@@ -14,27 +14,27 @@ const TableNameUser = "user"
|
|||||||
|
|
||||||
// User mapped from table <user>
|
// User mapped from table <user>
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
|
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:用户ID" json:"id"` // 用户ID
|
||||||
AdminID int32 `gorm:"column:admin_id" json:"admin_id"`
|
AdminID int32 `gorm:"column:admin_id;comment:管理员ID" json:"admin_id"` // 管理员ID
|
||||||
Phone string `gorm:"column:phone;not null" json:"phone"`
|
Phone string `gorm:"column:phone;not null;comment:手机号码" json:"phone"` // 手机号码
|
||||||
Username string `gorm:"column:username" json:"username"`
|
Username string `gorm:"column:username;comment:用户名" json:"username"` // 用户名
|
||||||
Email string `gorm:"column:email" json:"email"`
|
Email string `gorm:"column:email" json:"email"`
|
||||||
Password string `gorm:"column:password" json:"password"`
|
Password string `gorm:"column:password;comment:用户密码" json:"password"` // 用户密码
|
||||||
Name string `gorm:"column:name" json:"name"`
|
Name string `gorm:"column:name;comment:真实姓名" json:"name"` // 真实姓名
|
||||||
Avatar string `gorm:"column:avatar" json:"avatar"`
|
Avatar string `gorm:"column:avatar;comment:头像URL" json:"avatar"` // 头像URL
|
||||||
Status int32 `gorm:"column:status;not null;default:1" json:"status"`
|
Status int32 `gorm:"column:status;not null;default:1;comment:用户状态:1-正常,0-禁用" json:"status"` // 用户状态:1-正常,0-禁用
|
||||||
Balance float64 `gorm:"column:balance;not null" json:"balance"`
|
Balance float64 `gorm:"column:balance;not null;comment:账户余额" json:"balance"` // 账户余额
|
||||||
IDType int32 `gorm:"column:id_type;not null" json:"id_type"`
|
IDType int32 `gorm:"column:id_type;not null;comment:认证类型:0-未认证,1-个人认证,2-企业认证" json:"id_type"` // 认证类型:0-未认证,1-个人认证,2-企业认证
|
||||||
IDNo string `gorm:"column:id_no" json:"id_no"`
|
IDNo string `gorm:"column:id_no;comment:身份证号或营业执照号" json:"id_no"` // 身份证号或营业执照号
|
||||||
IDToken string `gorm:"column:id_token" json:"id_token"`
|
IDToken string `gorm:"column:id_token;comment:身份验证标识" json:"id_token"` // 身份验证标识
|
||||||
ContactQq string `gorm:"column:contact_qq" json:"contact_qq"`
|
ContactQq string `gorm:"column:contact_qq;comment:QQ联系方式" json:"contact_qq"` // QQ联系方式
|
||||||
ContactWechat string `gorm:"column:contact_wechat" json:"contact_wechat"`
|
ContactWechat string `gorm:"column:contact_wechat;comment:微信联系方式" json:"contact_wechat"` // 微信联系方式
|
||||||
LastLogin time.Time `gorm:"column:last_login" json:"last_login"`
|
LastLogin time.Time `gorm:"column:last_login;comment:最后登录时间" json:"last_login"` // 最后登录时间
|
||||||
LastLoginAddr string `gorm:"column:last_login_addr" json:"last_login_addr"`
|
LastLoginHost string `gorm:"column:last_login_host;comment:最后登录地址" json:"last_login_host"` // 最后登录地址
|
||||||
LastLoginAgent string `gorm:"column:last_login_agent" json:"last_login_agent"`
|
LastLoginAgent string `gorm:"column:last_login_agent;comment:最后登录代理" json:"last_login_agent"` // 最后登录代理
|
||||||
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
|
||||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"`
|
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName User's table name
|
// TableName User's table name
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const TableNameWhitelist = "whitelist"
|
|||||||
type Whitelist struct {
|
type Whitelist struct {
|
||||||
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:白名单ID" json:"id"` // 白名单ID
|
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:白名单ID" json:"id"` // 白名单ID
|
||||||
UserID int32 `gorm:"column:user_id;not null;comment:用户ID" json:"user_id"` // 用户ID
|
UserID int32 `gorm:"column:user_id;not null;comment:用户ID" json:"user_id"` // 用户ID
|
||||||
Address string `gorm:"column:address;not null;comment:IP地址" json:"address"` // IP地址
|
Host string `gorm:"column:host;not null;comment:IP地址" json:"host"` // IP地址
|
||||||
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
|
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
|
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
|
||||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
|
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func newAdmin(db *gorm.DB, opts ...gen.DOOption) admin {
|
|||||||
_admin.Email = field.NewString(tableName, "email")
|
_admin.Email = field.NewString(tableName, "email")
|
||||||
_admin.Status = field.NewInt32(tableName, "status")
|
_admin.Status = field.NewInt32(tableName, "status")
|
||||||
_admin.LastLogin = field.NewTime(tableName, "last_login")
|
_admin.LastLogin = field.NewTime(tableName, "last_login")
|
||||||
_admin.LastLoginAddr = field.NewString(tableName, "last_login_addr")
|
_admin.LastLoginHost = field.NewString(tableName, "last_login_host")
|
||||||
_admin.LastLoginAgent = field.NewString(tableName, "last_login_agent")
|
_admin.LastLoginAgent = field.NewString(tableName, "last_login_agent")
|
||||||
_admin.CreatedAt = field.NewTime(tableName, "created_at")
|
_admin.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
_admin.UpdatedAt = field.NewTime(tableName, "updated_at")
|
_admin.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
@@ -60,7 +60,7 @@ type admin struct {
|
|||||||
Email field.String // 邮箱
|
Email field.String // 邮箱
|
||||||
Status field.Int32 // 状态:1-正常,0-禁用
|
Status field.Int32 // 状态:1-正常,0-禁用
|
||||||
LastLogin field.Time // 最后登录时间
|
LastLogin field.Time // 最后登录时间
|
||||||
LastLoginAddr field.String // 最后登录地址
|
LastLoginHost field.String // 最后登录地址
|
||||||
LastLoginAgent field.String // 最后登录代理
|
LastLoginAgent field.String // 最后登录代理
|
||||||
CreatedAt field.Time // 创建时间
|
CreatedAt field.Time // 创建时间
|
||||||
UpdatedAt field.Time // 更新时间
|
UpdatedAt field.Time // 更新时间
|
||||||
@@ -90,7 +90,7 @@ func (a *admin) updateTableName(table string) *admin {
|
|||||||
a.Email = field.NewString(table, "email")
|
a.Email = field.NewString(table, "email")
|
||||||
a.Status = field.NewInt32(table, "status")
|
a.Status = field.NewInt32(table, "status")
|
||||||
a.LastLogin = field.NewTime(table, "last_login")
|
a.LastLogin = field.NewTime(table, "last_login")
|
||||||
a.LastLoginAddr = field.NewString(table, "last_login_addr")
|
a.LastLoginHost = field.NewString(table, "last_login_host")
|
||||||
a.LastLoginAgent = field.NewString(table, "last_login_agent")
|
a.LastLoginAgent = field.NewString(table, "last_login_agent")
|
||||||
a.CreatedAt = field.NewTime(table, "created_at")
|
a.CreatedAt = field.NewTime(table, "created_at")
|
||||||
a.UpdatedAt = field.NewTime(table, "updated_at")
|
a.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
@@ -121,7 +121,7 @@ func (a *admin) fillFieldMap() {
|
|||||||
a.fieldMap["email"] = a.Email
|
a.fieldMap["email"] = a.Email
|
||||||
a.fieldMap["status"] = a.Status
|
a.fieldMap["status"] = a.Status
|
||||||
a.fieldMap["last_login"] = a.LastLogin
|
a.fieldMap["last_login"] = a.LastLogin
|
||||||
a.fieldMap["last_login_addr"] = a.LastLoginAddr
|
a.fieldMap["last_login_host"] = a.LastLoginHost
|
||||||
a.fieldMap["last_login_agent"] = a.LastLoginAgent
|
a.fieldMap["last_login_agent"] = a.LastLoginAgent
|
||||||
a.fieldMap["created_at"] = a.CreatedAt
|
a.fieldMap["created_at"] = a.CreatedAt
|
||||||
a.fieldMap["updated_at"] = a.UpdatedAt
|
a.fieldMap["updated_at"] = a.UpdatedAt
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func newChannel(db *gorm.DB, opts ...gen.DOOption) channel {
|
|||||||
_channel.ID = field.NewInt32(tableName, "id")
|
_channel.ID = field.NewInt32(tableName, "id")
|
||||||
_channel.UserID = field.NewInt32(tableName, "user_id")
|
_channel.UserID = field.NewInt32(tableName, "user_id")
|
||||||
_channel.NodeID = field.NewInt32(tableName, "node_id")
|
_channel.NodeID = field.NewInt32(tableName, "node_id")
|
||||||
_channel.UserAddr = field.NewString(tableName, "user_addr")
|
_channel.UserHost = field.NewString(tableName, "user_host")
|
||||||
_channel.NodePort = field.NewInt32(tableName, "node_port")
|
_channel.NodePort = field.NewInt32(tableName, "node_port")
|
||||||
_channel.AuthIP = field.NewBool(tableName, "auth_ip")
|
_channel.AuthIP = field.NewBool(tableName, "auth_ip")
|
||||||
_channel.AuthPass = field.NewBool(tableName, "auth_pass")
|
_channel.AuthPass = field.NewBool(tableName, "auth_pass")
|
||||||
@@ -54,7 +54,7 @@ type channel struct {
|
|||||||
ID field.Int32 // 通道ID
|
ID field.Int32 // 通道ID
|
||||||
UserID field.Int32 // 用户ID
|
UserID field.Int32 // 用户ID
|
||||||
NodeID field.Int32 // 节点ID
|
NodeID field.Int32 // 节点ID
|
||||||
UserAddr field.String // 用户地址
|
UserHost field.String // 用户地址
|
||||||
NodePort field.Int32 // 节点端口
|
NodePort field.Int32 // 节点端口
|
||||||
AuthIP field.Bool // IP认证
|
AuthIP field.Bool // IP认证
|
||||||
AuthPass field.Bool // 密码认证
|
AuthPass field.Bool // 密码认证
|
||||||
@@ -84,7 +84,7 @@ func (c *channel) updateTableName(table string) *channel {
|
|||||||
c.ID = field.NewInt32(table, "id")
|
c.ID = field.NewInt32(table, "id")
|
||||||
c.UserID = field.NewInt32(table, "user_id")
|
c.UserID = field.NewInt32(table, "user_id")
|
||||||
c.NodeID = field.NewInt32(table, "node_id")
|
c.NodeID = field.NewInt32(table, "node_id")
|
||||||
c.UserAddr = field.NewString(table, "user_addr")
|
c.UserHost = field.NewString(table, "user_host")
|
||||||
c.NodePort = field.NewInt32(table, "node_port")
|
c.NodePort = field.NewInt32(table, "node_port")
|
||||||
c.AuthIP = field.NewBool(table, "auth_ip")
|
c.AuthIP = field.NewBool(table, "auth_ip")
|
||||||
c.AuthPass = field.NewBool(table, "auth_pass")
|
c.AuthPass = field.NewBool(table, "auth_pass")
|
||||||
@@ -115,7 +115,7 @@ func (c *channel) fillFieldMap() {
|
|||||||
c.fieldMap["id"] = c.ID
|
c.fieldMap["id"] = c.ID
|
||||||
c.fieldMap["user_id"] = c.UserID
|
c.fieldMap["user_id"] = c.UserID
|
||||||
c.fieldMap["node_id"] = c.NodeID
|
c.fieldMap["node_id"] = c.NodeID
|
||||||
c.fieldMap["user_addr"] = c.UserAddr
|
c.fieldMap["user_host"] = c.UserHost
|
||||||
c.fieldMap["node_port"] = c.NodePort
|
c.fieldMap["node_port"] = c.NodePort
|
||||||
c.fieldMap["auth_ip"] = c.AuthIP
|
c.fieldMap["auth_ip"] = c.AuthIP
|
||||||
c.fieldMap["auth_pass"] = c.AuthPass
|
c.fieldMap["auth_pass"] = c.AuthPass
|
||||||
|
|||||||
371
web/queries/client.gen.go
Normal file
371
web/queries/client.gen.go
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package queries
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"gorm.io/gorm/schema"
|
||||||
|
|
||||||
|
"gorm.io/gen"
|
||||||
|
"gorm.io/gen/field"
|
||||||
|
|
||||||
|
"gorm.io/plugin/dbresolver"
|
||||||
|
|
||||||
|
"platform/web/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newClient(db *gorm.DB, opts ...gen.DOOption) client {
|
||||||
|
_client := client{}
|
||||||
|
|
||||||
|
_client.clientDo.UseDB(db, opts...)
|
||||||
|
_client.clientDo.UseModel(&models.Client{})
|
||||||
|
|
||||||
|
tableName := _client.clientDo.TableName()
|
||||||
|
_client.ALL = field.NewAsterisk(tableName)
|
||||||
|
_client.ID = field.NewInt32(tableName, "id")
|
||||||
|
_client.ClientID = field.NewString(tableName, "client_id")
|
||||||
|
_client.ClientSecret = field.NewString(tableName, "client_secret")
|
||||||
|
_client.RedirectURI = field.NewString(tableName, "redirect_uri")
|
||||||
|
_client.GrantCode = field.NewBool(tableName, "grant_code")
|
||||||
|
_client.GrantClient = field.NewBool(tableName, "grant_client")
|
||||||
|
_client.GrantRefresh = field.NewBool(tableName, "grant_refresh")
|
||||||
|
_client.Spec = field.NewInt32(tableName, "spec")
|
||||||
|
_client.Name = field.NewString(tableName, "name")
|
||||||
|
_client.Version = field.NewInt32(tableName, "version")
|
||||||
|
_client.Status = field.NewInt32(tableName, "status")
|
||||||
|
_client.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
|
_client.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
|
_client.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||||
|
|
||||||
|
_client.fillFieldMap()
|
||||||
|
|
||||||
|
return _client
|
||||||
|
}
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
clientDo
|
||||||
|
|
||||||
|
ALL field.Asterisk
|
||||||
|
ID field.Int32 // 客户端ID
|
||||||
|
ClientID field.String // OAuth2客户端标识符
|
||||||
|
ClientSecret field.String // OAuth2客户端密钥
|
||||||
|
RedirectURI field.String // OAuth2 重定向URI
|
||||||
|
GrantCode field.Bool // 允许授权码授予
|
||||||
|
GrantClient field.Bool // 允许客户端凭证授予
|
||||||
|
GrantRefresh field.Bool // 允许刷新令牌授予
|
||||||
|
Spec field.Int32 // 安全规范:0-web,1-native,2-browser
|
||||||
|
Name field.String // 名称
|
||||||
|
Version field.Int32 // 版本
|
||||||
|
Status field.Int32 // 状态:1-正常,0-禁用
|
||||||
|
CreatedAt field.Time // 创建时间
|
||||||
|
UpdatedAt field.Time // 更新时间
|
||||||
|
DeletedAt field.Field // 删除时间
|
||||||
|
|
||||||
|
fieldMap map[string]field.Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c client) Table(newTableName string) *client {
|
||||||
|
c.clientDo.UseTable(newTableName)
|
||||||
|
return c.updateTableName(newTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c client) As(alias string) *client {
|
||||||
|
c.clientDo.DO = *(c.clientDo.As(alias).(*gen.DO))
|
||||||
|
return c.updateTableName(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) updateTableName(table string) *client {
|
||||||
|
c.ALL = field.NewAsterisk(table)
|
||||||
|
c.ID = field.NewInt32(table, "id")
|
||||||
|
c.ClientID = field.NewString(table, "client_id")
|
||||||
|
c.ClientSecret = field.NewString(table, "client_secret")
|
||||||
|
c.RedirectURI = field.NewString(table, "redirect_uri")
|
||||||
|
c.GrantCode = field.NewBool(table, "grant_code")
|
||||||
|
c.GrantClient = field.NewBool(table, "grant_client")
|
||||||
|
c.GrantRefresh = field.NewBool(table, "grant_refresh")
|
||||||
|
c.Spec = field.NewInt32(table, "spec")
|
||||||
|
c.Name = field.NewString(table, "name")
|
||||||
|
c.Version = field.NewInt32(table, "version")
|
||||||
|
c.Status = field.NewInt32(table, "status")
|
||||||
|
c.CreatedAt = field.NewTime(table, "created_at")
|
||||||
|
c.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
|
c.DeletedAt = field.NewField(table, "deleted_at")
|
||||||
|
|
||||||
|
c.fillFieldMap()
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||||
|
_f, ok := c.fieldMap[fieldName]
|
||||||
|
if !ok || _f == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
_oe, ok := _f.(field.OrderExpr)
|
||||||
|
return _oe, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) fillFieldMap() {
|
||||||
|
c.fieldMap = make(map[string]field.Expr, 14)
|
||||||
|
c.fieldMap["id"] = c.ID
|
||||||
|
c.fieldMap["client_id"] = c.ClientID
|
||||||
|
c.fieldMap["client_secret"] = c.ClientSecret
|
||||||
|
c.fieldMap["redirect_uri"] = c.RedirectURI
|
||||||
|
c.fieldMap["grant_code"] = c.GrantCode
|
||||||
|
c.fieldMap["grant_client"] = c.GrantClient
|
||||||
|
c.fieldMap["grant_refresh"] = c.GrantRefresh
|
||||||
|
c.fieldMap["spec"] = c.Spec
|
||||||
|
c.fieldMap["name"] = c.Name
|
||||||
|
c.fieldMap["version"] = c.Version
|
||||||
|
c.fieldMap["status"] = c.Status
|
||||||
|
c.fieldMap["created_at"] = c.CreatedAt
|
||||||
|
c.fieldMap["updated_at"] = c.UpdatedAt
|
||||||
|
c.fieldMap["deleted_at"] = c.DeletedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c client) clone(db *gorm.DB) client {
|
||||||
|
c.clientDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c client) replaceDB(db *gorm.DB) client {
|
||||||
|
c.clientDo.ReplaceDB(db)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientDo struct{ gen.DO }
|
||||||
|
|
||||||
|
func (c clientDo) Debug() *clientDo {
|
||||||
|
return c.withDO(c.DO.Debug())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) WithContext(ctx context.Context) *clientDo {
|
||||||
|
return c.withDO(c.DO.WithContext(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) ReadDB() *clientDo {
|
||||||
|
return c.Clauses(dbresolver.Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) WriteDB() *clientDo {
|
||||||
|
return c.Clauses(dbresolver.Write)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Session(config *gorm.Session) *clientDo {
|
||||||
|
return c.withDO(c.DO.Session(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Clauses(conds ...clause.Expression) *clientDo {
|
||||||
|
return c.withDO(c.DO.Clauses(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Returning(value interface{}, columns ...string) *clientDo {
|
||||||
|
return c.withDO(c.DO.Returning(value, columns...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Not(conds ...gen.Condition) *clientDo {
|
||||||
|
return c.withDO(c.DO.Not(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Or(conds ...gen.Condition) *clientDo {
|
||||||
|
return c.withDO(c.DO.Or(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Select(conds ...field.Expr) *clientDo {
|
||||||
|
return c.withDO(c.DO.Select(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Where(conds ...gen.Condition) *clientDo {
|
||||||
|
return c.withDO(c.DO.Where(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Order(conds ...field.Expr) *clientDo {
|
||||||
|
return c.withDO(c.DO.Order(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Distinct(cols ...field.Expr) *clientDo {
|
||||||
|
return c.withDO(c.DO.Distinct(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Omit(cols ...field.Expr) *clientDo {
|
||||||
|
return c.withDO(c.DO.Omit(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Join(table schema.Tabler, on ...field.Expr) *clientDo {
|
||||||
|
return c.withDO(c.DO.Join(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) LeftJoin(table schema.Tabler, on ...field.Expr) *clientDo {
|
||||||
|
return c.withDO(c.DO.LeftJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) RightJoin(table schema.Tabler, on ...field.Expr) *clientDo {
|
||||||
|
return c.withDO(c.DO.RightJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Group(cols ...field.Expr) *clientDo {
|
||||||
|
return c.withDO(c.DO.Group(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Having(conds ...gen.Condition) *clientDo {
|
||||||
|
return c.withDO(c.DO.Having(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Limit(limit int) *clientDo {
|
||||||
|
return c.withDO(c.DO.Limit(limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Offset(offset int) *clientDo {
|
||||||
|
return c.withDO(c.DO.Offset(offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *clientDo {
|
||||||
|
return c.withDO(c.DO.Scopes(funcs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Unscoped() *clientDo {
|
||||||
|
return c.withDO(c.DO.Unscoped())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Create(values ...*models.Client) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.DO.Create(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) CreateInBatches(values []*models.Client, batchSize int) error {
|
||||||
|
return c.DO.CreateInBatches(values, batchSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save : !!! underlying implementation is different with GORM
|
||||||
|
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
|
||||||
|
func (c clientDo) Save(values ...*models.Client) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.DO.Save(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) First() (*models.Client, error) {
|
||||||
|
if result, err := c.DO.First(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.Client), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Take() (*models.Client, error) {
|
||||||
|
if result, err := c.DO.Take(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.Client), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Last() (*models.Client, error) {
|
||||||
|
if result, err := c.DO.Last(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.Client), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Find() ([]*models.Client, error) {
|
||||||
|
result, err := c.DO.Find()
|
||||||
|
return result.([]*models.Client), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*models.Client, err error) {
|
||||||
|
buf := make([]*models.Client, 0, batchSize)
|
||||||
|
err = c.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
|
||||||
|
defer func() { results = append(results, buf...) }()
|
||||||
|
return fc(tx, batch)
|
||||||
|
})
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) FindInBatches(result *[]*models.Client, batchSize int, fc func(tx gen.Dao, batch int) error) error {
|
||||||
|
return c.DO.FindInBatches(result, batchSize, fc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Attrs(attrs ...field.AssignExpr) *clientDo {
|
||||||
|
return c.withDO(c.DO.Attrs(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Assign(attrs ...field.AssignExpr) *clientDo {
|
||||||
|
return c.withDO(c.DO.Assign(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Joins(fields ...field.RelationField) *clientDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
c = *c.withDO(c.DO.Joins(_f))
|
||||||
|
}
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Preload(fields ...field.RelationField) *clientDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
c = *c.withDO(c.DO.Preload(_f))
|
||||||
|
}
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) FirstOrInit() (*models.Client, error) {
|
||||||
|
if result, err := c.DO.FirstOrInit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.Client), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) FirstOrCreate() (*models.Client, error) {
|
||||||
|
if result, err := c.DO.FirstOrCreate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.Client), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) FindByPage(offset int, limit int) (result []*models.Client, count int64, err error) {
|
||||||
|
result, err = c.Offset(offset).Limit(limit).Find()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if size := len(result); 0 < limit && 0 < size && size < limit {
|
||||||
|
count = int64(size + offset)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err = c.Offset(-1).Limit(-1).Count()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
|
||||||
|
count, err = c.Count()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.Offset(offset).Limit(limit).Scan(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Scan(result interface{}) (err error) {
|
||||||
|
return c.DO.Scan(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientDo) Delete(models ...*models.Client) (result gen.ResultInfo, err error) {
|
||||||
|
return c.DO.Delete(models)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientDo) withDO(do gen.Dao) *clientDo {
|
||||||
|
c.DO = *do.(*gen.DO)
|
||||||
|
return c
|
||||||
|
}
|
||||||
339
web/queries/client_permission_link.gen.go
Normal file
339
web/queries/client_permission_link.gen.go
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package queries
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"gorm.io/gorm/schema"
|
||||||
|
|
||||||
|
"gorm.io/gen"
|
||||||
|
"gorm.io/gen/field"
|
||||||
|
|
||||||
|
"gorm.io/plugin/dbresolver"
|
||||||
|
|
||||||
|
"platform/web/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newClientPermissionLink(db *gorm.DB, opts ...gen.DOOption) clientPermissionLink {
|
||||||
|
_clientPermissionLink := clientPermissionLink{}
|
||||||
|
|
||||||
|
_clientPermissionLink.clientPermissionLinkDo.UseDB(db, opts...)
|
||||||
|
_clientPermissionLink.clientPermissionLinkDo.UseModel(&models.ClientPermissionLink{})
|
||||||
|
|
||||||
|
tableName := _clientPermissionLink.clientPermissionLinkDo.TableName()
|
||||||
|
_clientPermissionLink.ALL = field.NewAsterisk(tableName)
|
||||||
|
_clientPermissionLink.ID = field.NewInt32(tableName, "id")
|
||||||
|
_clientPermissionLink.ClientID = field.NewInt32(tableName, "client_id")
|
||||||
|
_clientPermissionLink.PermissionID = field.NewInt32(tableName, "permission_id")
|
||||||
|
_clientPermissionLink.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
|
_clientPermissionLink.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
|
_clientPermissionLink.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||||
|
|
||||||
|
_clientPermissionLink.fillFieldMap()
|
||||||
|
|
||||||
|
return _clientPermissionLink
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientPermissionLink struct {
|
||||||
|
clientPermissionLinkDo
|
||||||
|
|
||||||
|
ALL field.Asterisk
|
||||||
|
ID field.Int32 // 关联ID
|
||||||
|
ClientID field.Int32 // 客户端ID
|
||||||
|
PermissionID field.Int32 // 权限ID
|
||||||
|
CreatedAt field.Time // 创建时间
|
||||||
|
UpdatedAt field.Time // 更新时间
|
||||||
|
DeletedAt field.Field // 删除时间
|
||||||
|
|
||||||
|
fieldMap map[string]field.Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLink) Table(newTableName string) *clientPermissionLink {
|
||||||
|
c.clientPermissionLinkDo.UseTable(newTableName)
|
||||||
|
return c.updateTableName(newTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLink) As(alias string) *clientPermissionLink {
|
||||||
|
c.clientPermissionLinkDo.DO = *(c.clientPermissionLinkDo.As(alias).(*gen.DO))
|
||||||
|
return c.updateTableName(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientPermissionLink) updateTableName(table string) *clientPermissionLink {
|
||||||
|
c.ALL = field.NewAsterisk(table)
|
||||||
|
c.ID = field.NewInt32(table, "id")
|
||||||
|
c.ClientID = field.NewInt32(table, "client_id")
|
||||||
|
c.PermissionID = field.NewInt32(table, "permission_id")
|
||||||
|
c.CreatedAt = field.NewTime(table, "created_at")
|
||||||
|
c.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
|
c.DeletedAt = field.NewField(table, "deleted_at")
|
||||||
|
|
||||||
|
c.fillFieldMap()
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientPermissionLink) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||||
|
_f, ok := c.fieldMap[fieldName]
|
||||||
|
if !ok || _f == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
_oe, ok := _f.(field.OrderExpr)
|
||||||
|
return _oe, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientPermissionLink) fillFieldMap() {
|
||||||
|
c.fieldMap = make(map[string]field.Expr, 6)
|
||||||
|
c.fieldMap["id"] = c.ID
|
||||||
|
c.fieldMap["client_id"] = c.ClientID
|
||||||
|
c.fieldMap["permission_id"] = c.PermissionID
|
||||||
|
c.fieldMap["created_at"] = c.CreatedAt
|
||||||
|
c.fieldMap["updated_at"] = c.UpdatedAt
|
||||||
|
c.fieldMap["deleted_at"] = c.DeletedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLink) clone(db *gorm.DB) clientPermissionLink {
|
||||||
|
c.clientPermissionLinkDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLink) replaceDB(db *gorm.DB) clientPermissionLink {
|
||||||
|
c.clientPermissionLinkDo.ReplaceDB(db)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientPermissionLinkDo struct{ gen.DO }
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Debug() *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Debug())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) WithContext(ctx context.Context) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.WithContext(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) ReadDB() *clientPermissionLinkDo {
|
||||||
|
return c.Clauses(dbresolver.Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) WriteDB() *clientPermissionLinkDo {
|
||||||
|
return c.Clauses(dbresolver.Write)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Session(config *gorm.Session) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Session(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Clauses(conds ...clause.Expression) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Clauses(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Returning(value interface{}, columns ...string) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Returning(value, columns...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Not(conds ...gen.Condition) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Not(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Or(conds ...gen.Condition) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Or(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Select(conds ...field.Expr) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Select(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Where(conds ...gen.Condition) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Where(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Order(conds ...field.Expr) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Order(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Distinct(cols ...field.Expr) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Distinct(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Omit(cols ...field.Expr) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Omit(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Join(table schema.Tabler, on ...field.Expr) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Join(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) LeftJoin(table schema.Tabler, on ...field.Expr) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.LeftJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) RightJoin(table schema.Tabler, on ...field.Expr) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.RightJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Group(cols ...field.Expr) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Group(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Having(conds ...gen.Condition) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Having(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Limit(limit int) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Limit(limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Offset(offset int) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Offset(offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Scopes(funcs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Unscoped() *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Unscoped())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Create(values ...*models.ClientPermissionLink) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.DO.Create(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) CreateInBatches(values []*models.ClientPermissionLink, batchSize int) error {
|
||||||
|
return c.DO.CreateInBatches(values, batchSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save : !!! underlying implementation is different with GORM
|
||||||
|
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
|
||||||
|
func (c clientPermissionLinkDo) Save(values ...*models.ClientPermissionLink) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.DO.Save(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) First() (*models.ClientPermissionLink, error) {
|
||||||
|
if result, err := c.DO.First(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.ClientPermissionLink), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Take() (*models.ClientPermissionLink, error) {
|
||||||
|
if result, err := c.DO.Take(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.ClientPermissionLink), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Last() (*models.ClientPermissionLink, error) {
|
||||||
|
if result, err := c.DO.Last(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.ClientPermissionLink), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Find() ([]*models.ClientPermissionLink, error) {
|
||||||
|
result, err := c.DO.Find()
|
||||||
|
return result.([]*models.ClientPermissionLink), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*models.ClientPermissionLink, err error) {
|
||||||
|
buf := make([]*models.ClientPermissionLink, 0, batchSize)
|
||||||
|
err = c.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
|
||||||
|
defer func() { results = append(results, buf...) }()
|
||||||
|
return fc(tx, batch)
|
||||||
|
})
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) FindInBatches(result *[]*models.ClientPermissionLink, batchSize int, fc func(tx gen.Dao, batch int) error) error {
|
||||||
|
return c.DO.FindInBatches(result, batchSize, fc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Attrs(attrs ...field.AssignExpr) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Attrs(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Assign(attrs ...field.AssignExpr) *clientPermissionLinkDo {
|
||||||
|
return c.withDO(c.DO.Assign(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Joins(fields ...field.RelationField) *clientPermissionLinkDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
c = *c.withDO(c.DO.Joins(_f))
|
||||||
|
}
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Preload(fields ...field.RelationField) *clientPermissionLinkDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
c = *c.withDO(c.DO.Preload(_f))
|
||||||
|
}
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) FirstOrInit() (*models.ClientPermissionLink, error) {
|
||||||
|
if result, err := c.DO.FirstOrInit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.ClientPermissionLink), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) FirstOrCreate() (*models.ClientPermissionLink, error) {
|
||||||
|
if result, err := c.DO.FirstOrCreate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.ClientPermissionLink), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) FindByPage(offset int, limit int) (result []*models.ClientPermissionLink, count int64, err error) {
|
||||||
|
result, err = c.Offset(offset).Limit(limit).Find()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if size := len(result); 0 < limit && 0 < size && size < limit {
|
||||||
|
count = int64(size + offset)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err = c.Offset(-1).Limit(-1).Count()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
|
||||||
|
count, err = c.Count()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.Offset(offset).Limit(limit).Scan(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Scan(result interface{}) (err error) {
|
||||||
|
return c.DO.Scan(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientPermissionLinkDo) Delete(models ...*models.ClientPermissionLink) (result gen.ResultInfo, err error) {
|
||||||
|
return c.DO.Delete(models)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientPermissionLinkDo) withDO(do gen.Dao) *clientPermissionLinkDo {
|
||||||
|
c.DO = *do.(*gen.DO)
|
||||||
|
return c
|
||||||
|
}
|
||||||
@@ -23,6 +23,8 @@ var (
|
|||||||
AdminRolePermissionLink *adminRolePermissionLink
|
AdminRolePermissionLink *adminRolePermissionLink
|
||||||
Bill *bill
|
Bill *bill
|
||||||
Channel *channel
|
Channel *channel
|
||||||
|
Client *client
|
||||||
|
ClientPermissionLink *clientPermissionLink
|
||||||
Node *node
|
Node *node
|
||||||
Permission *permission
|
Permission *permission
|
||||||
Product *product
|
Product *product
|
||||||
@@ -47,6 +49,8 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
|||||||
AdminRolePermissionLink = &Q.AdminRolePermissionLink
|
AdminRolePermissionLink = &Q.AdminRolePermissionLink
|
||||||
Bill = &Q.Bill
|
Bill = &Q.Bill
|
||||||
Channel = &Q.Channel
|
Channel = &Q.Channel
|
||||||
|
Client = &Q.Client
|
||||||
|
ClientPermissionLink = &Q.ClientPermissionLink
|
||||||
Node = &Q.Node
|
Node = &Q.Node
|
||||||
Permission = &Q.Permission
|
Permission = &Q.Permission
|
||||||
Product = &Q.Product
|
Product = &Q.Product
|
||||||
@@ -72,6 +76,8 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
|
|||||||
AdminRolePermissionLink: newAdminRolePermissionLink(db, opts...),
|
AdminRolePermissionLink: newAdminRolePermissionLink(db, opts...),
|
||||||
Bill: newBill(db, opts...),
|
Bill: newBill(db, opts...),
|
||||||
Channel: newChannel(db, opts...),
|
Channel: newChannel(db, opts...),
|
||||||
|
Client: newClient(db, opts...),
|
||||||
|
ClientPermissionLink: newClientPermissionLink(db, opts...),
|
||||||
Node: newNode(db, opts...),
|
Node: newNode(db, opts...),
|
||||||
Permission: newPermission(db, opts...),
|
Permission: newPermission(db, opts...),
|
||||||
Product: newProduct(db, opts...),
|
Product: newProduct(db, opts...),
|
||||||
@@ -98,6 +104,8 @@ type Query struct {
|
|||||||
AdminRolePermissionLink adminRolePermissionLink
|
AdminRolePermissionLink adminRolePermissionLink
|
||||||
Bill bill
|
Bill bill
|
||||||
Channel channel
|
Channel channel
|
||||||
|
Client client
|
||||||
|
ClientPermissionLink clientPermissionLink
|
||||||
Node node
|
Node node
|
||||||
Permission permission
|
Permission permission
|
||||||
Product product
|
Product product
|
||||||
@@ -125,6 +133,8 @@ func (q *Query) clone(db *gorm.DB) *Query {
|
|||||||
AdminRolePermissionLink: q.AdminRolePermissionLink.clone(db),
|
AdminRolePermissionLink: q.AdminRolePermissionLink.clone(db),
|
||||||
Bill: q.Bill.clone(db),
|
Bill: q.Bill.clone(db),
|
||||||
Channel: q.Channel.clone(db),
|
Channel: q.Channel.clone(db),
|
||||||
|
Client: q.Client.clone(db),
|
||||||
|
ClientPermissionLink: q.ClientPermissionLink.clone(db),
|
||||||
Node: q.Node.clone(db),
|
Node: q.Node.clone(db),
|
||||||
Permission: q.Permission.clone(db),
|
Permission: q.Permission.clone(db),
|
||||||
Product: q.Product.clone(db),
|
Product: q.Product.clone(db),
|
||||||
@@ -159,6 +169,8 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query {
|
|||||||
AdminRolePermissionLink: q.AdminRolePermissionLink.replaceDB(db),
|
AdminRolePermissionLink: q.AdminRolePermissionLink.replaceDB(db),
|
||||||
Bill: q.Bill.replaceDB(db),
|
Bill: q.Bill.replaceDB(db),
|
||||||
Channel: q.Channel.replaceDB(db),
|
Channel: q.Channel.replaceDB(db),
|
||||||
|
Client: q.Client.replaceDB(db),
|
||||||
|
ClientPermissionLink: q.ClientPermissionLink.replaceDB(db),
|
||||||
Node: q.Node.replaceDB(db),
|
Node: q.Node.replaceDB(db),
|
||||||
Permission: q.Permission.replaceDB(db),
|
Permission: q.Permission.replaceDB(db),
|
||||||
Product: q.Product.replaceDB(db),
|
Product: q.Product.replaceDB(db),
|
||||||
@@ -183,6 +195,8 @@ type queryCtx struct {
|
|||||||
AdminRolePermissionLink *adminRolePermissionLinkDo
|
AdminRolePermissionLink *adminRolePermissionLinkDo
|
||||||
Bill *billDo
|
Bill *billDo
|
||||||
Channel *channelDo
|
Channel *channelDo
|
||||||
|
Client *clientDo
|
||||||
|
ClientPermissionLink *clientPermissionLinkDo
|
||||||
Node *nodeDo
|
Node *nodeDo
|
||||||
Permission *permissionDo
|
Permission *permissionDo
|
||||||
Product *productDo
|
Product *productDo
|
||||||
@@ -207,6 +221,8 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx {
|
|||||||
AdminRolePermissionLink: q.AdminRolePermissionLink.WithContext(ctx),
|
AdminRolePermissionLink: q.AdminRolePermissionLink.WithContext(ctx),
|
||||||
Bill: q.Bill.WithContext(ctx),
|
Bill: q.Bill.WithContext(ctx),
|
||||||
Channel: q.Channel.WithContext(ctx),
|
Channel: q.Channel.WithContext(ctx),
|
||||||
|
Client: q.Client.WithContext(ctx),
|
||||||
|
ClientPermissionLink: q.ClientPermissionLink.WithContext(ctx),
|
||||||
Node: q.Node.WithContext(ctx),
|
Node: q.Node.WithContext(ctx),
|
||||||
Permission: q.Permission.WithContext(ctx),
|
Permission: q.Permission.WithContext(ctx),
|
||||||
Product: q.Product.WithContext(ctx),
|
Product: q.Product.WithContext(ctx),
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func newUser(db *gorm.DB, opts ...gen.DOOption) user {
|
|||||||
_user.ContactQq = field.NewString(tableName, "contact_qq")
|
_user.ContactQq = field.NewString(tableName, "contact_qq")
|
||||||
_user.ContactWechat = field.NewString(tableName, "contact_wechat")
|
_user.ContactWechat = field.NewString(tableName, "contact_wechat")
|
||||||
_user.LastLogin = field.NewTime(tableName, "last_login")
|
_user.LastLogin = field.NewTime(tableName, "last_login")
|
||||||
_user.LastLoginAddr = field.NewString(tableName, "last_login_addr")
|
_user.LastLoginHost = field.NewString(tableName, "last_login_host")
|
||||||
_user.LastLoginAgent = field.NewString(tableName, "last_login_agent")
|
_user.LastLoginAgent = field.NewString(tableName, "last_login_agent")
|
||||||
_user.CreatedAt = field.NewTime(tableName, "created_at")
|
_user.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
_user.UpdatedAt = field.NewTime(tableName, "updated_at")
|
_user.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
@@ -58,27 +58,27 @@ type user struct {
|
|||||||
userDo
|
userDo
|
||||||
|
|
||||||
ALL field.Asterisk
|
ALL field.Asterisk
|
||||||
ID field.Int32
|
ID field.Int32 // 用户ID
|
||||||
AdminID field.Int32
|
AdminID field.Int32 // 管理员ID
|
||||||
Phone field.String
|
Phone field.String // 手机号码
|
||||||
Username field.String
|
Username field.String // 用户名
|
||||||
Email field.String
|
Email field.String
|
||||||
Password field.String
|
Password field.String // 用户密码
|
||||||
Name field.String
|
Name field.String // 真实姓名
|
||||||
Avatar field.String
|
Avatar field.String // 头像URL
|
||||||
Status field.Int32
|
Status field.Int32 // 用户状态:1-正常,0-禁用
|
||||||
Balance field.Float64
|
Balance field.Float64 // 账户余额
|
||||||
IDType field.Int32
|
IDType field.Int32 // 认证类型:0-未认证,1-个人认证,2-企业认证
|
||||||
IDNo field.String
|
IDNo field.String // 身份证号或营业执照号
|
||||||
IDToken field.String
|
IDToken field.String // 身份验证标识
|
||||||
ContactQq field.String
|
ContactQq field.String // QQ联系方式
|
||||||
ContactWechat field.String
|
ContactWechat field.String // 微信联系方式
|
||||||
LastLogin field.Time
|
LastLogin field.Time // 最后登录时间
|
||||||
LastLoginAddr field.String
|
LastLoginHost field.String // 最后登录地址
|
||||||
LastLoginAgent field.String
|
LastLoginAgent field.String // 最后登录代理
|
||||||
CreatedAt field.Time
|
CreatedAt field.Time // 创建时间
|
||||||
UpdatedAt field.Time
|
UpdatedAt field.Time // 更新时间
|
||||||
DeletedAt field.Field
|
DeletedAt field.Field // 删除时间
|
||||||
|
|
||||||
fieldMap map[string]field.Expr
|
fieldMap map[string]field.Expr
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,7 @@ func (u *user) updateTableName(table string) *user {
|
|||||||
u.ContactQq = field.NewString(table, "contact_qq")
|
u.ContactQq = field.NewString(table, "contact_qq")
|
||||||
u.ContactWechat = field.NewString(table, "contact_wechat")
|
u.ContactWechat = field.NewString(table, "contact_wechat")
|
||||||
u.LastLogin = field.NewTime(table, "last_login")
|
u.LastLogin = field.NewTime(table, "last_login")
|
||||||
u.LastLoginAddr = field.NewString(table, "last_login_addr")
|
u.LastLoginHost = field.NewString(table, "last_login_host")
|
||||||
u.LastLoginAgent = field.NewString(table, "last_login_agent")
|
u.LastLoginAgent = field.NewString(table, "last_login_agent")
|
||||||
u.CreatedAt = field.NewTime(table, "created_at")
|
u.CreatedAt = field.NewTime(table, "created_at")
|
||||||
u.UpdatedAt = field.NewTime(table, "updated_at")
|
u.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
@@ -149,7 +149,7 @@ func (u *user) fillFieldMap() {
|
|||||||
u.fieldMap["contact_qq"] = u.ContactQq
|
u.fieldMap["contact_qq"] = u.ContactQq
|
||||||
u.fieldMap["contact_wechat"] = u.ContactWechat
|
u.fieldMap["contact_wechat"] = u.ContactWechat
|
||||||
u.fieldMap["last_login"] = u.LastLogin
|
u.fieldMap["last_login"] = u.LastLogin
|
||||||
u.fieldMap["last_login_addr"] = u.LastLoginAddr
|
u.fieldMap["last_login_host"] = u.LastLoginHost
|
||||||
u.fieldMap["last_login_agent"] = u.LastLoginAgent
|
u.fieldMap["last_login_agent"] = u.LastLoginAgent
|
||||||
u.fieldMap["created_at"] = u.CreatedAt
|
u.fieldMap["created_at"] = u.CreatedAt
|
||||||
u.fieldMap["updated_at"] = u.UpdatedAt
|
u.fieldMap["updated_at"] = u.UpdatedAt
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func newWhitelist(db *gorm.DB, opts ...gen.DOOption) whitelist {
|
|||||||
_whitelist.ALL = field.NewAsterisk(tableName)
|
_whitelist.ALL = field.NewAsterisk(tableName)
|
||||||
_whitelist.ID = field.NewInt32(tableName, "id")
|
_whitelist.ID = field.NewInt32(tableName, "id")
|
||||||
_whitelist.UserID = field.NewInt32(tableName, "user_id")
|
_whitelist.UserID = field.NewInt32(tableName, "user_id")
|
||||||
_whitelist.Address = field.NewString(tableName, "address")
|
_whitelist.Host = field.NewString(tableName, "host")
|
||||||
_whitelist.CreatedAt = field.NewTime(tableName, "created_at")
|
_whitelist.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
_whitelist.UpdatedAt = field.NewTime(tableName, "updated_at")
|
_whitelist.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
_whitelist.DeletedAt = field.NewField(tableName, "deleted_at")
|
_whitelist.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||||
@@ -45,7 +45,7 @@ type whitelist struct {
|
|||||||
ALL field.Asterisk
|
ALL field.Asterisk
|
||||||
ID field.Int32 // 白名单ID
|
ID field.Int32 // 白名单ID
|
||||||
UserID field.Int32 // 用户ID
|
UserID field.Int32 // 用户ID
|
||||||
Address field.String // IP地址
|
Host field.String // IP地址
|
||||||
CreatedAt field.Time // 创建时间
|
CreatedAt field.Time // 创建时间
|
||||||
UpdatedAt field.Time // 更新时间
|
UpdatedAt field.Time // 更新时间
|
||||||
DeletedAt field.Field // 删除时间
|
DeletedAt field.Field // 删除时间
|
||||||
@@ -67,7 +67,7 @@ func (w *whitelist) updateTableName(table string) *whitelist {
|
|||||||
w.ALL = field.NewAsterisk(table)
|
w.ALL = field.NewAsterisk(table)
|
||||||
w.ID = field.NewInt32(table, "id")
|
w.ID = field.NewInt32(table, "id")
|
||||||
w.UserID = field.NewInt32(table, "user_id")
|
w.UserID = field.NewInt32(table, "user_id")
|
||||||
w.Address = field.NewString(table, "address")
|
w.Host = field.NewString(table, "host")
|
||||||
w.CreatedAt = field.NewTime(table, "created_at")
|
w.CreatedAt = field.NewTime(table, "created_at")
|
||||||
w.UpdatedAt = field.NewTime(table, "updated_at")
|
w.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
w.DeletedAt = field.NewField(table, "deleted_at")
|
w.DeletedAt = field.NewField(table, "deleted_at")
|
||||||
@@ -90,7 +90,7 @@ func (w *whitelist) fillFieldMap() {
|
|||||||
w.fieldMap = make(map[string]field.Expr, 6)
|
w.fieldMap = make(map[string]field.Expr, 6)
|
||||||
w.fieldMap["id"] = w.ID
|
w.fieldMap["id"] = w.ID
|
||||||
w.fieldMap["user_id"] = w.UserID
|
w.fieldMap["user_id"] = w.UserID
|
||||||
w.fieldMap["address"] = w.Address
|
w.fieldMap["host"] = w.Host
|
||||||
w.fieldMap["created_at"] = w.CreatedAt
|
w.fieldMap["created_at"] = w.CreatedAt
|
||||||
w.fieldMap["updated_at"] = w.UpdatedAt
|
w.fieldMap["updated_at"] = w.UpdatedAt
|
||||||
w.fieldMap["deleted_at"] = w.DeletedAt
|
w.fieldMap["deleted_at"] = w.DeletedAt
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"platform/web/handlers"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func UseRoute(app *fiber.App) {
|
func ApplyRouters(app *fiber.App) {
|
||||||
|
api := app.Group("/api")
|
||||||
|
|
||||||
|
// 认证路由
|
||||||
|
auth := api.Group("/auth")
|
||||||
|
auth.Post("/verify/sms", Protect(), handlers.SmsCode)
|
||||||
|
auth.Post("/login/sms", Protect(), handlers.Login)
|
||||||
|
auth.Post("/token", handlers.Token)
|
||||||
|
|
||||||
|
// 客户端路由
|
||||||
|
client := api.Group("/client")
|
||||||
|
client.Get("/test/create", handlers.CreateClient)
|
||||||
}
|
}
|
||||||
|
|||||||
85
web/services/auth.go
Normal file
85
web/services/auth.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"platform/web/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Auth = &authService{}
|
||||||
|
|
||||||
|
type authService struct{}
|
||||||
|
|
||||||
|
type AuthServiceError string
|
||||||
|
|
||||||
|
func (e AuthServiceError) Error() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthServiceOauthError string
|
||||||
|
|
||||||
|
func (e AuthServiceOauthError) Error() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrOauthInvalidRequest = AuthServiceOauthError("invalid_request")
|
||||||
|
ErrOauthInvalidClient = AuthServiceOauthError("invalid_client")
|
||||||
|
ErrOauthInvalidGrant = AuthServiceOauthError("invalid_grant")
|
||||||
|
ErrOauthInvalidScope = AuthServiceOauthError("invalid_scope")
|
||||||
|
ErrOauthUnauthorizedClient = AuthServiceOauthError("unauthorized_client")
|
||||||
|
ErrOauthUnsupportedGrantType = AuthServiceOauthError("unsupported_grant_type")
|
||||||
|
)
|
||||||
|
|
||||||
|
// OauthAuthorizationCode 验证授权码
|
||||||
|
func (s *authService) OauthAuthorizationCode(ctx context.Context, client *models.Client, code, redirectURI, codeVerifier string) (*TokenDetails, error) {
|
||||||
|
// TODO: 从数据库验证授权码
|
||||||
|
return nil, errors.New("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// OauthClientCredentials 验证客户端凭证
|
||||||
|
func (s *authService) OauthClientCredentials(ctx context.Context, client *models.Client, scope ...[]string) (*TokenDetails, error) {
|
||||||
|
|
||||||
|
var clientType PayloadType
|
||||||
|
switch client.Spec {
|
||||||
|
case 0:
|
||||||
|
clientType = PayloadClientConfidential
|
||||||
|
case 1:
|
||||||
|
clientType = PayloadClientPublic
|
||||||
|
case 2:
|
||||||
|
clientType = PayloadClientConfidential
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存会话并返回令牌
|
||||||
|
auth := AuthContext{
|
||||||
|
Permissions: map[string]struct{}{
|
||||||
|
"client": {},
|
||||||
|
},
|
||||||
|
Payload: Payload{
|
||||||
|
Type: clientType,
|
||||||
|
Id: client.ID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo 数据库定义会话持续时间
|
||||||
|
token, err := Session.Create(ctx, auth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OauthRefreshToken 验证刷新令牌
|
||||||
|
func (s *authService) OauthRefreshToken(ctx context.Context, client *models.Client, refreshToken string, scope ...[]string) (*TokenDetails, error) {
|
||||||
|
// TODO: 从数据库验证刷新令牌
|
||||||
|
return nil, errors.New("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
type GrantType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
GrantTypeAuthorizationCode GrantType = iota
|
||||||
|
GrantTypeClientCredentials
|
||||||
|
GrantTypeRefreshToken
|
||||||
|
)
|
||||||
288
web/services/session.go
Normal file
288
web/services/session.go
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"platform/init/rds"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
// region SessionService
|
||||||
|
|
||||||
|
var Session = &sessionService{}
|
||||||
|
|
||||||
|
type sessionService struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionServiceError string
|
||||||
|
|
||||||
|
func (e SessionServiceError) Error() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidToken = SessionServiceError("invalid_token")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find 通过访问令牌获取会话信息
|
||||||
|
func (s *sessionService) Find(ctx context.Context, token string) (*AuthContext, error) {
|
||||||
|
|
||||||
|
// 读取认证数据
|
||||||
|
authJSON, err := rds.Client.Get(ctx, accessKey(token)).Result()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, redis.Nil) {
|
||||||
|
return nil, ErrInvalidToken
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反序列化
|
||||||
|
auth := new(AuthContext)
|
||||||
|
if err := json.Unmarshal([]byte(authJSON), auth); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建一个新的会话
|
||||||
|
func (s *sessionService) Create(ctx context.Context, auth AuthContext, config ...SessionConfig) (*TokenDetails, error) {
|
||||||
|
// 解析可选配置
|
||||||
|
cfg := DefaultSessionConfig
|
||||||
|
if len(config) > 0 {
|
||||||
|
cfg = mergeConfig(DefaultSessionConfig, config[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成令牌组
|
||||||
|
accessToken := genToken()
|
||||||
|
refreshToken := genToken()
|
||||||
|
|
||||||
|
// 序列化认证数据
|
||||||
|
authData, err := json.Marshal(auth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序列化刷新令牌数据
|
||||||
|
refreshData, err := json.Marshal(RefreshData{
|
||||||
|
AuthContext: auth,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事务保存数据到 Redis
|
||||||
|
pipe := rds.Client.TxPipeline()
|
||||||
|
pipe.Set(ctx, accessKey(accessToken), authData, cfg.AccessTokenDuration)
|
||||||
|
pipe.Set(ctx, refreshKey(refreshToken), refreshData, cfg.RefreshTokenDuration)
|
||||||
|
_, err = pipe.Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TokenDetails{
|
||||||
|
AccessToken: accessToken,
|
||||||
|
AccessTokenExpires: time.Now().Add(cfg.AccessTokenDuration),
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
RefreshTokenExpires: time.Now().Add(cfg.RefreshTokenDuration),
|
||||||
|
Auth: auth,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh 刷新一个会话
|
||||||
|
func (s *sessionService) Refresh(ctx context.Context, refreshToken string, config ...SessionConfig) (*TokenDetails, error) {
|
||||||
|
// 解析可选配置
|
||||||
|
cfg := DefaultSessionConfig
|
||||||
|
if len(config) > 0 {
|
||||||
|
cfg = mergeConfig(DefaultSessionConfig, config[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
rKey := refreshKey(refreshToken)
|
||||||
|
var tokenDetails *TokenDetails
|
||||||
|
|
||||||
|
// 刷新令牌
|
||||||
|
err := rds.Client.Watch(ctx, func(tx *redis.Tx) error {
|
||||||
|
|
||||||
|
// 先获取刷新令牌数据
|
||||||
|
refreshJson, err := tx.Get(ctx, rKey).Result()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, redis.Nil) {
|
||||||
|
return ErrInvalidToken
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析刷新令牌数据
|
||||||
|
refreshData := new(RefreshData)
|
||||||
|
if err := json.Unmarshal([]byte(refreshJson), refreshData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除旧的令牌
|
||||||
|
pipeline := tx.Pipeline()
|
||||||
|
pipeline.Del(ctx, accessKey(refreshData.AccessToken))
|
||||||
|
pipeline.Del(ctx, refreshKey(refreshToken))
|
||||||
|
|
||||||
|
// 生成新的令牌
|
||||||
|
newAccessToken := genToken()
|
||||||
|
newRefreshToken := genToken()
|
||||||
|
|
||||||
|
authData, err := json.Marshal(refreshData.AuthContext)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newRefreshData, err := json.Marshal(RefreshData{
|
||||||
|
AuthContext: refreshData.AuthContext,
|
||||||
|
AccessToken: newAccessToken,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline.Set(ctx, accessKey(newAccessToken), authData, cfg.AccessTokenDuration)
|
||||||
|
pipeline.Set(ctx, refreshKey(newRefreshToken), newRefreshData, cfg.RefreshTokenDuration)
|
||||||
|
|
||||||
|
_, err = pipeline.Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenDetails = &TokenDetails{
|
||||||
|
AccessToken: newAccessToken,
|
||||||
|
RefreshToken: newRefreshToken,
|
||||||
|
AccessTokenExpires: time.Now().Add(cfg.AccessTokenDuration),
|
||||||
|
RefreshTokenExpires: time.Now().Add(cfg.RefreshTokenDuration),
|
||||||
|
Auth: refreshData.AuthContext,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, rKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("刷新令牌失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenDetails, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove 删除会话
|
||||||
|
func (s *sessionService) Remove(ctx context.Context, accessToken, refreshToken string) error {
|
||||||
|
rds.Client.Del(ctx, accessKey(accessToken), refreshKey(refreshToken))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 令牌键的格式为 "session:<token>"
|
||||||
|
func accessKey(token string) string {
|
||||||
|
return fmt.Sprintf("session:%s", token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新令牌键的格式为 "session:refreshKey:<token>"
|
||||||
|
func refreshKey(token string) string {
|
||||||
|
return fmt.Sprintf("session:refresh:%s", token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成一个新的令牌
|
||||||
|
func genToken() string {
|
||||||
|
return uuid.NewString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region SessionConfig
|
||||||
|
|
||||||
|
// SessionConfig 定义会话管理的配置选项
|
||||||
|
type SessionConfig struct {
|
||||||
|
// 令牌配置
|
||||||
|
AccessTokenDuration time.Duration
|
||||||
|
RefreshTokenDuration time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultSessionConfig 默认会话配置
|
||||||
|
var DefaultSessionConfig = SessionConfig{
|
||||||
|
AccessTokenDuration: 2 * time.Hour,
|
||||||
|
RefreshTokenDuration: 7 * 24 * time.Hour,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并配置,保留非零值
|
||||||
|
func mergeConfig(defaultCfg SessionConfig, customCfg SessionConfig) SessionConfig {
|
||||||
|
result := defaultCfg
|
||||||
|
|
||||||
|
if customCfg.AccessTokenDuration != 0 {
|
||||||
|
result.AccessTokenDuration = customCfg.AccessTokenDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
if customCfg.RefreshTokenDuration != 0 {
|
||||||
|
result.RefreshTokenDuration = customCfg.RefreshTokenDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region AuthContext
|
||||||
|
|
||||||
|
// AuthContext 定义认证信息
|
||||||
|
type AuthContext struct {
|
||||||
|
Payload Payload
|
||||||
|
Permissions map[string]struct{}
|
||||||
|
Metadata map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Payload 定义负载信息
|
||||||
|
type Payload struct {
|
||||||
|
Type PayloadType
|
||||||
|
Id int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// PayloadType 定义负载类型
|
||||||
|
type PayloadType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PayloadUser 用户类型
|
||||||
|
PayloadUser PayloadType = iota
|
||||||
|
// PayloadAdmin 管理员类型
|
||||||
|
PayloadAdmin
|
||||||
|
// PayloadClientPublic 公共客户端类型
|
||||||
|
PayloadClientPublic
|
||||||
|
// PayloadClientConfidential 机密客户端类型
|
||||||
|
PayloadClientConfidential
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnyPermission 检查认证是否包含指定权限
|
||||||
|
func (a *AuthContext) AnyPermission(requiredPermission ...string) bool {
|
||||||
|
if a == nil || a.Permissions == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, permission := range requiredPermission {
|
||||||
|
if _, ok := a.Permissions[permission]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
type RefreshData struct {
|
||||||
|
AuthContext AuthContext
|
||||||
|
AccessToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenDetails 存储令牌详细信息
|
||||||
|
type TokenDetails struct {
|
||||||
|
// 访问令牌
|
||||||
|
AccessToken string
|
||||||
|
// 刷新令牌
|
||||||
|
RefreshToken string
|
||||||
|
// 访问令牌过期时间
|
||||||
|
AccessTokenExpires time.Time
|
||||||
|
// 刷新令牌过期时间
|
||||||
|
RefreshTokenExpires time.Time
|
||||||
|
// 认证信息
|
||||||
|
Auth AuthContext
|
||||||
|
}
|
||||||
124
web/services/verifier.go
Normal file
124
web/services/verifier.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"math/rand"
|
||||||
|
"platform/init/rds"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Verifier = &verifierService{}
|
||||||
|
|
||||||
|
type verifierService struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type VerifierServiceError string
|
||||||
|
|
||||||
|
func (e VerifierServiceError) Error() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrVerifierServiceInvalid = VerifierServiceError("验证码错误")
|
||||||
|
)
|
||||||
|
|
||||||
|
type VerifierServiceSendLimitErr int
|
||||||
|
|
||||||
|
func (e VerifierServiceSendLimitErr) Error() string {
|
||||||
|
return "发送频率过快"
|
||||||
|
}
|
||||||
|
|
||||||
|
type VerifierSmsPurpose int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Login VerifierSmsPurpose = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
func smsKey(phone string, purpose VerifierSmsPurpose) string {
|
||||||
|
return fmt.Sprintf("verify:sms:%d:%s", purpose, phone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *verifierService) SendSms(ctx context.Context, phone string, purpose VerifierSmsPurpose) error {
|
||||||
|
key := smsKey(phone, purpose)
|
||||||
|
keyLock := key + ":lock"
|
||||||
|
|
||||||
|
// 生成验证码
|
||||||
|
code := rand.Intn(900000) + 100000 // 6-digit code between 100000-999999
|
||||||
|
|
||||||
|
// 检查发送频率,1 分钟内只能发送一次
|
||||||
|
err := rds.Client.Watch(ctx, func(tx *redis.Tx) error {
|
||||||
|
result, err := tx.TTL(ctx, keyLock).Result()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if result > 0 {
|
||||||
|
return VerifierServiceSendLimitErr(result.Seconds())
|
||||||
|
}
|
||||||
|
if result != -2 {
|
||||||
|
return VerifierServiceError("验证码检查异常")
|
||||||
|
}
|
||||||
|
|
||||||
|
pipe := rds.Client.Pipeline()
|
||||||
|
pipe.Set(ctx, key, code, 10*time.Minute)
|
||||||
|
pipe.Set(ctx, keyLock, "", 1*time.Minute)
|
||||||
|
_, err = pipe.Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, keyLock)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 发送短信验证码
|
||||||
|
slog.Debug("发送验证码", slog.String("phone", phone), slog.String("code", strconv.Itoa(code)))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *verifierService) VerifySms(ctx context.Context, phone, code string) (bool, error) {
|
||||||
|
key := smsKey(phone, Login)
|
||||||
|
keyLock := key + ":lock"
|
||||||
|
|
||||||
|
err := rds.Client.Watch(ctx, func(tx *redis.Tx) error {
|
||||||
|
|
||||||
|
// 检查验证码
|
||||||
|
val, err := rds.Client.Get(ctx, key).Result()
|
||||||
|
if err != nil && !errors.Is(err, redis.Nil) {
|
||||||
|
slog.Error("验证码获取失败", slog.Any("err", err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if val != code {
|
||||||
|
return ErrVerifierServiceInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除验证码
|
||||||
|
_, err = tx.Pipelined(ctx, func(pipe redis.Pipeliner) error {
|
||||||
|
pipe.Del(ctx, key)
|
||||||
|
pipe.Del(ctx, keyLock)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("验证码删除失败", slog.Any("err", err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, key)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrVerifierServiceInvalid) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
type ErrResp struct {
|
|
||||||
Cause string `json:"cause"`
|
|
||||||
}
|
|
||||||
32
web/web.go
32
web/web.go
@@ -1,18 +1,20 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"platform/init/env"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/requestid"
|
||||||
)
|
)
|
||||||
import "log/slog"
|
import "log/slog"
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Logger *slog.Logger
|
|
||||||
Listen string
|
Listen string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
config *Config
|
config *Config
|
||||||
log *slog.Logger
|
|
||||||
fiber *fiber.App
|
fiber *fiber.App
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,33 +24,35 @@ func New(config *Config) (*Server, error) {
|
|||||||
_config = &Config{}
|
_config = &Config{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _config.Logger == nil {
|
|
||||||
_config.Logger = slog.Default()
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Server{
|
return &Server{
|
||||||
config: _config,
|
config: _config,
|
||||||
log: _config.Logger,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Run() error {
|
func (s *Server) Run() error {
|
||||||
s.fiber = fiber.New(fiber.Config{})
|
s.fiber = fiber.New(fiber.Config{
|
||||||
UseRoute(s.fiber)
|
ErrorHandler: ErrorHandler,
|
||||||
|
})
|
||||||
|
|
||||||
s.log.Info("Server started on :8080")
|
s.fiber.Use(requestid.New())
|
||||||
err := s.fiber.Listen(":8080")
|
s.fiber.Use(logger.New())
|
||||||
|
|
||||||
|
ApplyRouters(s.fiber)
|
||||||
|
|
||||||
|
port := env.AppPort
|
||||||
|
slog.Info("Server started on :" + port)
|
||||||
|
err := s.fiber.Listen(":" + port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error("Failed to start server", slog.Any("error", err))
|
slog.Error("Failed to start server", slog.Any("err", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Info("Server stopped")
|
slog.Info("Server stopped")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Stop() {
|
func (s *Server) Stop() {
|
||||||
err := s.fiber.Shutdown()
|
err := s.fiber.Shutdown()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error("Failed to shutdown server", slog.Any("error", err))
|
slog.Error("Failed to shutdown server", slog.Any("err", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user