From a245229bc2493eaa487b7de6e890d7189711445b Mon Sep 17 00:00:00 2001 From: luorijun Date: Mon, 17 Nov 2025 18:38:10 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=BB=A3=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E4=B8=8E=E8=AE=A4=E8=AF=81=E4=BD=93=E7=B3=BB=EF=BC=8C?= =?UTF-8?q?=E9=9B=86=E6=88=90=E5=BC=82=E6=AD=A5=E4=BB=BB=E5=8A=A1=E6=B6=88?= =?UTF-8?q?=E8=B4=B9=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- .vscode/launch.json | 30 +- cmd/fill/main.go | 128 ------- cmd/gen/main.go | 44 ++- cmd/main/main.go | 39 +-- docker-compose.yaml | 20 +- go.mod | 48 +-- go.sum | 96 +++--- pkg/env/env.go | 552 ++++++++----------------------- pkg/logs/logs.go | 7 +- pkg/u/u.go | 43 ++- scripts/pre/docker-compose.yaml | 50 --- scripts/pre/vector/vector.toml | 43 --- scripts/sql/init.sql | 370 ++++++++++++--------- web/auth/authenticate.go | 192 ++++++----- web/auth/authorize.go | 378 ++++++++++++++++++++- web/auth/check.go | 99 ++++++ web/auth/context.go | 103 ------ web/auth/errors.go | 24 ++ web/auth/session.go | 200 ++--------- web/core/types.go | 2 +- web/domains/client/types.go | 9 +- web/error.go | 6 +- web/{tasks => events}/channel.go | 2 +- web/{tasks => events}/log.go | 2 +- web/{tasks => events}/trade.go | 5 +- web/globals/alipay.go | 10 +- web/globals/aliyun.go | 6 +- web/globals/baiyin.go | 3 +- web/globals/init.go | 46 ++- web/globals/orm.go | 23 +- web/globals/proxy.go | 3 +- web/globals/redis.go | 12 +- web/globals/shangfutong.go | 57 ++-- web/globals/validator.go | 9 +- web/globals/wechatpay.go | 16 +- web/handlers/announcement.go | 5 +- web/handlers/auth.go | 269 +-------------- web/handlers/bill.go | 4 +- web/handlers/channel.go | 45 +-- web/handlers/edge.go | 7 +- web/handlers/iden.go | 12 +- web/handlers/proxy.go | 12 +- web/handlers/resource.go | 31 +- web/handlers/trade.go | 35 +- web/handlers/user.go | 17 +- web/handlers/verifier.go | 20 +- web/handlers/whitelist.go | 17 +- web/middlewares.go | 42 +++ web/models/channel.gen.go | 8 +- web/models/client.gen.go | 27 +- web/models/logs_login.gen.go | 4 +- web/models/logs_request.gen.go | 22 +- web/models/proxy.gen.go | 4 +- web/models/session.gen.go | 31 +- web/models/trade.gen.go | 34 +- web/queries/channel.gen.go | 32 +- web/queries/client.gen.go | 46 +-- web/queries/logs_login.gen.go | 16 +- web/queries/logs_request.gen.go | 32 +- web/queries/proxy.gen.go | 16 +- web/queries/session.gen.go | 293 +++++++++++++++- web/queries/trade.gen.go | 52 +-- web/{router.go => routes.go} | 15 +- web/services/auth.go | 201 ----------- web/services/channel.go | 7 +- web/services/trade.go | 10 +- web/services/verifier.go | 59 ++-- web/tasks/task.go | 41 +++ web/web.go | 189 ++++------- 70 files changed, 2000 insertions(+), 2334 deletions(-) delete mode 100644 cmd/fill/main.go delete mode 100644 scripts/pre/docker-compose.yaml delete mode 100644 scripts/pre/vector/vector.toml create mode 100644 web/auth/check.go delete mode 100644 web/auth/context.go create mode 100644 web/auth/errors.go rename web/{tasks => events}/channel.go (95%) rename web/{tasks => events}/log.go (97%) rename web/{tasks => events}/trade.go (97%) create mode 100644 web/middlewares.go rename web/{router.go => routes.go} (85%) delete mode 100644 web/services/auth.go create mode 100644 web/tasks/task.go diff --git a/.gitignore b/.gitignore index b0a9201..44b6088 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ !.env.example bin/ -*.exe +*.exe* *.pem *.http diff --git a/.vscode/launch.json b/.vscode/launch.json index e7af644..bbd6612 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,16 +1,16 @@ { - // 使用 IntelliSense 了解相关属性。 - // 悬停以查看现有属性的描述。 - // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "main", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${workspaceFolder}/cmd/main", - "cwd": "${workspaceFolder}", - } - ] -} \ No newline at end of file + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "main", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceFolder}/cmd/main", + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/cmd/fill/main.go b/cmd/fill/main.go deleted file mode 100644 index 4a0261d..0000000 --- a/cmd/fill/main.go +++ /dev/null @@ -1,128 +0,0 @@ -package main - -import ( - "fmt" - "golang.org/x/crypto/bcrypt" - "gorm.io/driver/postgres" - "gorm.io/gorm" - "gorm.io/gorm/schema" - "log/slog" - "platform/pkg/env" - "platform/pkg/logs" - "platform/pkg/u" - client2 "platform/web/domains/client" - proxy2 "platform/web/domains/proxy" - m "platform/web/models" - q "platform/web/queries" -) - -func main() { - - env.Init() - logs.Init() - - // 初始化数据库连接 - dsn := fmt.Sprintf( - "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Shanghai", - env.DbHost, env.DbUserName, env.DbPassword, env.DbName, env.DbPort, - ) - db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ - NamingStrategy: schema.NamingStrategy{ - SingularTable: true, - }, - }) - if err != nil { - slog.Error("gorm 初始化数据库失败:", slog.Any("err", err)) - panic(err) - } - - q.SetDefault(db) - - // 填充数据 - err = q.Q.Transaction(func(tx *q.Query) (err error) { - - // 代理 - err = q.Proxy. - Select(q.Proxy.Version, q.Proxy.Name, q.Proxy.Host, q.Proxy.Type, q.Proxy.Secret). - Create(&m.Proxy{ - Version: 1, - Name: "7a17e8b4-cdc3-4500-bf16-4a665991a7f6", - Host: "110.40.82.248", - Type: int32(proxy2.TypeSelfHosted), - Secret: u.P("api:123456"), - }, &m.Proxy{ - Version: 1, - Name: "58e03f38-4cef-429c-8bb8-530142d0a745", - Host: "123.6.147.241", - Type: int32(proxy2.TypeThirdParty), - Secret: u.P("api:123456"), - }) - if err != nil { - return err - } - - // 客户端 - testSecret, err := bcrypt.GenerateFromPassword([]byte("test"), bcrypt.DefaultCost) - if err != nil { - return err - } - - tasksSecret, err := bcrypt.GenerateFromPassword([]byte("tasks"), bcrypt.DefaultCost) - if err != nil { - return err - } - - proxySecret, err := bcrypt.GenerateFromPassword([]byte("proxy"), bcrypt.DefaultCost) - if err != nil { - return err - } - - err = q.Client. - Select( - q.Client.ClientID, - q.Client.ClientSecret, - q.Client.GrantClient, - q.Client.GrantRefresh, - q.Client.GrantPassword, - q.Client.Spec, - q.Client.Name, - ). - Create(&m.Client{ - ClientID: "test", - ClientSecret: string(testSecret), - GrantCode: true, - GrantClient: true, - GrantRefresh: true, - GrantPassword: true, - Spec: int32(client2.SpecTrusted), - Name: "默认客户端", - }, &m.Client{ - ClientID: "tasks", - ClientSecret: string(tasksSecret), - GrantClient: true, - Spec: int32(client2.SpecTrusted), - Name: "异步任务处理服务", - }, &m.Client{ - ClientID: "proxy", - ClientSecret: string(proxySecret), - GrantClient: true, - Spec: int32(client2.SpecTrusted), - Name: "代理转发服务", - }, &m.Client{ - ClientID: "edge", - GrantClient: true, - Spec: int32(client2.SpecWeb), - Name: "代理边缘节点", - }) - if err != nil { - return err - } - - return nil - }) - if err != nil { - panic(err) - } - - slog.Info("✔ Data inserted successfully") -} diff --git a/cmd/gen/main.go b/cmd/gen/main.go index c72e10c..c6b46e4 100644 --- a/cmd/gen/main.go +++ b/cmd/gen/main.go @@ -1,12 +1,13 @@ package main import ( + "strings" + "gorm.io/driver/postgres" "gorm.io/gen" "gorm.io/gen/field" "gorm.io/gorm" "gorm.io/gorm/schema" - "strings" ) var g *gen.Generator @@ -15,7 +16,7 @@ func main() { // 初始化 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=dev password=dev dbname=app port=5432 sslmode=disable TimeZone=Asia/Shanghai"), &gorm.Config{ NamingStrategy: schema.NamingStrategy{ SingularTable: true, @@ -47,11 +48,13 @@ func main() { return field }), gen.FieldRename("contact_qq", "ContactQQ"), + gen.FieldRename("ua", "UA"), } // 生成模型 customs := make(map[string]any) + // resource resourceShort := g.GenerateModel("resource_short", common...) customs["resource_short"] = resourceShort @@ -76,6 +79,7 @@ func main() { )...) customs["resource"] = resource + // trade trade := g.GenerateModel("trade", common...) customs["trade"] = trade @@ -104,6 +108,7 @@ func main() { )...) customs["bill"] = bill + // proxy edge := g.GenerateModel("edge", common...) customs["edge"] = edge @@ -117,9 +122,42 @@ func main() { )...) customs["proxy"] = proxy + // session + user := g.GenerateModel("user", common...) + customs["user"] = user + + admin := g.GenerateModel("admin", common...) + customs["admin"] = admin + + client := g.GenerateModel("client", common...) + customs["client"] = client + + session := g.GenerateModel("session", append(common, + gen.FieldRelate(field.BelongsTo, "User", user, &field.RelateConfig{ + RelatePointer: true, + GORMTag: field.GormTag{ + "foreignKey": []string{"UserID"}, + }, + }), + gen.FieldRelate(field.BelongsTo, "Admin", admin, &field.RelateConfig{ + RelatePointer: true, + GORMTag: field.GormTag{ + "foreignKey": []string{"AdminID"}, + }, + }), + gen.FieldRelate(field.BelongsTo, "Client", client, &field.RelateConfig{ + RelatePointer: true, + GORMTag: field.GormTag{ + "foreignKey": []string{"ClientID"}, + "belongsTo": []string{"ID"}, + }, + }), + )...) + customs["session"] = session + // 生成表结构 tables, _ := db.Migrator().GetTables() - models := make([]interface{}, len(tables)) + models := make([]any, len(tables)) for i, name := range tables { if customs[name] != nil { models[i] = customs[name] diff --git a/cmd/main/main.go b/cmd/main/main.go index cbb727c..18ebc44 100644 --- a/cmd/main/main.go +++ b/cmd/main/main.go @@ -1,6 +1,8 @@ package main import ( + "context" + "fmt" "log/slog" "os" "os/signal" @@ -11,45 +13,16 @@ import ( ) func main() { - - // 退出信号 - shutdown := make(chan os.Signal, 1) - signal.Notify(shutdown, syscall.SIGINT, syscall.SIGTERM) + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer cancel() // 初始化应用 env.Init() logs.Init() // 创建服务 - app, err := web.New(&web.Config{ - Listen: ":8080", - }) + err := web.RunApp(ctx) if err != nil { - slog.Error("Failed to create server", slog.Any("err", err)) - return - } - - // 异步运行服务 - errCh := make(chan error) - defer close(errCh) - go func() { - err := app.Run() - if err != nil { - slog.Error("Failed to run server", slog.Any("err", err)) - errCh <- err - } - errCh <- nil - }() - - // 关闭服务 - select { - case err = <-errCh: - case <-shutdown: - slog.Debug("捕获结束信号") - app.Stop() - err = <-errCh - } - if err != nil { - slog.Error("Server error", slog.Any("err", err)) + slog.Error(fmt.Sprintf("%v", err)) } } diff --git a/docker-compose.yaml b/docker-compose.yaml index 78d2449..a877b62 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,7 +1,6 @@ name: server-dev services: - postgres: image: postgres:17 environment: @@ -9,24 +8,19 @@ services: POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: ${DB_NAME} ports: - - "5432:5432" + - "${DB_PORT}:5432" volumes: - postgres_data:/var/lib/postgresql/data - - postgres-migration: - image: postgres:17 - environment: - POSTGRES_USER: ${DB_USERNAME} - POSTGRES_PASSWORD: ${DB_PASSWORD} - POSTGRES_DB: ${DB_NAME} - ports: - - "5433:5432" + restart: unless-stopped redis: image: redis:7.4 - restart: always ports: - - "6379:6379" + - "${REDIS_PORT}:6379" + volumes: + - redis_data:/data + restart: unless-stopped volumes: postgres_data: + redis_data: diff --git a/go.mod b/go.mod index 1c3d587..18cca89 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module platform -go 1.24.5 +go 1.25.3 require ( github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.7 @@ -9,18 +9,20 @@ require ( github.com/go-playground/universal-translator v0.18.1 github.com/go-playground/validator/v10 v10.26.0 github.com/go-redsync/redsync/v4 v4.13.0 - github.com/gofiber/fiber/v2 v2.52.6 + github.com/gofiber/contrib/otelfiber/v2 v2.2.3 + github.com/gofiber/fiber/v2 v2.52.9 github.com/google/uuid v1.6.0 github.com/hibiken/asynq v0.25.1 github.com/jdcloud-api/jdcloud-sdk-go v1.64.0 github.com/joho/godotenv v1.5.1 github.com/jxskiss/base62 v1.1.0 github.com/lmittmann/tint v1.0.7 - github.com/redis/go-redis/v9 v9.8.0 + github.com/redis/go-redis/v9 v9.12.1 github.com/shopspring/decimal v1.4.0 github.com/smartwalle/alipay/v3 v3.2.25 github.com/wechatpay-apiv3/wechatpay-go v0.2.20 - golang.org/x/crypto v0.36.0 + golang.org/x/crypto v0.43.0 + golang.org/x/sync v0.17.0 gorm.io/driver/postgres v1.5.11 gorm.io/gen v0.3.27 gorm.io/gorm v1.25.12 @@ -36,15 +38,18 @@ require ( github.com/alibabacloud-go/tea v1.3.8 // indirect github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect github.com/aliyun/credentials-go v1.4.5 // indirect - github.com/andybalholm/brotli v1.1.1 // indirect + github.com/andybalholm/brotli v1.2.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect + github.com/clipperhouse/stringish v0.1.1 // indirect + github.com/clipperhouse/uax29/v2 v2.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect - github.com/go-sql-driver/mysql v1.9.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gomodule/redigo v2.0.0+incompatible // indirect - github.com/google/go-cmp v0.7.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -54,31 +59,36 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.1 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/mattn/go-sqlite3 v1.14.24 // indirect + github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/mattn/go-sqlite3 v1.14.28 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/rivo/uniseg v0.4.7 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/smartwalle/ncrypto v1.0.4 // indirect github.com/smartwalle/ngx v1.0.9 // indirect github.com/smartwalle/nsign v1.0.9 // indirect github.com/spf13/cast v1.7.0 // indirect + github.com/stretchr/testify v1.11.1 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.59.0 // indirect - golang.org/x/mod v0.24.0 // indirect - golang.org/x/net v0.37.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect + github.com/valyala/fasthttp v1.68.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib v1.20.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.8.0 // indirect - golang.org/x/tools v0.31.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect + golang.org/x/tools v0.37.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gorm.io/datatypes v1.2.5 // indirect gorm.io/driver/mysql v1.5.7 // indirect diff --git a/go.sum b/go.sum index c39c4fa..c1b3dd4 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,8 @@ github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTs github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= github.com/aliyun/credentials-go v1.4.5 h1:O76WYKgdy1oQYYiJkERjlA2dxGuvLRrzuO2ScrtGWSk= github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= -github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= -github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= 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= @@ -66,6 +66,10 @@ github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= +github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= +github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= +github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 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= @@ -79,6 +83,11 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -96,10 +105,12 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq github.com/go-redsync/redsync/v4 v4.13.0 h1:49X6GJfnbLGaIpBBREM/zA4uIMDXKAh1NDkvQ1EkZKA= github.com/go-redsync/redsync/v4 v4.13.0/go.mod h1:HMW4Q224GZQz6x1Xc7040Yfgacukdzu7ifTDAKiyErQ= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI= -github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= -github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= -github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/gofiber/contrib/otelfiber/v2 v2.2.3 h1:WKW1XezHFAoohGZwnvC0R8TFJcNkabQwB5YIpdKmz00= +github.com/gofiber/contrib/otelfiber/v2 v2.2.3/go.mod h1:WdQ1tYbL83IYC6oBaWvKBMVGSAYvSTRuUWTcr0wK1T4= +github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw= +github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= @@ -160,8 +171,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw= github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= +github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -176,12 +187,12 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= -github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -195,17 +206,14 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= -github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg= +github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo= github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/smartwalle/alipay/v3 v3.2.25 h1:cRDN+fpDWTVHnuHIF/vsJETskRXS/S+fDOdAkzXmV/Q= @@ -231,8 +239,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= @@ -240,8 +248,8 @@ github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI= -github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU= +github.com/valyala/fasthttp v1.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFnok= +github.com/valyala/fasthttp v1.68.0/go.mod h1:5EXiRfYQAoiO/khu4oU9VISC/eVY6JqmSpPJoHCKsz4= github.com/wechatpay-apiv3/wechatpay-go v0.2.20 h1:gS8oFn1bHGnyapR2Zb4aqTV6l4kJWgbtqjCq6k1L9DQ= github.com/wechatpay-apiv3/wechatpay-go v0.2.20/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= @@ -249,6 +257,16 @@ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3i github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib v1.20.0 h1:oXUiIQLlkbi9uZB/bt5B1WRLsrTKqb7bPpAQ+6htn2w= +go.opentelemetry.io/contrib v1.20.0/go.mod h1:gIzjwWFoGazJmtCaDgViqOSJPde2mCWzv60o0bWPcZs= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -265,8 +283,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -277,8 +295,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -300,8 +318,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -313,8 +331,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -335,8 +353,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -359,8 +377,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -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/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -375,8 +393,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= -golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -394,8 +412,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/pkg/env/env.go b/pkg/env/env.go index bb262b2..7da8583 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -1,274 +1,54 @@ package env import ( + "fmt" "log/slog" "os" + "platform/pkg/u" + "slices" "strconv" "github.com/gofiber/fiber/v2/log" "github.com/joho/godotenv" ) -// region app - const ( - RunModeDev = "debug" + RunModeDev = "development" RunModeProd = "production" ) var ( - RunMode = RunModeDev - TradeExpire = 30 * 60 // 交易过期时间,单位秒 -) + RunMode = RunModeProd + LogLevel = slog.LevelDebug + TradeExpire = 30 * 60 // 交易过期时间,单位秒 + SessionAccessExpire = 60 * 60 * 2 // 默认 2 小时 + SessionRefreshExpire = 60 * 60 * 24 * 7 // 默认 7 天 + DebugHttpDump = false // 是否打印请求和响应的原始数据 + DebugExternalChange = true // 是否实际执行外部非幂等接口调用,在开发调试时可以关闭,避免对外部数据产生影响 -func loadApp() { - _RunMode := os.Getenv("RUN_MODE") - switch _RunMode { - case RunModeDev, RunModeProd: - RunMode = _RunMode - case "": - break - default: - panic("环境变量 RUN_MODE 的值只能是 " + RunModeDev + " 或 " + RunModeProd) - } - - _TradeExpire := os.Getenv("TRADE_EXPIRE") - if _TradeExpire != "" { - value, err := strconv.Atoi(_TradeExpire) - if err != nil { - panic("环境变量 TRADE_EXPIRE 的值不是数字") - } - TradeExpire = value - } -} - -// endregion - -// region auth - -var ( - SessionAccessExpire = 60 * 60 * 2 // 2小时 - SessionRefreshExpire = 60 * 60 * 24 * 7 // 7天 -) - -func loadAuth() { - _SessionAccessExpire := os.Getenv("SESSION_ACCESS_EXPIRE") - if _SessionAccessExpire != "" { - value, err := strconv.Atoi(_SessionAccessExpire) - if err != nil { - panic("环境变量 SESSION_ACCESS_EXPIRE 的值不是数字") - } - SessionAccessExpire = value - } - - _SessionRefreshExpire := os.Getenv("SESSION_REFRESH_EXPIRE") - if _SessionRefreshExpire != "" { - value, err := strconv.Atoi(_SessionRefreshExpire) - if err != nil { - panic("环境变量 SESSION_REFRESH_EXPIRE 的值不是数字") - } - SessionRefreshExpire = value - } -} - -// endregion - -// region db - -var ( DbHost = "localhost" DbPort = "5432" DbName string DbUserName string DbPassword string -) -func loadDb() { - _DbHost := os.Getenv("DB_HOST") - if _DbHost != "" { - DbHost = _DbHost - } + RedisHost = "localhost" + RedisPort = "6379" + RedisPassword = "" - _DbPort := os.Getenv("DB_PORT") - if _DbPort != "" { - DbPort = _DbPort - } - - _DbName := os.Getenv("DB_NAME") - if _DbName != "" { - DbName = _DbName - } else { - panic("环境变量 DB_NAME 的值不能为空") - } - - _DbUserName := os.Getenv("DB_USERNAME") - if _DbUserName != "" { - DbUserName = _DbUserName - } else { - panic("环境变量 DB_USERNAME 的值不能为空") - } - - _DbPassword := os.Getenv("DB_PASSWORD") - if _DbPassword != "" { - DbPassword = _DbPassword - } else { - 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") - switch _LogLevel { - case "debug": - LogLevel = slog.LevelDebug - case "info": - LogLevel = slog.LevelInfo - case "warn": - LogLevel = slog.LevelWarn - case "error": - LogLevel = slog.LevelError - } -} - -// endregion - -// region remote - -var ( BaiyinAddr = "http://103.139.212.110:9989" BaiyinTokenUrl string -) -var ( IdenCallbackUrl string IdenAccessKey string IdenSecretKey string -) -func loadRemote() { - _BaiyinAddr := os.Getenv("BAIYIN_ADDR") - if _BaiyinAddr != "" { - BaiyinAddr = _BaiyinAddr - } - - _BaiyinTokenUrl := os.Getenv("BAIYIN_TOKEN_URL") - if _BaiyinTokenUrl == "" { - panic("环境变量 BAIYIN_TOKEN_URL 的值不能为空") - } - BaiyinTokenUrl = _BaiyinTokenUrl - - _IdenCallbackUrl := os.Getenv("IDEN_CALLBACK_URL") - if _IdenCallbackUrl == "" { - panic("环境变量 IDEN_CALLBACK_URL 的值不能为空") - } - IdenCallbackUrl = _IdenCallbackUrl - - _IdenAccessKey := os.Getenv("IDEN_ACCESS_KEY") - if _IdenAccessKey == "" { - panic("环境变量 IDEN_ACCESS_KEY 的值不能为空") - } - IdenAccessKey = _IdenAccessKey - - _IdenSecretKey := os.Getenv("IDEN_SECRET_KEY") - if _IdenSecretKey == "" { - panic("环境变量 IDEN_SECRET_KEY 的值不能为空") - } - IdenSecretKey = _IdenSecretKey -} - -// endregion - -// region alipay - -var ( AlipayAppId string AlipayAppPrivateKey string AlipayPublicKey string AlipayApiCert string AlipayProduction = false -) -func loadAlipay() { - AlipayAppId = os.Getenv("ALIPAY_APP_ID") - if AlipayAppId == "" { - panic("环境变量 ALIPAY_APP_ID 的值不能为空") - } - - AlipayAppPrivateKey = os.Getenv("ALIPAY_APP_PRIVATE_KEY") - if AlipayAppPrivateKey == "" { - panic("环境变量 ALIPAY_APP_PRIVATE_KEY 的值不能为空") - } - - AlipayPublicKey = os.Getenv("ALIPAY_PUBLIC_KEY") - if AlipayPublicKey == "" { - panic("环境变量 ALIPAY_PUBLIC_KEY 的值不能为空") - } - - AlipayApiCert = os.Getenv("ALIPAY_API_CERT") - if AlipayApiCert == "" { - panic("环境变量 ALIPAY_API_CERT 的值不能为空") - } - - _AlipayProduction := os.Getenv("ALIPAY_PRODUCTION") - if _AlipayProduction != "" { - value, err := strconv.ParseBool(_AlipayProduction) - if err != nil { - panic("环境变量 ALIPAY_PRODUCTION 的值不是布尔值") - } - AlipayProduction = value - } -} - -// endregion - -// region wechatpay - -var ( WechatPayAppId string WechatPayMchId string WechatPayMchPrivateKeySerial string @@ -277,185 +57,21 @@ var ( WechatPayPublicKey string WechatPayApiCert string WechatPayCallbackUrl string -) -func loadWechatPay() { - - WechatPayAppId = os.Getenv("WECHATPAY_APP_ID") - if WechatPayAppId == "" { - panic("环境变量 WECHATPAY_APP_ID 的值不能为空") - } - - WechatPayMchId = os.Getenv("WECHATPAY_MCH_ID") - if WechatPayMchId == "" { - panic("环境变量 WECHATPAY_MCH_ID 的值不能为空") - } - - WechatPayMchPrivateKeySerial = os.Getenv("WECHATPAY_MCH_PRIVATE_KEY_SERIAL") - if WechatPayMchPrivateKeySerial == "" { - panic("环境变量 WECHATPAY_MCH_PRIVATE_KEY_SERIAL 的值不能为空") - } - - WechatPayMchPrivateKey = os.Getenv("WECHATPAY_MCH_PRIVATE_KEY") - if WechatPayMchPrivateKey == "" { - panic("环境变量 WECHATPAY_MCH_PRIVATE_KEY 的值不能为空") - } - - WechatPayPublicKeyId = os.Getenv("WECHATPAY_PUBLIC_KEY_ID") - if WechatPayPublicKeyId == "" { - panic("环境变量 WECHATPAY_PUBLIC_KEY_ID 的值不能为空") - } - - WechatPayPublicKey = os.Getenv("WECHATPAY_PUBLIC_KEY") - if WechatPayPublicKey == "" { - panic("环境变量 WECHATPAY_PUBLIC_KEY 的值不能为空") - } - - WechatPayApiCert = os.Getenv("WECHATPAY_API_CERT") - if WechatPayApiCert == "" { - panic("环境变量 WECHATPAY_API_CERT 的值不能为空") - } - - WechatPayCallbackUrl = os.Getenv("WECHATPAY_CALLBACK_URL") - if WechatPayCallbackUrl == "" { - panic("环境变量 WECHATPAY_CALLBACK_URL 的值不能为空") - } -} - -// endregion - -// region aliyun - -var ( AliyunAccessKey string AliyunAccessKeySecret string AliyunSmsSignature string AliyunSmsTemplateLogin string -) -func loadAliyun() { - AliyunAccessKey = os.Getenv("ALIYUN_ACCESS_KEY") - if AliyunAccessKey == "" { - panic("环境变量 ALIYUN_ACCESS_KEY 的值不能为空") - } - - AliyunAccessKeySecret = os.Getenv("ALIYUN_ACCESS_KEY_SECRET") - if AliyunAccessKeySecret == "" { - panic("环境变量 ALIYUN_ACCESS_KEY_SECRET 的值不能为空") - } - - AliyunSmsSignature = os.Getenv("ALIYUN_SMS_SIGNATURE") - if AliyunSmsSignature == "" { - panic("环境变量 ALIYUN_SMS_SIGNATURE 的值不能为空") - } - - AliyunSmsTemplateLogin = os.Getenv("ALIYUN_SMS_TEMPLATE_LOGIN") - if AliyunSmsTemplateLogin == "" { - panic("环境变量 ALIYUN_SMS_TEMPLATE_LOGIN 的值不能为空") - } -} - -// endregion - -// region 商福通 - -var ( SftPayEnable = false SftPayAppId string SftPayRouteId string SftPayAppPrivateKey string SftPayPublicKey string - SftReturnUrl *string - SftNotifyUrl *string + SftReturnUrl string + SftNotifyUrl string ) -func loadSftPay() { - var value string - - value = os.Getenv("SFTPAY_ENABLE") - if value != "" { - enabled, err := strconv.ParseBool(value) - if err != nil { - panic("环境变量 SFTPAY_ENABLE 的值不是布尔值") - } - SftPayEnable = enabled - } - - value = os.Getenv("SFTPAY_APP_ID") - if value == "" { - panic("环境变量 ALIYUN_SMS_TEMPLATE_LOGIN 的值不能为空") - } else { - SftPayAppId = value - } - - value = os.Getenv("SFTPAY_ROUTE_ID") - if value != "" { - SftPayRouteId = value - } - - value = os.Getenv("SFTPAY_APP_PRIVATE_KEY") - if value == "" { - panic("环境变量 SFTPAY_APP_PRIVATE_KEY 的值不能为空") - } else { - SftPayAppPrivateKey = value - } - - value = os.Getenv("SFTPAY_PUBLIC_KEY") - if value == "" { - panic("环境变量 SFTPAY_PUBLIC_KEY 的值不能为空") - } else { - SftPayPublicKey = value - } - - value = os.Getenv("SFTPAY_RETURN_URL") - if value != "" { - SftReturnUrl = &value - } else { - SftReturnUrl = nil - } - - value = os.Getenv("SFTPAY_NOTIFY_URL") - if value != "" { - SftNotifyUrl = &value - } else { - SftNotifyUrl = nil - } -} - -// endregion - -// region debug - -var ( - // DebugHttpDump 是否打印请求和响应的原始数据 - DebugHttpDump = false - // DebugExternalChange 是否实际执行非幂等外部接口的调用。 - // 例如外部数据修改接口,在内部接口调试时可以关闭,避免对外部数据产生影响 - DebugExternalChange = true -) - -func loadDebug() { - debugHttpDump := os.Getenv("DEBUG_HTTP_DUMP") - if debugHttpDump != "" { - value, err := strconv.ParseBool(debugHttpDump) - if err != nil { - panic("环境变量 DEBUG_HTTP_DUMP 的值不是布尔值") - } - DebugHttpDump = value - } - - debugExternalChange := os.Getenv("DEBUG_EXTERNAL_CHANGE") - if debugExternalChange != "" { - value, err := strconv.ParseBool(debugExternalChange) - if err != nil { - panic("环境变量 DEBUG_EXTERNAL_CHANGE 的值不是布尔值") - } - DebugExternalChange = value - } -} - -// endregion - func Init() { err := godotenv.Load() if err != nil { @@ -464,15 +80,129 @@ func Init() { log.Debug("✔ 加载本地环境变量") } - loadApp() - loadAuth() - loadDb() - loadRedis() - loadLog() - loadDebug() - loadRemote() - loadAlipay() - loadWechatPay() - loadAliyun() - loadSftPay() + // 收集所有错误 + var errs []error + + errs = append(errs, parse(&RunMode, "RUN_MODE", true, &[]string{RunModeDev, RunModeProd})) + errs = append(errs, parse(&LogLevel, "LOG_LEVEL", true, nil, func(value string) (slog.Level, error) { + switch value { + case "debug": + return slog.LevelDebug, nil + case "info": + return slog.LevelInfo, nil + case "warn": + return slog.LevelWarn, nil + case "error": + return slog.LevelError, nil + default: + return slog.LevelInfo, fmt.Errorf("无效的日志级别: %s", value) + } + })) + + errs = append(errs, parse(&TradeExpire, "TRADE_EXPIRE", true, nil)) + errs = append(errs, parse(&SessionAccessExpire, "SESSION_ACCESS_EXPIRE", true, nil)) + errs = append(errs, parse(&SessionRefreshExpire, "SESSION_REFRESH_EXPIRE", true, nil)) + errs = append(errs, parse(&DebugHttpDump, "DEBUG_HTTP_DUMP", true, nil)) + errs = append(errs, parse(&DebugExternalChange, "DEBUG_EXTERNAL_CHANGE", true, nil)) + + errs = append(errs, parse(&DbHost, "DB_HOST", true, nil)) + errs = append(errs, parse(&DbPort, "DB_PORT", true, nil)) + errs = append(errs, parse(&DbName, "DB_NAME", false, nil)) + errs = append(errs, parse(&DbUserName, "DB_USERNAME", false, nil)) + errs = append(errs, parse(&DbPassword, "DB_PASSWORD", false, nil)) + + errs = append(errs, parse(&RedisHost, "REDIS_HOST", true, nil)) + errs = append(errs, parse(&RedisPort, "REDIS_PORT", true, nil)) + errs = append(errs, parse(&RedisPassword, "REDIS_PASS", true, nil)) + + errs = append(errs, parse(&BaiyinAddr, "BAIYIN_ADDR", true, nil)) + errs = append(errs, parse(&BaiyinTokenUrl, "BAIYIN_TOKEN_URL", false, nil)) + + errs = append(errs, parse(&IdenCallbackUrl, "IDEN_CALLBACK_URL", false, nil)) + errs = append(errs, parse(&IdenAccessKey, "IDEN_ACCESS_KEY", false, nil)) + errs = append(errs, parse(&IdenSecretKey, "IDEN_SECRET_KEY", false, nil)) + + errs = append(errs, parse(&AlipayAppId, "ALIPAY_APP_ID", false, nil)) + errs = append(errs, parse(&AlipayAppPrivateKey, "ALIPAY_APP_PRIVATE_KEY", false, nil)) + errs = append(errs, parse(&AlipayPublicKey, "ALIPAY_PUBLIC_KEY", false, nil)) + errs = append(errs, parse(&AlipayApiCert, "ALIPAY_API_CERT", false, nil)) + errs = append(errs, parse(&AlipayProduction, "ALIPAY_PRODUCTION", true, nil)) + + errs = append(errs, parse(&WechatPayAppId, "WECHATPAY_APP_ID", false, nil)) + errs = append(errs, parse(&WechatPayMchId, "WECHATPAY_MCH_ID", false, nil)) + errs = append(errs, parse(&WechatPayMchPrivateKeySerial, "WECHATPAY_MCH_PRIVATE_KEY_SERIAL", false, nil)) + errs = append(errs, parse(&WechatPayMchPrivateKey, "WECHATPAY_MCH_PRIVATE_KEY", false, nil)) + errs = append(errs, parse(&WechatPayPublicKeyId, "WECHATPAY_PUBLIC_KEY_ID", false, nil)) + errs = append(errs, parse(&WechatPayPublicKey, "WECHATPAY_PUBLIC_KEY", false, nil)) + errs = append(errs, parse(&WechatPayApiCert, "WECHATPAY_API_CERT", false, nil)) + errs = append(errs, parse(&WechatPayCallbackUrl, "WECHATPAY_CALLBACK_URL", false, nil)) + + errs = append(errs, parse(&AliyunAccessKey, "ALIYUN_ACCESS_KEY", false, nil)) + errs = append(errs, parse(&AliyunAccessKeySecret, "ALIYUN_ACCESS_KEY_SECRET", false, nil)) + errs = append(errs, parse(&AliyunSmsSignature, "ALIYUN_SMS_SIGNATURE", false, nil)) + errs = append(errs, parse(&AliyunSmsTemplateLogin, "ALIYUN_SMS_TEMPLATE_LOGIN", false, nil)) + + errs = append(errs, parse(&SftPayEnable, "SFTPAY_ENABLE", true, nil)) + errs = append(errs, parse(&SftPayAppId, "SFTPAY_APP_ID", false, nil)) + errs = append(errs, parse(&SftPayRouteId, "SFTPAY_ROUTE_ID", true, nil)) + errs = append(errs, parse(&SftPayAppPrivateKey, "SFTPAY_APP_PRIVATE_KEY", false, nil)) + errs = append(errs, parse(&SftPayPublicKey, "SFTPAY_PUBLIC_KEY", false, nil)) + errs = append(errs, parse(&SftReturnUrl, "SFTPAY_RETURN_URL", true, nil)) + errs = append(errs, parse(&SftNotifyUrl, "SFTPAY_NOTIFY_URL", true, nil)) + + // 统一处理错误 + if err := u.CombineErrors(errs); err != nil { + panic(err) + } +} + +func parse[T comparable](ptr *T, key string, inited bool, enum *[]string, convOpt ...func(value string) (T, error)) error { + value := os.Getenv(key) + + // 处理空值 + if value == "" { + if inited { + return nil + } else { + return fmt.Errorf("环境变量 %s 未设置", key) + } + } + + // 处理枚举映射 + if enum != nil { + valid := slices.Contains(*enum, value) + if !valid { + return fmt.Errorf("环境变量 %s 的值 '%s' 必须是 %v 之一", key, value, *enum) + } + } + + // 根据指针类型进行赋值和类型转换 + switch p := any(ptr).(type) { + case *string: + *p = value + case *int: + intValue, err := strconv.Atoi(value) + if err != nil { + return fmt.Errorf("环境变量 %s 的值 '%s' 不是有效的整数: %v", key, value, err) + } + *p = intValue + case *bool: + boolValue, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("环境变量 %s 的值 '%s' 不是有效的布尔值: %v", key, value, err) + } + *p = boolValue + default: + if len(convOpt) == 0 { + return fmt.Errorf("环境变量 %s 的值 '%s' 无法赋值到目标类型", key, value) + } + conv := convOpt[0] + convertedValue, err := conv(value) + if err != nil { + return fmt.Errorf("环境变量 %s 的值 '%s' 转换失败: %v", key, value, err) + } + *ptr = convertedValue + } + + return nil } diff --git a/pkg/logs/logs.go b/pkg/logs/logs.go index 01a0da0..dcd9db7 100644 --- a/pkg/logs/logs.go +++ b/pkg/logs/logs.go @@ -1,10 +1,11 @@ package logs import ( - "github.com/lmittmann/tint" "log/slog" "os" "platform/pkg/env" + + "github.com/lmittmann/tint" ) func Init() { @@ -14,7 +15,7 @@ func Init() { var handler slog.Handler switch env.RunMode { - case "debug": + case env.RunModeDev: handler = tint.NewHandler(writer, &tint.Options{ Level: env.LogLevel, TimeFormat: timeFormat, @@ -26,7 +27,7 @@ func Init() { return attr }, }) - case "production": + case env.RunModeProd: handler = slog.NewJSONHandler(writer, &slog.HandlerOptions{ Level: env.LogLevel, ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { diff --git a/pkg/u/u.go b/pkg/u/u.go index 3c9c26b..00973a9 100644 --- a/pkg/u/u.go +++ b/pkg/u/u.go @@ -1,12 +1,31 @@ package u -import "time" +import ( + "fmt" + "time" +) // P 是一个工具函数,用于在表达式内原地创建一个指针 func P[T any](v T) *T { return &v } +func Z[T any](v *T) T { + if v == nil { + var zero T + return zero + } + return *v +} + +func X[T comparable](v T) *T { + var zero T + if v == zero { + return nil + } + return &v +} + func Today() time.Time { var now = time.Now() return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) @@ -21,14 +40,6 @@ func SameDate(date time.Time) bool { return date.Year() == now.Year() && date.Month() == now.Month() && date.Day() == now.Day() } -func Z[T any](v *T) T { - if v == nil { - var zero T - return zero - } - return *v -} - func Or[T any](v *T, or T) T { if v == nil { return or @@ -36,3 +47,17 @@ func Or[T any](v *T, or T) T { return *v } } + +func CombineErrors(errs []error) error { + var combinedErr error = nil + for _, err := range errs { + if err != nil { + if combinedErr == nil { + combinedErr = err + } else { + combinedErr = fmt.Errorf("%v; %w", combinedErr, err) + } + } + } + return combinedErr +} diff --git a/scripts/pre/docker-compose.yaml b/scripts/pre/docker-compose.yaml deleted file mode 100644 index 9d74f69..0000000 --- a/scripts/pre/docker-compose.yaml +++ /dev/null @@ -1,50 +0,0 @@ -name: server-pre - -services: - - postgres: - image: postgres:17 - environment: - POSTGRES_USER: ${DB_USERNAME} - POSTGRES_PASSWORD: ${DB_PASSWORD} - POSTGRES_DB: ${DB_NAME} - ports: - - "5434:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - - redis: - image: redis:7.4 - restart: always - ports: - - "6380:6379" - - platform: - build: - context: ../.. - dockerfile: Dockerfile - environment: - - RUN_MODE=production - - DB_PORT=5434 - - REDIS_PORT=6380 - ports: - - "8081:8080" - depends_on: - - postgres - - redis - - vector: - image: timberio/vector:0.47.0-alpine - volumes: - - ./vector/vector.toml:/etc/vector/vector.toml - - vector_data:/var/lib/vector - ports: - - "9000:9000" - command: ["vector", "-c", "/etc/vector/vector.toml"] - depends_on: - - postgres - - platform - -volumes: - postgres_data: - vector_data: diff --git a/scripts/pre/vector/vector.toml b/scripts/pre/vector/vector.toml deleted file mode 100644 index 3b5296e..0000000 --- a/scripts/pre/vector/vector.toml +++ /dev/null @@ -1,43 +0,0 @@ -## 源配置:从 Docker 获取容器日志 - -[sources.platform_logs] -type = "docker_logs" -include_containers = ["platform"] - -## 转换配置:为日志添加元数据 - -[transforms.platform_logs_parse] -type = "remap" -inputs = ["platform_logs"] -source = ''' -.container = "platform" -json, err = parse_json(.message) -if err != null { - log.error("日志转换 json 格式失败: {}", err) - .tag = "error" - return -} -. = merge(., json) -''' - -[transform.platform_logs_route] -type = "route" -inputs = ["platform_logs_parse"] - -[transform.platform_logs_route.route] -request = '.message == "接口请求"' -usage = '.message == "创建通道"' - -## 输出配置:将日志保存到 postgresql - -[sinks.platform_logs_request] -type = "postgres" -inputs = ["platform_logs_route.request"] - -[sinks.platform_logs_login] -type = "postgres" -inputs = ["platform_logs_route.login"] - -[sinks.platform_logs_usage] -type = "postgres" -inputs = ["platform_logs_route.usage"] diff --git a/scripts/sql/init.sql b/scripts/sql/init.sql index b47f31a..d94994e 100644 --- a/scripts/sql/init.sql +++ b/scripts/sql/init.sql @@ -5,32 +5,32 @@ -- logs_request drop table if exists logs_request cascade; create table logs_request ( - id serial primary key, + id serial primary key, - identity int not null, - visitor int, - ip varchar(45) not null, - ua varchar(255), + ip varchar(45) not null, + ua varchar(255) not null, + user_id int, + client_id int, - method varchar(10) not null, - path varchar(255) not null, + method varchar(10) not null, + path varchar(255) not null, - status int not null, - error text, + status int not null, + error text, - time timestamp not null, - latency varchar(255) not null + time timestamp not null, + latency varchar(255) not null ); -create index logs_request_identity_index on logs_request (identity); -create index logs_request_visitor_index on logs_request (visitor); +create index logs_request_user_id_index on logs_request (user_id); +create index logs_request_client_id_index on logs_request (client_id); -- logs_access表字段注释 comment on table logs_request is '访问日志表'; comment on column logs_request.id is '访问日志ID'; -comment on column logs_request.identity is '访客身份:0-游客,1-用户,2-管理员,3-公共服务,4-安全服务,5-内部服务'; -comment on column logs_request.visitor is '访客ID'; comment on column logs_request.ip is 'IP地址'; comment on column logs_request.ua is '用户代理'; +comment on column logs_request.user_id is '用户ID'; +comment on column logs_request.client_id is '客户端ID'; comment on column logs_request.method is '请求方法'; comment on column logs_request.path is '请求路径'; comment on column logs_request.status is '响应状态码'; @@ -131,8 +131,8 @@ create table admin ( last_login timestamp, last_login_host varchar(45), last_login_agent varchar(255), - created_at timestamp default current_timestamp, - updated_at timestamp default current_timestamp, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp, deleted_at timestamp ); create index admin_status_index on admin (status); @@ -161,8 +161,8 @@ create table admin_role ( id serial primary key, name varchar(255) not null unique, description varchar(255), - active bool default true, - sort int default 0, + active bool default true, + sort int default 0, created_at timestamp default current_timestamp, updated_at timestamp default current_timestamp, deleted_at timestamp @@ -190,8 +190,8 @@ create table announcement ( pin bool not null default false, status int not null default 1, sort int not null default 0, - created_at timestamp default current_timestamp, - updated_at timestamp default current_timestamp, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp, deleted_at timestamp ); create index announcement_status_index on announcement (status); @@ -220,9 +220,7 @@ comment on column announcement.deleted_at is '删除时间'; drop table if exists "user" cascade; create table "user" ( id serial primary key, - admin_id int references admin (id) -- - on update cascade -- - on delete set null, + admin_id int, phone varchar(255) not null unique, username varchar(255), email varchar(255), @@ -239,8 +237,8 @@ create table "user" ( last_login timestamp, last_login_host varchar(45), last_login_agent varchar(255), - created_at timestamp default current_timestamp, - updated_at timestamp default current_timestamp, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp, deleted_at timestamp ); create index user_admin_id_index on "user" (admin_id); @@ -278,8 +276,8 @@ create table user_role ( id serial primary key, name varchar(255) not null unique, description varchar(255), - active bool default true, - sort int default 0, + active bool default true, + sort int default 0, created_at timestamp default current_timestamp, updated_at timestamp default current_timestamp, deleted_at timestamp @@ -305,21 +303,18 @@ comment on column user_role.deleted_at is '删除时间'; 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, - grant_password bool not null default false, - spec int not null, - name varchar(255) not null, - icon varchar(255), - status int not null default 1, - created_at timestamp default current_timestamp, - updated_at timestamp default current_timestamp, - deleted_at timestamp + id serial primary key, + client_id varchar(255) not null unique, + client_secret varchar(255) not null, + redirect_uri varchar(255), + spec int not null, + name varchar(255) not null, + icon varchar(255), + status int not null default 1, + type int not null default 0, + 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); @@ -333,14 +328,11 @@ 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.grant_password is '允许密码授予'; -comment on column client.spec is '安全规范:1-native,2-browser,3-web,4-trusted'; +comment on column client.spec is '安全规范:1-native,2-browser,3-web,4-api'; comment on column client.name is '名称'; comment on column client.icon is '图标URL'; comment on column client.status is '状态:0-禁用,1-正常'; +comment on column client.type is '类型:0-普通,1-官方'; comment on column client.created_at is '创建时间'; comment on column client.updated_at is '更新时间'; comment on column client.deleted_at is '删除时间'; @@ -355,25 +347,22 @@ comment on column client.deleted_at is '删除时间'; drop table if exists session cascade; create table session ( id serial primary key, - user_id int references "user" (id) - on update cascade - on delete cascade, - client_id int references client (id) - on update cascade - on delete cascade, + user_id int, + admin_id int, + client_id int, ip varchar(45), ua varchar(255), - grant_type varchar(255) not null default 0, access_token varchar(255) not null unique, access_token_expires timestamp not null, refresh_token varchar(255) unique, refresh_token_expires timestamp, scopes varchar(255), - created_at timestamp default current_timestamp, - updated_at timestamp default current_timestamp, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp, deleted_at timestamp ); create index session_user_id_index on session (user_id); +create index session_admin_id_index on session (admin_id); create index session_client_id_index on session (client_id); create index session_access_token_index on session (access_token); create index session_refresh_token_index on session (refresh_token); @@ -384,10 +373,10 @@ create index session_deleted_at_index on session (deleted_at); comment on table session is '会话表'; comment on column session.id is '会话ID'; comment on column session.user_id is '用户ID'; +comment on column session.admin_id is '管理员ID'; comment on column session.client_id is '客户端ID'; comment on column session.ip is 'IP地址'; comment on column session.ua is '用户代理'; -comment on column session.grant_type is '授权类型:authorization_code-授权码模式,client_credentials-客户端凭证模式,refresh_token-刷新令牌模式,password-密码模式'; comment on column session.access_token is '访问令牌'; comment on column session.access_token_expires is '访问令牌过期时间'; comment on column session.refresh_token is '刷新令牌'; @@ -401,9 +390,7 @@ comment on column session.deleted_at is '删除时间'; drop table if exists permission cascade; create table permission ( id serial primary key, - parent_id int references permission (id) - on update cascade - on delete cascade, + parent_id int, name varchar(255) not null unique, description varchar(255), created_at timestamp default current_timestamp, @@ -428,12 +415,8 @@ comment on column permission.deleted_at is '删除时间'; drop table if exists user_role_link cascade; create table user_role_link ( id serial primary key, - user_id int not null references "user" (id) - on update cascade - on delete cascade, - role_id int not null references user_role (id) - on update cascade - on delete cascade, + user_id int not null, + role_id int not null, created_at timestamp default current_timestamp, updated_at timestamp default current_timestamp, deleted_at timestamp @@ -455,12 +438,8 @@ comment on column user_role_link.deleted_at is '删除时间'; drop table if exists admin_role_link cascade; create table admin_role_link ( id serial primary key, - admin_id int not null references admin (id) - on update cascade - on delete cascade, - role_id int not null references admin_role (id) - on update cascade - on delete cascade, + admin_id int not null, + role_id int not null, created_at timestamp default current_timestamp, updated_at timestamp default current_timestamp, deleted_at timestamp @@ -482,12 +461,8 @@ comment on column admin_role_link.deleted_at is '删除时间'; drop table if exists user_role_permission_link cascade; create table user_role_permission_link ( id serial primary key, - role_id int not null references user_role (id) - on update cascade - on delete cascade, - permission_id int not null references permission (id) - on update cascade - on delete cascade, + role_id int not null, + permission_id int not null, created_at timestamp default current_timestamp, updated_at timestamp default current_timestamp, deleted_at timestamp @@ -509,12 +484,8 @@ comment on column user_role_permission_link.deleted_at is '删除时间'; drop table if exists admin_role_permission_link cascade; create table admin_role_permission_link ( id serial primary key, - role_id int not null references admin_role (id) - on update cascade - on delete cascade, - permission_id int not null references permission (id) - on update cascade - on delete cascade, + role_id int not null, + permission_id int not null, created_at timestamp default current_timestamp, updated_at timestamp default current_timestamp, deleted_at timestamp @@ -536,12 +507,8 @@ comment on column admin_role_permission_link.deleted_at is '删除时间'; 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, + client_id int not null, + permission_id int not null, created_at timestamp default current_timestamp, updated_at timestamp default current_timestamp, deleted_at timestamp @@ -602,9 +569,7 @@ comment on column proxy.deleted_at is '删除时间'; drop table if exists edge cascade; create table edge ( id serial primary key, - proxy_id int references proxy (id) - on update cascade - on delete cascade, + proxy_id int, type int not null, version int not null, name varchar(255) not null unique, @@ -614,10 +579,10 @@ create table edge ( city varchar(255) not null, proxy_port int, status int not null default 0, - rtt int default 0, - loss int default 0, - created_at timestamp default current_timestamp, - updated_at timestamp default current_timestamp, + rtt int default 0, + loss int default 0, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp, deleted_at timestamp ); create index edge_proxy_id_index on edge (proxy_id); @@ -650,9 +615,7 @@ comment on column edge.deleted_at is '删除时间'; drop table if exists whitelist cascade; create table whitelist ( id serial primary key, - user_id int not null references "user" (id) - on update cascade - on delete cascade, + user_id int not null, host varchar(45) not null, remark varchar(255), created_at timestamp default current_timestamp, @@ -677,18 +640,10 @@ comment on column whitelist.deleted_at is '删除时间'; drop table if exists channel cascade; create table channel ( id serial primary key, - user_id int not null references "user" (id) - on update cascade - on delete cascade, - proxy_id int not null references proxy (id) -- - on update cascade -- - on delete set null, - edge_id int references edge (id) -- - on update cascade -- - on delete set null, - resource_id int not null references resource (id) -- - on update cascade -- - on delete set null, + user_id int not null, + proxy_id int not null, + edge_id int, + resource_id int not null, proxy_host varchar(255) not null default '', proxy_port int not null, edge_host varchar(255), @@ -699,8 +654,8 @@ create table channel ( username varchar(255), password varchar(255), expiration timestamp not null, - created_at timestamp default current_timestamp, - updated_at timestamp default current_timestamp, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp, deleted_at timestamp ); create index channel_user_id_index on channel (user_id); @@ -748,8 +703,8 @@ create table product ( description varchar(255), sort int not null default 0, status int not null default 1, - created_at timestamp default current_timestamp, - updated_at timestamp default current_timestamp, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp, deleted_at timestamp ); create index product_deleted_at_index on product (deleted_at); @@ -770,14 +725,12 @@ comment on column product.deleted_at is '删除时间'; drop table if exists resource cascade; create table resource ( id serial primary key, - user_id int not null references "user" (id) - on update cascade - on delete cascade, + user_id int not null, resource_no varchar(255) unique, active bool not null default true, type int not null, - created_at timestamp default current_timestamp, - updated_at timestamp default current_timestamp, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp, deleted_at timestamp ); create index resource_user_id_index on resource (user_id); @@ -797,13 +750,11 @@ comment on column resource.created_at is '创建时间'; comment on column resource.updated_at is '更新时间'; comment on column resource.deleted_at is '删除时间'; --- resource_short +-- resource_short drop table if exists resource_short cascade; create table resource_short ( id serial primary key, - resource_id int not null references resource (id) - on update cascade - on delete cascade, + resource_id int not null, type int not null, live int not null, expire timestamp, @@ -832,9 +783,7 @@ comment on column resource_short.daily_last is '今日最后使用时间'; drop table if exists resource_long cascade; create table resource_long ( id serial primary key, - resource_id int not null references resource (id) - on update cascade - on delete cascade, + resource_id int not null, type int not null, live int not null, expire timestamp, @@ -869,9 +818,7 @@ comment on column resource_long.daily_last is '今日最后使用时间'; drop table if exists trade cascade; create table trade ( id serial primary key, - user_id int not null references "user" (id) - on update cascade - on delete cascade, + user_id int not null, inner_no varchar(255) not null unique, outer_no varchar(255), type int not null, @@ -887,8 +834,8 @@ create table trade ( payment_url text, completed_at timestamp, canceled_at timestamp, - created_at timestamp default current_timestamp, - updated_at timestamp default current_timestamp, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp, deleted_at timestamp ); create index trade_user_id_index on trade (user_id); @@ -923,17 +870,13 @@ comment on column trade.deleted_at is '删除时间'; drop table if exists refund cascade; create table refund ( id serial primary key, - trade_id int not null references trade (id) - on update cascade - on delete cascade, - product_id int references product (id) -- - on update cascade -- - on delete set null, + trade_id int not null, + product_id int, amount decimal(12, 2) not null default 0, reason varchar(255), status int not null default 0, - created_at timestamp default current_timestamp, - updated_at timestamp default current_timestamp, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp, deleted_at timestamp ); create index refund_trade_id_index on refund (trade_id); @@ -956,24 +899,16 @@ comment on column refund.deleted_at is '删除时间'; drop table if exists bill cascade; create table bill ( id serial primary key, - user_id int not null references "user" (id) - on update cascade - on delete cascade, - trade_id int references trade (id) -- - on update cascade -- - on delete set null, - resource_id int references resource (id) -- - on update cascade -- - on delete set null, - refund_id int references refund (id) -- - on update cascade -- - on delete set null, + user_id int not null, + trade_id int, + resource_id int, + refund_id int, bill_no varchar(255) not null unique, info varchar(255), type int not null, amount decimal(12, 2) not null default 0, - created_at timestamp default current_timestamp, - updated_at timestamp default current_timestamp, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp, deleted_at timestamp ); create index bill_user_id_index on bill (user_id); @@ -1003,17 +938,15 @@ comment on column bill.deleted_at is '删除时间'; drop table if exists coupon cascade; create table coupon ( id serial primary key, - user_id int references "user" (id) - on update cascade - on delete cascade, + user_id int, code varchar(255) not null unique, remark varchar(255), amount decimal(12, 2) not null default 0, min_amount decimal(12, 2) not null default 0, status int not null default 0, expire_at timestamp, - created_at timestamp default current_timestamp, - updated_at timestamp default current_timestamp, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp, deleted_at timestamp ); create index coupon_user_id_index on coupon (user_id); @@ -1035,4 +968,119 @@ comment on column coupon.created_at is '创建时间'; comment on column coupon.updated_at is '更新时间'; comment on column coupon.deleted_at is '删除时间'; +-- endregion + +-- ==================== +-- region 外键约束 +-- ==================== + +-- user表外键 +alter table "user" + add constraint fk_user_admin_id foreign key (admin_id) references admin (id) on delete set null; + +-- session表外键 +alter table session + add constraint fk_session_user_id foreign key (user_id) references "user" (id) on delete cascade; +alter table session + add constraint fk_session_client_id foreign key (client_id) references client (id) on delete cascade; + +-- permission表外键 +alter table permission + add constraint fk_permission_parent_id foreign key (parent_id) references permission (id) on delete set null; + +-- user_role_link表外键 +alter table user_role_link + add constraint fk_user_role_link_user_id foreign key (user_id) references "user" (id) on delete cascade; +alter table user_role_link + add constraint fk_user_role_link_role_id foreign key (role_id) references user_role (id) on delete cascade; + +-- admin_role_link表外键 +alter table admin_role_link + add constraint fk_admin_role_link_admin_id foreign key (admin_id) references admin (id) on delete cascade; +alter table admin_role_link + add constraint fk_admin_role_link_role_id foreign key (role_id) references admin_role (id) on delete cascade; + +-- user_role_permission_link表外键 +alter table user_role_permission_link + add constraint fk_user_role_permission_link_role_id foreign key (role_id) references user_role (id) on delete cascade; +alter table user_role_permission_link + add constraint fk_user_role_permission_link_permission_id foreign key (permission_id) references permission (id) on delete cascade; + +-- admin_role_permission_link表外键 +alter table admin_role_permission_link + add constraint fk_admin_role_permission_link_role_id foreign key (role_id) references admin_role (id) on delete cascade; +alter table admin_role_permission_link + add constraint fk_admin_role_permission_link_permission_id foreign key (permission_id) references permission (id) on delete cascade; + +-- client_permission_link表外键 +alter table client_permission_link + add constraint fk_client_permission_link_client_id foreign key (client_id) references client (id) on delete cascade; +alter table client_permission_link + add constraint fk_client_permission_link_permission_id foreign key (permission_id) references permission (id) on delete cascade; + +-- edge表外键 +alter table edge + add constraint fk_edge_proxy_id foreign key (proxy_id) references proxy (id) on delete cascade; + +-- whitelist表外键 +alter table whitelist + add constraint fk_whitelist_user_id foreign key (user_id) references "user" (id) on delete cascade; + +-- channel表外键 +alter table channel + add constraint fk_channel_user_id foreign key (user_id) references "user" (id) on delete cascade; +alter table channel + add constraint fk_channel_proxy_id foreign key (proxy_id) references proxy (id) on delete set null; +alter table channel + add constraint fk_channel_edge_id foreign key (edge_id) references edge (id) on delete set null; +alter table channel + add constraint fk_channel_resource_id foreign key (resource_id) references resource (id) on delete set null; + +-- resource表外键 +alter table resource + add constraint fk_resource_user_id foreign key (user_id) references "user" (id) on delete cascade; + +-- resource_short表外键 +alter table resource_short + add constraint fk_resource_short_resource_id foreign key (resource_id) references resource (id) on delete cascade; + +-- resource_long表外键 +alter table resource_long + add constraint fk_resource_long_resource_id foreign key (resource_id) references resource (id) on delete cascade; + +-- trade表外键 +alter table trade + add constraint fk_trade_user_id foreign key (user_id) references "user" (id) on delete set null; + +-- refund表外键 +alter table refund + add constraint fk_refund_trade_id foreign key (trade_id) references trade (id) on delete cascade; +alter table refund + add constraint fk_refund_product_id foreign key (product_id) references product (id) on delete set null; + +-- bill表外键 +alter table bill + add constraint fk_bill_user_id foreign key (user_id) references "user" (id) on delete cascade; +alter table bill + add constraint fk_bill_trade_id foreign key (trade_id) references trade (id) on delete set null; +alter table bill + add constraint fk_bill_resource_id foreign key (resource_id) references resource (id) on delete set null; +alter table bill + add constraint fk_bill_refund_id foreign key (refund_id) references refund (id) on delete set null; + +-- coupon表外键 +alter table coupon + add constraint fk_coupon_user_id foreign key (user_id) references "user" (id) on delete cascade; + +-- endregion + +-- ==================== +-- region 填充数据 +-- ==================== + +insert into client ( + client_id, client_secret, redirect_uri, spec, name, type +) +values ('web', '$2a$10$Ss12mXQgpYyo1CKIZ3URouDm.Lc2KcYJzsvEK2PTIXlv6fHQht45a', '', 3, 'web', 1) + -- endregion \ No newline at end of file diff --git a/web/auth/authenticate.go b/web/auth/authenticate.go index f0f76df..27257d0 100644 --- a/web/auth/authenticate.go +++ b/web/auth/authenticate.go @@ -4,112 +4,101 @@ import ( "context" "encoding/base64" "errors" + "fmt" "log/slog" + "platform/web/core" client2 "platform/web/domains/client" + m "platform/web/models" q "platform/web/queries" - "slices" + s "platform/web/services" "strings" + "time" "github.com/gofiber/fiber/v2" "golang.org/x/crypto/bcrypt" ) -type ProtectBuilder struct { - c *fiber.Ctx - types []PayloadType - scopes []string +func Authenticate() fiber.Handler { + return func(ctx *fiber.Ctx) error { + header := ctx.Get(fiber.HeaderAuthorization) + authCtx, err := authHeader(ctx.Context(), header) + if err != nil { + return err + } + + if authCtx == nil { + authCtx = &AuthCtx{} + } + + SetAuthCtx(ctx, authCtx) + return ctx.Next() + } } -func NewProtect(c *fiber.Ctx) *ProtectBuilder { - return &ProtectBuilder{c, []PayloadType{}, []string{}} -} +func authHeader(ctx context.Context, header string) (*AuthCtx, error) { + if header == "" { + return nil, nil + } -func (p *ProtectBuilder) Payload(types ...PayloadType) *ProtectBuilder { - p.types = types - return p -} - -func (p *ProtectBuilder) Scopes(scopes ...string) *ProtectBuilder { - p.scopes = scopes - return p -} - -func (p *ProtectBuilder) Do() (*Context, error) { - return Protect(p.c, p.types, p.scopes) -} - -func Protect(c *fiber.Ctx, types []PayloadType, permissions []string) (*Context, error) { - // 获取令牌 - var header = c.Get("Authorization") var split = strings.Split(header, " ") if len(split) != 2 { slog.Debug("Authorization 头格式不正确") - return nil, ErrUnauthorize + return nil, ErrAuthenticateUnauthorize } var token = strings.TrimSpace(split[1]) if token == "" { slog.Debug("提供的令牌为空") - return nil, ErrUnauthorize + return nil, ErrAuthenticateUnauthorize } - var auth *Context + var authCtx *AuthCtx var err error switch split[0] { case "Bearer": - auth, err = authBearer(c.Context(), token) + authCtx, err = authBearer(ctx, token) if err != nil { slog.Debug("Bearer 认证失败", "err", err) - return nil, ErrUnauthorize + return nil, ErrAuthenticateUnauthorize } case "Basic": - if !slices.Contains(types, PayloadInternalServer) { - slog.Debug("禁止使用 Basic 认证方式") - return nil, ErrUnauthorize - } - auth, err = authBasic(c.Context(), token) + authCtx, err = authBasic(ctx, token) if err != nil { slog.Debug("Basic 认证失败", "err", err) - return nil, ErrUnauthorize + return nil, ErrAuthenticateUnauthorize } default: slog.Debug("无效的认证方式", "method", split[0]) - return nil, ErrUnauthorize + return nil, ErrAuthenticateUnauthorize } - // 检查权限 - if !slices.Contains(types, auth.Payload.Type) { - slog.Debug("无效的负载类型", "except", types, "actual", auth.Payload.Type) - return nil, ErrForbidden - } - - if len(permissions) > 0 && !auth.AnyPermission(permissions...) { - slog.Debug("无效的认证权限", "except", permissions, "actual", auth.Permissions) - return nil, ErrForbidden - } - - // 保存到上下文 - Locals(c, auth) - return auth, nil + return authCtx, err } -func Locals(c *fiber.Ctx, auth *Context) { - c.Locals("auth", auth) -} - -func authBearer(ctx context.Context, token string) (*Context, error) { - auth, err := FindSession(ctx, token) +func authBearer(_ context.Context, token string) (*AuthCtx, error) { + session, err := FindSession(token, time.Now()) if err != nil { - slog.Debug(err.Error()) - return nil, err + slog.Debug("Bearer 认证失败", "err", err) + return nil, ErrAuthenticateUnauthorize } - return auth, nil + + scopes := []string{} + if session.Scopes_ != nil { + scopes = strings.Split(*session.Scopes_, " ") + } + return &AuthCtx{ + User: session.User, + Admin: session.Admin, + Client: session.Client, + Scopes: scopes, + Session: session, + }, nil } -func authBasic(_ context.Context, token string) (*Context, error) { +func authBasic(_ context.Context, token string) (*AuthCtx, error) { // 解析 Basic 认证信息 var base, err = base64.RawURLEncoding.DecodeString(token) @@ -125,14 +114,23 @@ func authBasic(_ context.Context, token string) (*Context, error) { return nil, errors.New("令牌格式错误,必须是 : 格式") } - var clientID = split[0] + client, err := authClient(split[0], split[1]) + if err != nil { + return nil, fmt.Errorf("客户端认证失败:%w", err) + } + + return &AuthCtx{ + Client: client, + Scopes: []string{}, + }, nil +} + +func authClient(clientId, clientSecret string) (*m.Client, error) { // 获取客户端信息 client, err := q.Client. Where( - q.Client.ClientID.Eq(clientID), - q.Client.Spec.In(int32(client2.SpecWeb), int32(client2.SpecTrusted)), - q.Client.GrantClient.Is(true), + q.Client.ClientID.Eq(clientId), q.Client.Status.Eq(1)). Take() if err != nil { @@ -140,33 +138,57 @@ func authBasic(_ context.Context, token string) (*Context, error) { } // 检查客户端密钥 - var clientSecret = split[1] - if bcrypt.CompareHashAndPassword([]byte(client.ClientSecret), []byte(clientSecret)) != nil { - return nil, errors.New("客户端密钥错误") + spec := client2.Spec(client.Spec) + if spec == client2.SpecWeb || spec == client2.SpecApi { + if bcrypt.CompareHashAndPassword([]byte(client.ClientSecret), []byte(clientSecret)) != nil { + return nil, errors.New("客户端密钥错误") + } } // todo 查询客户端关联权限 // 组织授权信息(一次性请求) - return &Context{ - Payload: Payload{ - Id: client.ID, - Type: PayloadTypeFromClientSpec(client2.Spec(client.Spec)), - Name: client.Name, - Avatar: client.Icon, - }, - Permissions: nil, - Metadata: nil, - }, nil + return client, nil } -type AuthenticationErr string +func authUserBySms(tx *q.Query, username, code string) (*m.User, error) { + // 验证验证码 + err := s.Verifier.VerifySms(context.Background(), username, code) + if err != nil { + if errors.Is(err, s.ErrVerifierServiceInvalid) { + return nil, ErrAuthorizeInvalidRequest + } + return nil, err + } -func (e AuthenticationErr) Error() string { - return string(e) + // 查找用户 + return tx.User.Where(tx.User.Phone.Eq(username)).Take() } -var ( - ErrUnauthorize = AuthenticationErr("令牌无效") - ErrForbidden = AuthenticationErr("没有权限") -) +func authUserByEmail(tx *q.Query, username, code string) (*m.User, error) { + return nil, core.NewServErr("邮箱登录不可用") +} + +func authUserByPassword(tx *q.Query, username, password string) (*m.User, error) { + user, err := tx.User. + Where(tx.User.Phone.Eq(username)). + Or(tx.User.Email.Eq(username)). + Or(tx.User.Username.Eq(username)). + Take() + if err != nil { + slog.Debug("查找用户失败", "error", err) + return nil, core.NewBizErr("用户不存在或密码错误") + } + + // 验证密码 + if user.Password == nil || *user.Password == "" { + slog.Debug("用户未设置密码", "username", username) + return nil, core.NewBizErr("用户不存在或密码错误") + } + if bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(password)) != nil { + slog.Debug("密码验证失败", "username", username) + return nil, core.NewBizErr("用户不存在或密码错误") + } + + return user, nil +} diff --git a/web/auth/authorize.go b/web/auth/authorize.go index 45adf46..1e7ffb9 100644 --- a/web/auth/authorize.go +++ b/web/auth/authorize.go @@ -1,5 +1,28 @@ package auth +import ( + "context" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "errors" + "log/slog" + "platform/pkg/env" + "platform/pkg/u" + "platform/web/core" + user2 "platform/web/domains/user" + g "platform/web/globals" + "platform/web/globals/orm" + m "platform/web/models" + q "platform/web/queries" + "strings" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "gorm.io/gorm" +) + type GrantType string const ( @@ -17,36 +40,352 @@ const ( GrantPasswordEmail = PasswordGrantType("email_code") // 邮箱验证码 ) -func Token(grant GrantType) error { - return nil +type TokenReq struct { + GrantType GrantType `json:"grant_type" form:"grant_type"` + ClientID string `json:"client_id" form:"client_id"` + ClientSecret string `json:"client_secret" form:"client_secret"` + Scope string `json:"scope" form:"scope"` + GrantCodeData + GrantClientData + GrantRefreshData + GrantPasswordData } -func authAuthorizationCode() { - +type GrantCodeData struct { + Code string `json:"code" form:"code"` + RedirectURI string `json:"redirect_uri" form:"redirect_uri"` + CodeVerifier string `json:"code_verifier" form:"code_verifier"` } -func authClientCredential() { - +type GrantClientData struct { } -func authRefreshToken() { - +type GrantRefreshData struct { + RefreshToken string `json:"refresh_token" form:"refresh_token"` } -func authPassword() { - +type GrantPasswordData struct { + LoginType PasswordGrantType `json:"login_type" form:"login_type"` + Username string `json:"username" form:"username"` + Password string `json:"password" form:"password"` + Remember bool `json:"remember" form:"remember"` } -func authPasswordSecret() { - +type TokenResp struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token,omitempty"` + ExpiresIn int `json:"expires_in"` + TokenType string `json:"token_type"` + Scope string `json:"scope,omitempty"` } -func authPasswordPhone() { - +type TokenErrResp struct { + Error string `json:"error"` + Description string `json:"error_description,omitempty"` } -func authPasswordEmail() { +func Token(c *fiber.Ctx) error { + now := time.Now() + // 验证请求参数 + req := new(TokenReq) + if err := c.BodyParser(req); err != nil { + return sendError(c, ErrAuthorizeInvalidRequest, "无法解析请求参数") + } + if req.GrantType == "" { + return sendError(c, ErrAuthorizeInvalidRequest, "缺少必要参数: grant_type") + } + + switch req.GrantType { + // 授权码模式 + case GrantAuthorizationCode: + if req.Code == "" { + return sendError(c, ErrAuthorizeInvalidRequest, "缺少必要参数: code") + } + // 刷新令牌模式 + case GrantRefreshToken: + if req.RefreshToken == "" { + return sendError(c, ErrAuthorizeInvalidRequest, "缺少必要参数: refresh_token") + } + // 密码模式 + case GrantPassword: + if req.LoginType == "" { + return sendError(c, ErrAuthorizeInvalidRequest, "缺少必要参数: password_type") + } + if req.Username == "" { + return sendError(c, ErrAuthorizeInvalidRequest, "缺少必要参数: username") + } + if req.Password == "" { + return sendError(c, ErrAuthorizeInvalidRequest, "缺少必要参数: password") + } + } + + // 验证客户端身份 + authCtx := GetAuthCtx(c) + if authCtx == nil { + authCtx = &AuthCtx{} + } + if authCtx.Client == nil { + client, err := authClient(req.ClientID, req.ClientSecret) + if err != nil { + return sendError(c, err) + } + authCtx.Client = client + } + + // 处理授权 + var session *m.Session + var err error + switch req.GrantType { + // 授权码模式 + case GrantAuthorizationCode: + session, err = authAuthorizationCode(c, authCtx, req, now) + // 客户端凭证模式 + case GrantClientCredentials: + session, err = authClientCredential(c, authCtx, req, now) + // 刷新令牌模式 + case GrantRefreshToken: + session, err = authRefreshToken(c, authCtx, req, now) + // 密码模式 + case GrantPassword: + session, err = authPassword(c, authCtx, req, now) + default: + return sendError(c, ErrAuthorizeUnsupportedGrantType) + } + if err != nil { + return sendError(c, err) + } + + // 返回响应 + return c.JSON(&TokenResp{ + TokenType: "Bearer", + AccessToken: session.AccessToken, + RefreshToken: u.Z(session.RefreshToken), + ExpiresIn: int(time.Time(session.AccessTokenExpires).Sub(now).Seconds()), + Scope: u.Z(session.Scopes_), + }) +} + +func authAuthorizationCode(ctx *fiber.Ctx, auth *AuthCtx, req *TokenReq, now time.Time) (*m.Session, error) { + + // 检查 code 获取用户授权信息 + data, err := g.Redis.Get(context.Background(), req.Code).Result() + if err != nil { + return nil, err + } + var codeCtx CodeContext + if err := json.Unmarshal([]byte(data), &codeCtx); err != nil { + return nil, err + } + + // 检查 PKCE + if codeCtx.CodeChallengeMethod != "" { + if req.CodeVerifier == "" { + return nil, ErrAuthorizeInvalidPKCE + } + + switch codeCtx.CodeChallengeMethod { + case "plain": + if req.CodeVerifier != codeCtx.CodeChallenge { + return nil, ErrAuthorizeInvalidPKCE + } + case "S256": + hash := sha256.Sum256([]byte(req.CodeVerifier)) + verifier := base64.RawURLEncoding.EncodeToString(hash[:]) + if verifier != codeCtx.CodeChallenge { + return nil, ErrAuthorizeInvalidPKCE + } + default: + return nil, ErrAuthorizeInvalidPKCE + } + } + + user, err := q.User.Where( + q.User.ID.Eq(codeCtx.UserID), + q.User.Status.Eq(int32(user2.StatusEnabled)), + ).First() + if err != nil { + return nil, err + } + + // todo 检查 scope + + // 生成会话 + session := &m.Session{ + IP: u.X(ctx.IP()), + UA: u.X(ctx.Get(fiber.HeaderUserAgent)), + UserID: &user.ID, + ClientID: &auth.Client.ID, + Scopes_: u.P(strings.Join(codeCtx.Scopes, " ")), + AccessToken: uuid.NewString(), + AccessTokenExpires: orm.LocalDateTime(now.Add(time.Duration(env.SessionAccessExpire) * time.Second)), + } + if codeCtx.Remember { + session.RefreshToken = u.P(uuid.NewString()) + session.RefreshTokenExpires = u.P(orm.LocalDateTime(now.Add(time.Duration(env.SessionRefreshExpire) * time.Second))) + } + + err = SaveSession(session) + if err != nil { + return nil, err + } + + return session, nil +} + +func authClientCredential(ctx *fiber.Ctx, auth *AuthCtx, _ *TokenReq, now time.Time) (*m.Session, error) { + // todo 检查 scope + + // 生成会话 + session := &m.Session{ + IP: u.X(ctx.IP()), + UA: u.X(ctx.Get(fiber.HeaderUserAgent)), + ClientID: &auth.Client.ID, + AccessToken: uuid.NewString(), + AccessTokenExpires: orm.LocalDateTime(now.Add(time.Duration(env.SessionAccessExpire) * time.Second)), + } + + // 保存会话 + err := SaveSession(session) + if err != nil { + return nil, err + } + + return session, nil +} + +func authPassword(ctx *fiber.Ctx, auth *AuthCtx, req *TokenReq, now time.Time) (*m.Session, error) { + var user *m.User + err := q.Q.Transaction(func(tx *q.Query) (err error) { + switch req.LoginType { + case GrantPasswordPhone: + user, err = authUserBySms(tx, req.Username, req.Password) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return err + } + if user == nil { + user = &m.User{ + Phone: req.Username, + Username: u.P(req.Username), + Status: int32(user2.StatusEnabled), + } + } + case GrantPasswordEmail: + user, err = authUserByEmail(tx, req.Username, req.Password) + if err != nil { + return err + } + case GrantPasswordSecret: + user, err = authUserByPassword(tx, req.Username, req.Password) + if err != nil { + return err + } + default: + return ErrAuthorizeInvalidRequest + } + + // 账户状态 + if user2.Status(user.Status) == user2.StatusDisabled { + slog.Debug("账户状态异常", "username", req.Username, "status", user.Status) + return core.NewBizErr("账号无法登录") + } + + // 更新用户的登录时间 + user.LastLogin = u.P(orm.LocalDateTime(time.Now())) + user.LastLoginHost = u.X(ctx.IP()) + user.LastLoginAgent = u.X(ctx.Get(fiber.HeaderUserAgent)) + if err := tx.User.Save(user); err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, err + } + + // 生成会话 + session := &m.Session{ + IP: u.X(ctx.IP()), + UA: u.X(ctx.Get(fiber.HeaderUserAgent)), + UserID: &user.ID, + ClientID: &auth.Client.ID, + Scopes_: u.X(req.Scope), + AccessToken: uuid.NewString(), + AccessTokenExpires: orm.LocalDateTime(now.Add(time.Duration(env.SessionAccessExpire) * time.Second)), + } + if req.Remember { + session.RefreshToken = u.P(uuid.NewString()) + session.RefreshTokenExpires = u.P(orm.LocalDateTime(now.Add(time.Duration(env.SessionRefreshExpire) * time.Second))) + } + + err = SaveSession(session) + if err != nil { + return nil, err + } + + return session, nil +} + +func authRefreshToken(_ *fiber.Ctx, _ *AuthCtx, req *TokenReq, now time.Time) (*m.Session, error) { + session, err := FindSessionByRefresh(req.RefreshToken, now) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrAuthorizeInvalidGrant + } + return nil, err + } + + // todo 检查权限 + + // 生成令牌 + session.AccessToken = uuid.NewString() + session.AccessTokenExpires = orm.LocalDateTime(now.Add(time.Duration(env.SessionAccessExpire) * time.Second)) + if session.RefreshToken != nil { + session.RefreshToken = u.P(uuid.NewString()) + session.RefreshTokenExpires = u.P(orm.LocalDateTime(now.Add(time.Duration(env.SessionRefreshExpire) * time.Second))) + } + + // 保存令牌 + err = SaveSession(session) + if err != nil { + return nil, err + } + + return session, nil +} + +func sendError(c *fiber.Ctx, err error, description ...string) error { + var sErr AuthErr + if errors.As(err, &sErr) { + status := fiber.StatusBadRequest + var desc string + switch { + case errors.Is(sErr, ErrAuthorizeInvalidRequest): + desc = "无效的请求" + case errors.Is(sErr, ErrAuthorizeInvalidClient): + status = fiber.StatusUnauthorized + desc = "无效的客户端凭证" + case errors.Is(sErr, ErrAuthorizeInvalidGrant): + desc = "无效的授权凭证" + case errors.Is(sErr, ErrAuthorizeInvalidScope): + desc = "无效的授权范围" + case errors.Is(sErr, ErrAuthorizeUnauthorizedClient): + desc = "未授权的客户端" + case errors.Is(sErr, ErrAuthorizeUnsupportedGrantType): + desc = "不支持的授权类型" + } + if len(description) > 0 { + desc = description[0] + } + + return c.Status(status).JSON(TokenErrResp{ + Error: string(sErr), + Description: desc, + }) + } + + return err } func Revoke() error { @@ -56,3 +395,12 @@ func Revoke() error { func Introspect() error { return nil } + +type CodeContext struct { + UserID int32 `json:"user_id"` + ClientID int32 `json:"client_id"` + Scopes []string `json:"scopes"` + Remember bool `json:"remember"` + CodeChallenge string `json:"code_challenge"` + CodeChallengeMethod string `json:"code_challenge_method"` +} diff --git a/web/auth/check.go b/web/auth/check.go new file mode 100644 index 0000000..269242b --- /dev/null +++ b/web/auth/check.go @@ -0,0 +1,99 @@ +package auth + +import ( + "platform/web/domains/client" + m "platform/web/models" + + "github.com/gofiber/fiber/v2" +) + +type AuthCtx struct { + User *m.User `json:"account,omitempty"` + Admin *m.Admin `json:"admin,omitempty"` + Client *m.Client `json:"client,omitempty"` + Scopes []string `json:"scopes,omitempty"` + Session *m.Session `json:"session,omitempty"` + smap map[string]struct{} +} + +func (a *AuthCtx) PermitUser(scopes ...string) (*AuthCtx, error) { + if a.User == nil { + return a, ErrAuthenticateForbidden + } + if !a.checkScopes(scopes...) { + return a, ErrAuthenticateForbidden + } + return a, nil +} + +func (a *AuthCtx) PermitAdmin(scopes ...string) (*AuthCtx, error) { + if a.Admin == nil { + return a, ErrAuthenticateForbidden + } + if !a.checkScopes(scopes...) { + return a, ErrAuthenticateForbidden + } + return a, nil +} + +func (a *AuthCtx) PermitSecretClient(scopes ...string) (*AuthCtx, error) { + if a.Client == nil { + return a, ErrAuthenticateForbidden + } + spec := client.Spec(a.Client.Spec) + if spec != client.SpecApi && spec != client.SpecWeb { + return a, ErrAuthenticateForbidden + } + if !a.checkScopes(scopes...) { + return a, ErrAuthenticateForbidden + } + return a, nil +} + +func (a *AuthCtx) PermitInternalClient(scopes ...string) (*AuthCtx, error) { + if a.Client == nil { + return a, ErrAuthenticateForbidden + } + spec := client.Spec(a.Client.Spec) + if spec != client.SpecApi && spec != client.SpecWeb { + return a, ErrAuthenticateForbidden + } + cType := client.Type(a.Client.Type) + if cType != client.TypeInternal { + return a, ErrAuthenticateForbidden + } + if !a.checkScopes(scopes...) { + return a, ErrAuthenticateForbidden + } + return a, nil +} + +func (a *AuthCtx) checkScopes(scopes ...string) bool { + if len(scopes) == 0 || len(a.Scopes) == 0 { + return true + } + if len(a.smap) == 0 && len(a.Scopes) > 0 { + for _, scope := range scopes { + a.smap[scope] = struct{}{} + } + } + for _, scope := range scopes { + if _, ok := a.smap[scope]; ok { + return true + } + } + return false +} + +const AuthCtxKey = "session" + +func SetAuthCtx(c *fiber.Ctx, auth *AuthCtx) { + c.Locals(AuthCtxKey, auth) +} + +func GetAuthCtx(c *fiber.Ctx) *AuthCtx { + if authCtx, ok := c.Locals(AuthCtxKey).(*AuthCtx); ok { + return authCtx + } + return nil +} diff --git a/web/auth/context.go b/web/auth/context.go deleted file mode 100644 index 3f8f7a3..0000000 --- a/web/auth/context.go +++ /dev/null @@ -1,103 +0,0 @@ -package auth - -import ( - client2 "platform/web/domains/client" -) - -// Context 定义认证信息 -type Context struct { - Payload Payload `json:"payload"` - Permissions map[string]struct{} `json:"permissions,omitempty"` - Metadata map[string]interface{} `json:"metadata,omitempty"` -} - -func (a *Context) AnyType(types ...PayloadType) bool { - if a == nil { - return false - } - for _, t := range types { - if a.Payload.Type == t { - return true - } - } - return false -} - -// AnyPermission 检查认证是否包含指定权限 -func (a *Context) 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 -} - -// Payload 定义负载信息 -type Payload struct { - Id int32 `json:"id,omitempty"` - Type PayloadType `json:"type,omitempty"` - Name string `json:"name,omitempty"` - Avatar *string `json:"avatar,omitempty"` -} - -type PayloadType int - -const ( - PayloadNone PayloadType = iota // 游客 - PayloadUser // 用户 - PayloadAdmin // 管理员 - PayloadPublicServer // 公共服务(public_client) - PayloadSecuredServer // 安全服务(credential_client) - PayloadInternalServer // 内部服务 -) - -func (t PayloadType) ToStr() string { - switch t { - case PayloadUser: - return "user" - case PayloadAdmin: - return "admn" - case PayloadPublicServer: - return "cpub" - case PayloadSecuredServer: - return "ccnf" - case PayloadInternalServer: - return "inte" - default: - return "none" - } -} - -func PayloadTypeFromStr(name string) PayloadType { - switch name { - case "user": - return PayloadUser - case "admn": - return PayloadAdmin - case "cpub": - return PayloadPublicServer - case "ccnf": - return PayloadSecuredServer - case "inte": - return PayloadInternalServer - default: - return PayloadNone - } -} - -func PayloadTypeFromClientSpec(spec client2.Spec) PayloadType { - var clientType PayloadType - switch spec { - case client2.SpecNative, client2.SpecBrowser: - clientType = PayloadPublicServer - case client2.SpecWeb: - clientType = PayloadSecuredServer - case client2.SpecTrusted: - clientType = PayloadInternalServer - } - return clientType -} diff --git a/web/auth/errors.go b/web/auth/errors.go new file mode 100644 index 0000000..93b4507 --- /dev/null +++ b/web/auth/errors.go @@ -0,0 +1,24 @@ +package auth + +type AuthErr string + +func (e AuthErr) Error() string { + return string(e) +} + +// 认证错误 +const ( + ErrAuthenticateUnauthorize = AuthErr("令牌无效") + ErrAuthenticateForbidden = AuthErr("没有权限") +) + +// 授权错误 +const ( + ErrAuthorizeInvalidRequest = AuthErr("invalid_request") + ErrAuthorizeInvalidClient = AuthErr("invalid_client") + ErrAuthorizeInvalidGrant = AuthErr("invalid_grant") + ErrAuthorizeInvalidScope = AuthErr("invalid_scope") + ErrAuthorizeUnauthorizedClient = AuthErr("unauthorized_client") + ErrAuthorizeUnsupportedGrantType = AuthErr("unsupported_grant_type") + ErrAuthorizeInvalidPKCE = AuthErr("invalid_pkce") +) diff --git a/web/auth/session.go b/web/auth/session.go index 32f4f78..1070a03 100644 --- a/web/auth/session.go +++ b/web/auth/session.go @@ -2,160 +2,36 @@ package auth import ( "context" - "encoding/json" - "errors" "fmt" - "github.com/google/uuid" - "github.com/redis/go-redis/v9" - "platform/pkg/env" g "platform/web/globals" + "platform/web/globals/orm" + m "platform/web/models" + q "platform/web/queries" "time" + + "gorm.io/gen/field" ) -type Session struct { - // 认证主体 - Payload *Payload - // 令牌信息 - TokenDetails *TokenDetails +func FindSession(accessToken string, now time.Time) (*m.Session, error) { + return q.Session. + Preload(field.Associations). + Where( + q.Session.AccessToken.Eq(accessToken), + q.Session.AccessTokenExpires.Gt(orm.LocalDateTime(now)), + ).First() } -func FindSession(ctx context.Context, token string) (*Context, error) { - - // 读取认证数据 - authJSON, err := g.Redis.Get(ctx, accessKey(token)).Result() - if err != nil { - if errors.Is(err, redis.Nil) { - return nil, errors.New("invalid_token") - } - return nil, err - } - - // 反序列化 - auth := new(Context) - if err := json.Unmarshal([]byte(authJSON), auth); err != nil { - return nil, err - } - - return auth, nil +func FindSessionByRefresh(refreshToken string, now time.Time) (*m.Session, error) { + return q.Session. + Preload(field.Associations). + Where( + q.Session.RefreshToken.Eq(refreshToken), + q.Session.RefreshTokenExpires.Gt(orm.LocalDateTime(now)), + ).First() } -func CreateSession(ctx context.Context, authCtx *Context, remember bool) (*TokenDetails, error) { - var now = time.Now() - - // 生成令牌组 - accessToken := genToken() - refreshToken := genToken() - - // 序列化认证数据 - authData, err := json.Marshal(authCtx) - if err != nil { - return nil, err - } - - // 序列化刷新令牌数据 - refreshData, err := json.Marshal(RefreshData{ - AuthContext: authCtx, - AccessToken: accessToken, - }) - if err != nil { - return nil, err - } - - // 事务保存数据到 Redis - var accessExpire = time.Duration(env.SessionAccessExpire) * time.Second - var refreshExpire = time.Duration(env.SessionRefreshExpire) * time.Second - - pipe := g.Redis.TxPipeline() - pipe.Set(ctx, accessKey(accessToken), authData, accessExpire) - if remember { - pipe.Set(ctx, refreshKey(refreshToken), refreshData, refreshExpire) - } - _, err = pipe.Exec(ctx) - if err != nil { - return nil, err - } - - return &TokenDetails{ - AccessToken: accessToken, - AccessTokenExpires: now.Add(accessExpire), - RefreshToken: refreshToken, - RefreshTokenExpires: now.Add(refreshExpire), - Auth: authCtx, - }, nil -} - -func RefreshSession(ctx context.Context, refreshToken string, renew bool) (*TokenDetails, error) { - var now = time.Now() - - rKey := refreshKey(refreshToken) - var tokenDetails *TokenDetails - - // 刷新令牌 - err := g.Redis.Watch(ctx, func(tx *redis.Tx) error { - - // 先获取刷新令牌数据 - refreshJson, err := tx.Get(ctx, rKey).Result() - if err != nil { - if errors.Is(err, redis.Nil) { - return ErrInvalidRefreshToken - } - return err - } - - // 解析刷新令牌数据 - refreshData := new(RefreshData) - if err := json.Unmarshal([]byte(refreshJson), refreshData); err != nil { - return err - } - - // 生成新的令牌 - 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 := tx.Pipeline() - - // 保存新的令牌 - var accessExpire = time.Duration(env.SessionAccessExpire) * time.Second - var refreshExpire = time.Duration(env.SessionRefreshExpire) * time.Second - - pipeline.Set(ctx, accessKey(newAccessToken), authData, accessExpire) - pipeline.Set(ctx, refreshKey(newRefreshToken), newRefreshData, refreshExpire) - - // 删除旧的令牌 - pipeline.Del(ctx, accessKey(refreshData.AccessToken)) - pipeline.Del(ctx, refreshKey(refreshToken)) - - _, err = pipeline.Exec(ctx) - if err != nil { - return err - } - - tokenDetails = &TokenDetails{ - AccessToken: newAccessToken, - RefreshToken: newRefreshToken, - AccessTokenExpires: now.Add(accessExpire), - RefreshTokenExpires: now.Add(refreshExpire), - Auth: refreshData.AuthContext, - } - return nil - }, rKey) - if err != nil { - return nil, fmt.Errorf("刷新令牌失败: %w", err) - } - - return tokenDetails, nil +func SaveSession(session *m.Session) error { + return q.Session.Save(session) } func RemoveSession(ctx context.Context, accessToken string, refreshToken string) error { @@ -163,11 +39,6 @@ func RemoveSession(ctx context.Context, accessToken string, refreshToken string) return nil } -// 生成一个新的令牌 -func genToken() string { - return uuid.NewString() -} - // 令牌键的格式为 "session:" func accessKey(token string) string { return fmt.Sprintf("session:%s", token) @@ -177,32 +48,3 @@ func accessKey(token string) string { func refreshKey(token string) string { return fmt.Sprintf("session:refresh:%s", token) } - -// TokenDetails 存储令牌详细信息 -type TokenDetails struct { - // 访问令牌 - AccessToken string - // 刷新令牌 - RefreshToken string - // 访问令牌过期时间 - AccessTokenExpires time.Time - // 刷新令牌过期时间 - RefreshTokenExpires time.Time - // 认证信息 - Auth *Context -} - -type RefreshData struct { - AuthContext *Context - AccessToken string -} - -type SessionErr string - -func (e SessionErr) Error() string { - return string(e) -} - -const ( - ErrInvalidRefreshToken = SessionErr("无效的刷新令牌") -) diff --git a/web/core/types.go b/web/core/types.go index 4b0d8e9..33102ae 100644 --- a/web/core/types.go +++ b/web/core/types.go @@ -60,7 +60,7 @@ type Err struct { func (e *Err) Error() string { if e.err != nil { - return e.msg + ":" + e.err.Error() + return e.msg + ": " + e.err.Error() } return e.msg } diff --git a/web/domains/client/types.go b/web/domains/client/types.go index cd6b7d1..b8659cf 100644 --- a/web/domains/client/types.go +++ b/web/domains/client/types.go @@ -6,5 +6,12 @@ const ( SpecNative Spec = iota + 1 // 原生客户端 SpecBrowser // 浏览器客户端 SpecWeb // Web 服务 - SpecTrusted // 可信服务 + SpecApi // Api 服务 +) + +type Type int32 + +const ( + TypeNormal Type = iota // 普通客户端 + TypeInternal // 内部客户端 ) diff --git a/web/error.go b/web/error.go index 98e7268..b32d481 100644 --- a/web/error.go +++ b/web/error.go @@ -16,7 +16,7 @@ func ErrorHandler(c *fiber.Ctx, err error) error { var message = "服务器异常" var fiberErr *fiber.Error - var authErr auth.AuthenticationErr + var authErr auth.AuthErr var bizErr *core.BizErr var servErr *core.ServErr @@ -30,9 +30,9 @@ func ErrorHandler(c *fiber.Ctx, err error) error { // 认证授权错误 case errors.As(err, &authErr): switch { - case errors.Is(err, auth.ErrUnauthorize): + case errors.Is(err, auth.ErrAuthenticateUnauthorize): code = fiber.StatusUnauthorized - case errors.Is(err, auth.ErrForbidden): + case errors.Is(err, auth.ErrAuthenticateForbidden): code = fiber.StatusForbidden default: code = fiber.StatusBadRequest diff --git a/web/tasks/channel.go b/web/events/channel.go similarity index 95% rename from web/tasks/channel.go rename to web/events/channel.go index 1714c29..2de386b 100644 --- a/web/tasks/channel.go +++ b/web/events/channel.go @@ -1,4 +1,4 @@ -package tasks +package events import ( "encoding/json" diff --git a/web/tasks/log.go b/web/events/log.go similarity index 97% rename from web/tasks/log.go rename to web/events/log.go index 182e954..eec2aca 100644 --- a/web/tasks/log.go +++ b/web/events/log.go @@ -1,4 +1,4 @@ -package tasks +package events import ( "encoding/json" diff --git a/web/tasks/trade.go b/web/events/trade.go similarity index 97% rename from web/tasks/trade.go rename to web/events/trade.go index 4ee25e4..c5c94d0 100644 --- a/web/tasks/trade.go +++ b/web/events/trade.go @@ -1,10 +1,11 @@ -package tasks +package events import ( "encoding/json" - "github.com/hibiken/asynq" "log/slog" trade2 "platform/web/domains/trade" + + "github.com/hibiken/asynq" ) const CancelTrade = "trade:update" diff --git a/web/globals/alipay.go b/web/globals/alipay.go index 01d158f..5640134 100644 --- a/web/globals/alipay.go +++ b/web/globals/alipay.go @@ -1,6 +1,7 @@ package globals import ( + "fmt" "platform/pkg/env" "github.com/smartwalle/alipay/v3" @@ -8,25 +9,26 @@ import ( var Alipay *alipay.Client -func initAlipay() { +func initAlipay() error { var client, err = alipay.New( env.AlipayAppId, env.AlipayAppPrivateKey, env.AlipayProduction, ) if err != nil { - panic("初始化支付宝客户端失败: " + err.Error()) + return fmt.Errorf("初始化支付宝客户端失败: %w", err) } err = client.LoadAliPayPublicKey(env.AlipayPublicKey) if err != nil { - panic("加载支付宝公钥失败: " + err.Error()) + return fmt.Errorf("加载支付宝公钥失败: %w", err) } err = client.SetEncryptKey(env.AlipayApiCert) if err != nil { - panic("设置支付宝加密密钥失败: " + err.Error()) + return fmt.Errorf("设置支付宝加密证书失败: %w", err) } Alipay = client + return nil } diff --git a/web/globals/aliyun.go b/web/globals/aliyun.go index 1c325d9..8f0968d 100644 --- a/web/globals/aliyun.go +++ b/web/globals/aliyun.go @@ -1,6 +1,7 @@ package globals import ( + "fmt" "platform/pkg/env" "platform/pkg/u" @@ -14,17 +15,18 @@ type aliyunClient struct { Sms *sms.Client } -func initAliyun() { +func initAliyun() error { client, err := sms.NewClient(&openapi.Config{ AccessKeyId: &env.AliyunAccessKey, AccessKeySecret: &env.AliyunAccessKeySecret, Endpoint: u.P("dysmsapi.aliyuncs.com"), }) if err != nil { - panic(err) + return fmt.Errorf("初始化阿里云客户端失败: %w", err) } Aliyun = &aliyunClient{ Sms: client, } + return nil } diff --git a/web/globals/baiyin.go b/web/globals/baiyin.go index c124250..ca25807 100644 --- a/web/globals/baiyin.go +++ b/web/globals/baiyin.go @@ -35,10 +35,11 @@ type cloud struct { var Cloud CloudClient -func initBaiyin() { +func initBaiyin() error { Cloud = &cloud{ url: env.BaiyinAddr, } + return nil } type AutoConfig struct { diff --git a/web/globals/init.go b/web/globals/init.go index 6e4a108..692f436 100644 --- a/web/globals/init.go +++ b/web/globals/init.go @@ -1,14 +1,38 @@ package globals -func Init() { - initBaiyin() - initAlipay() - initWechatPay() - initAliyun() - initValidator() - initRedis() - initOrm() - initProxy() - initAsynq() - initSft() +import ( + "context" + "platform/pkg/u" +) + +func Init(ctx context.Context) error { + errs := make([]error, 0) + + errs = append(errs, initBaiyin()) + errs = append(errs, initAlipay()) + errs = append(errs, initWechatPay()) + errs = append(errs, initAliyun()) + errs = append(errs, initValidator()) + errs = append(errs, initRedis()) + errs = append(errs, initOrm()) + errs = append(errs, initProxy()) + errs = append(errs, initSft()) + + return u.CombineErrors(errs) +} + +func Stop() error { + var errs = make([]error, 0) + + err := stopRedis() + if err != nil { + errs = append(errs, err) + } + + err = stopOrm() + if err != nil { + errs = append(errs, err) + } + + return u.CombineErrors(errs) } diff --git a/web/globals/orm.go b/web/globals/orm.go index 6ecc2b5..e90c1e0 100644 --- a/web/globals/orm.go +++ b/web/globals/orm.go @@ -1,17 +1,20 @@ package globals import ( + "database/sql" "fmt" + "platform/pkg/env" + "platform/web/queries" + "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/schema" - "log/slog" - "platform/pkg/env" ) var DB *gorm.DB +var Conn *sql.DB -func initOrm() { +func initOrm() error { // 连接数据库 dsn := fmt.Sprintf( @@ -25,27 +28,29 @@ func initOrm() { }, }) if err != nil { - slog.Error("gorm 初始化数据库失败:", slog.Any("err", err)) - panic(err) + return fmt.Errorf("连接数据库失败: %w", err) } // 连接池 conn, err := db.DB() if err != nil { - slog.Error("gorm 初始化数据库失败:", slog.Any("err", err)) - panic(err) + return fmt.Errorf("配置连接池失败: %w", err) } conn.SetMaxIdleConns(10) conn.SetMaxOpenConns(100) + queries.SetDefault(db) DB = db + Conn = conn + + return nil } -func ExitOrm() error { +func stopOrm() error { if DB != nil { conn, err := DB.DB() if err != nil { - return err + return fmt.Errorf("关闭数据库连接失败: %w", err) } return conn.Close() } diff --git a/web/globals/proxy.go b/web/globals/proxy.go index e0edba1..8765aaf 100644 --- a/web/globals/proxy.go +++ b/web/globals/proxy.go @@ -23,8 +23,9 @@ var Proxy *ProxyClient type ProxyClient struct { } -func initProxy() { +func initProxy() error { Proxy = &ProxyClient{} + return nil } type ProxyPermitConfig struct { diff --git a/web/globals/redis.go b/web/globals/redis.go index 35e2488..eacf7e7 100644 --- a/web/globals/redis.go +++ b/web/globals/redis.go @@ -1,12 +1,13 @@ package globals import ( - "github.com/go-redsync/redsync/v4/redis/goredis/v9" "log/slog" "net" "platform/pkg/env" "platform/web/core" + "github.com/go-redsync/redsync/v4/redis/goredis/v9" + "github.com/go-redsync/redsync/v4" "github.com/redis/go-redis/v9" ) @@ -18,11 +19,10 @@ type ExtendRedSync struct { *redsync.Redsync } -func initRedis() { +func initRedis() error { client := redis.NewClient(&redis.Options{ Addr: net.JoinHostPort(env.RedisHost, env.RedisPort), - DB: env.RedisDb, - Password: env.RedisPass, + Password: env.RedisPassword, }) pool := goredis.NewPool(client) @@ -30,9 +30,11 @@ func initRedis() { Redis = client Redsync = &ExtendRedSync{sync} + + return nil } -func ExitRedis() error { +func stopRedis() error { if Redis != nil { return Redis.Close() } diff --git a/web/globals/shangfutong.go b/web/globals/shangfutong.go index 0f71ab2..1ecbb75 100644 --- a/web/globals/shangfutong.go +++ b/web/globals/shangfutong.go @@ -28,9 +28,9 @@ type SftClient struct { publicKey *rsa.PublicKey } -func initSft() { +func initSft() error { if !env.SftPayEnable { - panic("商福通支付未启用,请检查环境变量 SFTPAY_ENABLE") + return fmt.Errorf("商福通支付未启用,请检查环境变量 SFTPAY_ENABLE") } SFTPay = SftClient{ @@ -41,7 +41,7 @@ func initSft() { // 加载私钥 private, err := base64.StdEncoding.DecodeString(env.SftPayAppPrivateKey) if err != nil { - panic("解析商福通私钥失败: " + err.Error()) + return fmt.Errorf("解析商福通私钥失败: %w", err) } var privateKey *rsa.PrivateKey @@ -49,13 +49,13 @@ func initSft() { if err != nil { pkcs8, err := x509.ParsePKCS8PrivateKey(private) if err != nil { - panic("解析商福通私钥失败: " + err.Error()) + return fmt.Errorf("解析商福通私钥失败: %w", err) } var ok bool privateKey, ok = pkcs8.(*rsa.PrivateKey) if !ok { - panic("解析商福通私钥失败") + return fmt.Errorf("解析商福通私钥失败") } } SFTPay.privateKey = privateKey @@ -63,35 +63,36 @@ func initSft() { // 加载公钥 public, err := base64.StdEncoding.DecodeString(env.SftPayPublicKey) if err != nil { - panic("解析商福通公钥失败: " + err.Error()) + return fmt.Errorf("解析商福通公钥失败: %w", err) } var publicKey *rsa.PublicKey pkix, err := x509.ParsePKIXPublicKey(public) if err != nil { - panic("解析商福通公钥失败: " + err.Error()) + return fmt.Errorf("解析商福通公钥失败: %w", err) } var ok bool publicKey, ok = pkix.(*rsa.PublicKey) if !ok { - panic("解析商福通公钥失败") + return fmt.Errorf("解析商福通公钥失败") } SFTPay.publicKey = publicKey + return nil } func (s *SftClient) PaymentScanPay(req *PaymentScanPayReq) (*PaymentScanPayResp, error) { const url = "https://pay.rscygroup.com/api/open/payment/scanpay" - req.ReturnUrl = env.SftReturnUrl - req.NotifyUrl = env.SftNotifyUrl + req.ReturnUrl = u.X(env.SftReturnUrl) + req.NotifyUrl = u.X(env.SftNotifyUrl) req.RouteNo = u.P(s.routeId) return call[PaymentScanPayResp](s, url, req) } func (s *SftClient) PaymentH5Pay(req *PaymentH5PayReq) (*PaymentH5PayResp, error) { const url = "https://pay.rscygroup.com/api/open/payment/h5pay" - req.ReturnUrl = env.SftReturnUrl - req.NotifyUrl = env.SftNotifyUrl + req.ReturnUrl = u.X(env.SftReturnUrl) + req.NotifyUrl = u.X(env.SftNotifyUrl) req.RouteNo = u.P(s.routeId) return call[PaymentH5PayResp](s, url, req) } @@ -256,7 +257,7 @@ func call[T any](s *SftClient, url string, req any) (*T, error) { encode, err := s.sign(req) if err != nil { - return nil, fmt.Errorf("加密请求内容失败:%w", err) + return nil, fmt.Errorf("加密请求内容失败: %w", err) } bytes, err := json.Marshal(encode) @@ -266,33 +267,33 @@ func call[T any](s *SftClient, url string, req any) (*T, error) { request, err := http.NewRequest("POST", url, strings.NewReader(string(bytes))) if err != nil { - return nil, fmt.Errorf("创建请求失败:%w", err) + return nil, fmt.Errorf("创建请求失败: %w", err) } request.Header.Set("Content-Type", "application/json") if env.DebugHttpDump == true { reqDump, err := httputil.DumpRequest(request, true) if err != nil { - return nil, fmt.Errorf("请求内容转储失败:%w", err) + return nil, fmt.Errorf("请求内容转储失败: %w", err) } println(string(reqDump) + "\n\n") } response, err := http.DefaultClient.Do(request) if err != nil { - return nil, fmt.Errorf("请求失败:%w", err) + return nil, fmt.Errorf("请求失败: %w", err) } if env.DebugHttpDump == true { respDump, err := httputil.DumpResponse(response, true) if err != nil { - return nil, fmt.Errorf("响应内容转储失败:%w", err) + return nil, fmt.Errorf("响应内容转储失败: %w", err) } println(string(respDump) + "\n\n") } if response.StatusCode != http.StatusOK { - return nil, fmt.Errorf("请求响应失败:%d", response.StatusCode) + return nil, fmt.Errorf("请求响应失败: %d", response.StatusCode) } defer func(body io.ReadCloser) { _ = body.Close() @@ -300,18 +301,18 @@ func call[T any](s *SftClient, url string, req any) (*T, error) { body, err := io.ReadAll(response.Body) if err != nil { - return nil, fmt.Errorf("读取响应内容失败:%w", err) + return nil, fmt.Errorf("读取响应内容失败: %w", err) } decode, err := s.verify(body) if err != nil { - return nil, fmt.Errorf("解密响应内容失败:%w", err) + return nil, fmt.Errorf("解密响应内容失败: %w", err) } var resp = new(T) err = json.Unmarshal([]byte(decode), resp) if err != nil { - return nil, fmt.Errorf("响应正文解析失败:%w", err) + return nil, fmt.Errorf("响应正文解析失败: %w", err) } return resp, nil @@ -321,7 +322,7 @@ func (s *SftClient) sign(msg any) (*request, error) { bytes, err := json.Marshal(msg) if err != nil { - return nil, fmt.Errorf("格式化加密正文失败:%w", err) + return nil, fmt.Errorf("格式化加密正文失败: %w", err) } if env.DebugHttpDump { @@ -341,7 +342,7 @@ func (s *SftClient) sign(msg any) (*request, error) { hashed := sha256.Sum256([]byte(body.String())) signature, err := rsa.SignPKCS1v15(nil, s.privateKey, crypto.SHA256, hashed[:]) if err != nil { - return nil, fmt.Errorf("签名失败:%w", err) + return nil, fmt.Errorf("签名失败: %w", err) } body.Sign = base64.StdEncoding.EncodeToString(signature) @@ -353,11 +354,11 @@ func (s *SftClient) verify(str []byte) (string, error) { var resp = new(response) err := json.Unmarshal(str, resp) if err != nil { - return "", fmt.Errorf("解析响应正文失败:%w", err) + return "", fmt.Errorf("解析响应正文失败: %w", err) } if resp.Code != "000000" { - return "", fmt.Errorf("请求业务响应失败:%s", u.Z(resp.Msg)) + return "", fmt.Errorf("请求业务响应失败: %s", u.Z(resp.Msg)) } if resp.Sign == nil { @@ -371,13 +372,13 @@ func (s *SftClient) verify(str []byte) (string, error) { ser, err := resp.String() if err != nil { - return "", fmt.Errorf("格式化响应内容失败:%w", err) + return "", fmt.Errorf("格式化响应内容失败: %w", err) } hashed := sha256.Sum256([]byte(ser)) err = rsa.VerifyPKCS1v15(s.publicKey, crypto.SHA256, hashed[:], sign) if err != nil { - return "", fmt.Errorf("验签失败:%w", err) + return "", fmt.Errorf("验签失败: %w", err) } return *resp.BizData, nil @@ -412,7 +413,7 @@ type response struct { func (r response) String() (string, error) { if r.BizData == nil || r.Msg == nil || r.SignType == nil { return "", core.NewServErr(fmt.Sprintf( - "上游数据返回有空值:BizData %v,Msg %v, SignType %v", + "上游数据返回有空值: BizData %v,Msg %v, SignType %v", r.BizData == nil, r.Msg == nil, r.SignType == nil, )) } diff --git a/web/globals/validator.go b/web/globals/validator.go index fbebe98..50d79bc 100644 --- a/web/globals/validator.go +++ b/web/globals/validator.go @@ -2,12 +2,14 @@ package globals import ( "errors" + "fmt" + "strings" + "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" zhtrans "github.com/go-playground/validator/v10/translations/zh" "github.com/gofiber/fiber/v2" - "strings" ) var Validator *ValidatorClient @@ -38,17 +40,18 @@ func (v *ValidatorClient) Validate(c *fiber.Ctx, data any) error { return nil } -func initValidator() { +func initValidator() error { var validate = validator.New(validator.WithRequiredStructEnabled()) var translator = ut.New(zh.New()).GetFallback() err := zhtrans.RegisterDefaultTranslations(validate, translator) if err != nil { - panic(err) + return fmt.Errorf("初始化验证器失败: %w", err) } Validator = &ValidatorClient{ validator: validate, translator: translator, } + return nil } diff --git a/web/globals/wechatpay.go b/web/globals/wechatpay.go index 04f6f55..04dde53 100644 --- a/web/globals/wechatpay.go +++ b/web/globals/wechatpay.go @@ -3,6 +3,7 @@ package globals import ( "context" "encoding/base64" + "fmt" "platform/pkg/env" "github.com/wechatpay-apiv3/wechatpay-go/core" @@ -20,28 +21,28 @@ type WechatPayClient struct { Notify *notify.Handler } -func initWechatPay() { +func initWechatPay() error { // 加载商户私钥 private, err := base64.StdEncoding.DecodeString(env.WechatPayMchPrivateKey) if err != nil { - panic(err) + return fmt.Errorf("加载微信支付商户私钥失败: %w", err) } appPrivateKey, err := utils.LoadPrivateKey(string(private)) if err != nil { - panic(err) + return fmt.Errorf("解析微信支付商户私钥失败: %w", err) } // 加载微信支付公钥 public, err := base64.StdEncoding.DecodeString(env.WechatPayPublicKey) if err != nil { - panic(err) + return fmt.Errorf("加载微信支付公钥失败: %w", err) } wechatPublicKey, err := utils.LoadPublicKey(string(public)) if err != nil { - panic(err) + return fmt.Errorf("解析微信支付公钥失败: %w", err) } // 创建 WechatPay 客户端 @@ -55,7 +56,7 @@ func initWechatPay() { ), ) if err != nil { - panic(err) + return fmt.Errorf("创建微信支付客户端失败: %w", err) } // 创建 WechatPay 通知处理器 @@ -64,7 +65,7 @@ func initWechatPay() { *wechatPublicKey, )) if err != nil { - panic(err) + return fmt.Errorf("创建微信支付通知处理器失败: %w", err) } // 创建 WechatPay 服务 @@ -72,4 +73,5 @@ func initWechatPay() { Native: &native.NativeApiService{Client: client}, Notify: handler, } + return nil } diff --git a/web/handlers/announcement.go b/web/handlers/announcement.go index 1e08fc2..0117cb0 100644 --- a/web/handlers/announcement.go +++ b/web/handlers/announcement.go @@ -1,10 +1,11 @@ package handlers import ( - "github.com/gofiber/fiber/v2" "platform/web/auth" "platform/web/core" q "platform/web/queries" + + "github.com/gofiber/fiber/v2" ) // region ListAnnouncements @@ -16,7 +17,7 @@ type ListAnnouncementsRequest struct { func ListAnnouncements(c *fiber.Ctx) error { // 检查权限 - _, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) + _, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } diff --git a/web/handlers/auth.go b/web/handlers/auth.go index 3ce9970..cb710fb 100644 --- a/web/handlers/auth.go +++ b/web/handlers/auth.go @@ -1,277 +1,14 @@ package handlers import ( - "encoding/base64" - "errors" - "log/slog" "platform/pkg/u" auth2 "platform/web/auth" - client2 "platform/web/domains/client" m "platform/web/models" q "platform/web/queries" - s "platform/web/services" - "strings" - "time" "github.com/gofiber/fiber/v2" - "golang.org/x/crypto/bcrypt" - "gorm.io/gorm" ) -// region /token - -type TokenReq struct { - GrantType auth2.GrantType `json:"grant_type" form:"grant_type"` - ClientID string `json:"client_id" form:"client_id"` - ClientSecret string `json:"client_secret" form:"client_secret"` - Scope string `json:"scope" form:"scope"` - s.GrantCodeData - s.GrantClientData - s.GrantRefreshData - s.GrantPasswordData -} - -type TokenResp struct { - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token,omitempty"` - ExpiresIn int `json:"expires_in"` - TokenType string `json:"token_type"` - Scope string `json:"scope,omitempty"` -} - -type TokenErrResp struct { - Error string `json:"error"` - Description string `json:"error_description,omitempty"` -} - -// Token 处理 OAuth2.0 授权请求 -func Token(c *fiber.Ctx) error { - - // 验证请求参数 - req := new(TokenReq) - if err := c.BodyParser(req); err != nil { - return sendError(c, s.ErrOauthInvalidRequest, "无法解析请求参数") - } - if req.GrantType == "" { - return sendError(c, s.ErrOauthInvalidRequest, "缺少必要参数:grant_type") - } - - slog.Debug("oauth token", slog.String("grant_type", - string(req.GrantType)), - slog.String("client_id", req.ClientID), - ) - - // 基于授权类型处理请求 - switch req.GrantType { - - // 授权码模式 - case auth2.GrantAuthorizationCode: - if req.Code == "" { - return sendError(c, s.ErrOauthInvalidRequest, "缺少必要参数:code") - } - - client, err := protect(c, req.GrantType, req.ClientID, req.ClientSecret) - if err != nil { - return sendError(c, err) - } - - token, err := s.Auth.OauthAuthorizationCode(c.Context(), client, req.Code, req.RedirectURI, req.CodeVerifier) - if err != nil { - return sendError(c, err.(s.AuthServiceError)) - } - - return sendSuccess(c, token) - - // 客户端凭证模式 - case auth2.GrantClientCredentials: - client, err := protect(c, req.GrantType, req.ClientID, req.ClientSecret) - if err != nil { - return sendError(c, err) - } - - scope := strings.Split(req.Scope, ",") - token, err := s.Auth.OauthClientCredentials(c.Context(), client, scope...) - if err != nil { - return sendError(c, err.(s.AuthServiceError)) - } - - return sendSuccess(c, token) - - // 刷新令牌模式 - case auth2.GrantRefreshToken: - if req.RefreshToken == "" { - return sendError(c, s.ErrOauthInvalidRequest, "缺少必要参数:refresh_token") - } - - client, err := protect(c, req.GrantType, req.ClientID, req.ClientSecret) - if err != nil { - return sendError(c, err) - } - - scope := strings.Split(req.Scope, ",") - token, err := s.Auth.OauthRefreshToken(c.Context(), client, req.RefreshToken, scope) - if err != nil { - if errors.Is(err, auth2.ErrInvalidRefreshToken) { - return sendError(c, s.ErrOauthInvalidGrant) - } - return sendError(c, err) - } - - return sendSuccess(c, token) - - // 密码模式 - case auth2.GrantPassword: - if req.LoginType == "" { - return sendError(c, s.ErrOauthInvalidRequest, "缺少必要参数:password_type") - } - if req.Username == "" { - return sendError(c, s.ErrOauthInvalidRequest, "缺少必要参数:username") - } - if req.Password == "" { - return sendError(c, s.ErrOauthInvalidRequest, "缺少必要参数:password") - } - - client, err := protect(c, req.GrantType, req.ClientID, req.ClientSecret) - if err != nil { - return sendError(c, err) - } - - token, err := s.Auth.OauthPassword(c.Context(), client, &req.GrantPasswordData, c.IP(), c.Get("User-Agent")) - if err != nil { - return sendError(c, err) - } - - return sendSuccess(c, token) - - default: - return sendError(c, s.ErrOauthUnsupportedGrantType) - } -} - -// 检查客户端凭证 -func protect(c *fiber.Ctx, grant auth2.GrantType, clientId, clientSecret string) (*m.Client, error) { - header := c.Get("Authorization") - if header != "" { - basic := strings.TrimPrefix(header, "Basic ") - if basic != "" { - base, err := base64.RawURLEncoding.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, s.ErrOauthInvalidRequest - } - client, err := q.Client.Where(q.Client.ClientID.Eq(clientId)).Take() - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, s.ErrOauthInvalidClient - } - return nil, err - } - - // 验证客户端状态 - if client.Status != 1 { - return nil, s.ErrOauthUnauthorizedClient - } - - // 验证授权类型 - switch grant { - case auth2.GrantAuthorizationCode: - if !client.GrantCode { - return nil, s.ErrOauthUnauthorizedClient - } - case auth2.GrantClientCredentials: - if !client.GrantClient || client.Spec != int32(client2.SpecWeb) || client.Spec != int32(client2.SpecTrusted) { - return nil, s.ErrOauthUnauthorizedClient - } - case auth2.GrantRefreshToken: - if !client.GrantRefresh { - return nil, s.ErrOauthUnauthorizedClient - } - case auth2.GrantPassword: - if !client.GrantPassword { - return nil, s.ErrOauthUnauthorizedClient - } - } - - // 如果客户端是 confidential,验证 client_secret,失败返回错误 - if client.Spec == int32(client2.SpecWeb) || client.Spec == int32(client2.SpecTrusted) { - if clientSecret == "" { - return nil, s.ErrOauthInvalidRequest - } - if bcrypt.CompareHashAndPassword([]byte(client.ClientSecret), []byte(clientSecret)) != nil { - return nil, s.ErrOauthInvalidClient - } - } - - // 保存 auth 信息到上下文(以兼容通用 auth 处理逻辑) - auth2.Locals(c, &auth2.Context{ - Payload: auth2.Payload{ - Id: client.ID, - Type: auth2.PayloadSecuredServer, - Name: client.Name, - Avatar: client.Icon, - }, - }) - - return client, nil -} - -// 发送成功响应 -func sendSuccess(c *fiber.Ctx, details *auth2.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 s.AuthServiceError - if errors.As(err, &sErr) { - status := fiber.StatusBadRequest - var desc string - switch { - case errors.Is(sErr, s.ErrOauthInvalidRequest): - desc = "无效的请求" - case errors.Is(sErr, s.ErrOauthInvalidClient): - status = fiber.StatusUnauthorized - desc = "无效的客户端凭证" - case errors.Is(sErr, s.ErrOauthInvalidGrant): - desc = "无效的授权凭证" - case errors.Is(sErr, s.ErrOauthInvalidScope): - desc = "无效的授权范围" - case errors.Is(sErr, s.ErrOauthUnauthorizedClient): - desc = "未授权的客户端" - case errors.Is(sErr, s.ErrOauthUnsupportedGrantType): - desc = "不支持的授权类型" - } - if len(description) > 0 { - desc = description[0] - } - - return c.Status(status).JSON(TokenErrResp{ - Error: string(sErr), - Description: desc, - }) - } - - return err -} - -// endregion - // region /revoke type RevokeReq struct { @@ -280,7 +17,7 @@ type RevokeReq struct { } func Revoke(c *fiber.Ctx) error { - _, err := auth2.Protect(c, []auth2.PayloadType{auth2.PayloadUser}, []string{}) + _, err := auth2.GetAuthCtx(c).PermitUser() if err != nil { // 用户未登录 return nil @@ -312,14 +49,14 @@ type IntrospectResp struct { func Introspect(c *fiber.Ctx) error { // 验证权限 - authCtx, err := auth2.Protect(c, []auth2.PayloadType{auth2.PayloadUser}, []string{}) + authCtx, err := auth2.GetAuthCtx(c).PermitUser() if err != nil { return err } // 获取用户信息 profile, err := q.User. - Where(q.User.ID.Eq(authCtx.Payload.Id)). + Where(q.User.ID.Eq(authCtx.User.ID)). Omit(q.User.DeletedAt). Take() if err != nil { diff --git a/web/handlers/bill.go b/web/handlers/bill.go index 269df6a..016f9c5 100644 --- a/web/handlers/bill.go +++ b/web/handlers/bill.go @@ -23,7 +23,7 @@ type ListBillReq struct { // ListBill 获取账单列表 func ListBill(c *fiber.Ctx) error { // 检查权限 - authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -36,7 +36,7 @@ func ListBill(c *fiber.Ctx) error { // 查询账单列表 do := q.Bill. - Where(q.Bill.UserID.Eq(authContext.Payload.Id)) + Where(q.Bill.UserID.Eq(authCtx.User.ID)) if req.Type != nil { do.Where(q.Bill.Type.Eq(int32(*req.Type))) diff --git a/web/handlers/channel.go b/web/handlers/channel.go index 9dd9986..32d7000 100644 --- a/web/handlers/channel.go +++ b/web/handlers/channel.go @@ -24,7 +24,7 @@ type ListChannelsReq struct { func ListChannels(c *fiber.Ctx) error { // 检查权限 - authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) + authContext, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -37,7 +37,7 @@ func ListChannels(c *fiber.Ctx) error { // 构造查询条件 cond := q.Channel. - Where(q.Channel.UserID.Eq(authContext.Payload.Id)) + Where(q.Channel.UserID.Eq(authContext.User.ID)) switch req.AuthType { case s.ChannelAuthTypeIp: cond.Where(q.Channel.AuthIP.Is(true)) @@ -110,24 +110,19 @@ type CreateChannelRespItem struct { func CreateChannel(c *fiber.Ctx) error { // 检查权限 - authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } // 检查用户其他权限 - user, err := q.User. - Where(q.User.ID.Eq(authContext.Payload.Id)). - Take() - if err != nil { - return err - } + user := authCtx.User if user.IDToken == nil || *user.IDToken == "" { return fiber.NewError(fiber.StatusForbidden, "账号未实名") } count, err := q.Whitelist.Where( - q.Whitelist.UserID.Eq(authContext.Payload.Id), + q.Whitelist.UserID.Eq(user.ID), q.Whitelist.Host.Eq(c.IP()), ).Count() if err != nil { @@ -155,7 +150,7 @@ func CreateChannel(c *fiber.Ctx) error { // 创建通道 result, err := s.Channel.CreateChannel( c, - authContext.Payload.Id, + user.ID, req.ResourceId, req.Protocol, req.AuthType, @@ -198,7 +193,7 @@ type RemoveChannelsReq struct { func RemoveChannels(c *fiber.Ctx) error { // 检查权限 - authCtx, err := auth.NewProtect(c).Payload(auth.PayloadUser).Do() + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -210,31 +205,7 @@ func RemoveChannels(c *fiber.Ctx) error { } // 删除通道 - err = s.Channel.RemoveChannels(req.ByIds, authCtx.Payload.Id) - if err != nil { - return err - } - - return c.SendStatus(fiber.StatusOK) -} - -type RemoveChannelByTaskReq []int32 - -func RemoveChannelByTask(c *fiber.Ctx) error { - // 检查权限 - _, err := auth.NewProtect(c).Payload(auth.PayloadInternalServer).Do() - if err != nil { - return err - } - - // 解析请求参数 - var req RemoveChannelByTaskReq - if err := c.BodyParser(&req); err != nil { - return err - } - - // 删除通道 - err = s.Channel.RemoveChannels(req) + err = s.Channel.RemoveChannels(req.ByIds, authCtx.User.ID) if err != nil { return err } diff --git a/web/handlers/edge.go b/web/handlers/edge.go index 10a0c15..de19e46 100644 --- a/web/handlers/edge.go +++ b/web/handlers/edge.go @@ -2,8 +2,6 @@ package handlers import ( "errors" - "gorm.io/gen/field" - "gorm.io/gorm" "log/slog" "platform/pkg/u" "platform/web/auth" @@ -14,6 +12,9 @@ import ( q "platform/web/queries" s "platform/web/services" + "gorm.io/gen/field" + "gorm.io/gorm" + "github.com/gofiber/fiber/v2" ) @@ -120,7 +121,7 @@ type AllEdgesAvailableRespItem struct { func AllEdgesAvailable(c *fiber.Ctx) (err error) { // 检查权限 - _, err = auth.NewProtect(c).Payload(auth.PayloadInternalServer).Do() + _, err = auth.GetAuthCtx(c).PermitSecretClient() if err != nil { return err } diff --git a/web/handlers/iden.go b/web/handlers/iden.go index 96f6d0f..97c06f2 100644 --- a/web/handlers/iden.go +++ b/web/handlers/iden.go @@ -37,17 +37,11 @@ type IdentifyRes struct { func Identify(c *fiber.Ctx) error { // 检查权限 - authCtx, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) - if err != nil { - return err - } - user, err := q.User. - Where(q.User.ID.Eq(authCtx.Payload.Id)). - Select(q.User.ID, q.User.IDToken). - Take() + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } + user := authCtx.User if user.IDToken != nil && *user.IDToken != "" { // 用户已实名认证 return c.JSON(IdentifyRes{ @@ -86,7 +80,7 @@ func Identify(c *fiber.Ctx) error { // 保存认证中间状态 info := idenInfo{ - Uid: authCtx.Payload.Id, + Uid: user.ID, Type: req.Type, Name: req.Name, IdNo: req.IdenNo, diff --git a/web/handlers/proxy.go b/web/handlers/proxy.go index 7cdfaa2..dd634d2 100644 --- a/web/handlers/proxy.go +++ b/web/handlers/proxy.go @@ -40,9 +40,7 @@ type ProxyReportOnlineResp struct { func ProxyReportOnline(c *fiber.Ctx) (err error) { // 检查接口权限 - _, err = auth2.NewProtect(c).Payload( - auth2.PayloadInternalServer, - ).Do() + _, err = auth2.GetAuthCtx(c).PermitSecretClient() if err != nil { return err } @@ -149,9 +147,7 @@ type ProxyReportOfflineReq struct { func ProxyReportOffline(c *fiber.Ctx) (err error) { // 检查接口权限 - _, err = auth2.NewProtect(c).Payload( - auth2.PayloadInternalServer, - ).Do() + _, err = auth2.GetAuthCtx(c).PermitSecretClient() if err != nil { return err } @@ -193,9 +189,7 @@ type ProxyReportUpdateReq struct { func ProxyReportUpdate(c *fiber.Ctx) (err error) { // 检查接口权限 - _, err = auth2.NewProtect(c).Payload( - auth2.PayloadInternalServer, - ).Do() + _, err = auth2.GetAuthCtx(c).PermitSecretClient() if err != nil { return err } diff --git a/web/handlers/resource.go b/web/handlers/resource.go index 3479da0..387da43 100644 --- a/web/handlers/resource.go +++ b/web/handlers/resource.go @@ -1,7 +1,6 @@ package handlers import ( - "gorm.io/gen/field" "platform/pkg/u" "platform/web/auth" "platform/web/core" @@ -12,6 +11,8 @@ import ( s "platform/web/services" "time" + "gorm.io/gen/field" + "github.com/gofiber/fiber/v2" ) @@ -28,7 +29,7 @@ type ListResourceShortReq struct { func ListResourceShort(c *fiber.Ctx) error { // 检查权限 - authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -41,7 +42,7 @@ func ListResourceShort(c *fiber.Ctx) error { // 查询套餐列表 do := q.Resource.Where( - q.Resource.UserID.Eq(authContext.Payload.Id), + q.Resource.UserID.Eq(authCtx.User.ID), q.Resource.Type.Eq(int32(resource2.TypeShort)), ) if req.ResourceNo != nil && *req.ResourceNo != "" { @@ -109,7 +110,7 @@ type ListResourceLongReq struct { func ListResourceLong(c *fiber.Ctx) error { // 检查权限 - authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -122,7 +123,7 @@ func ListResourceLong(c *fiber.Ctx) error { // 查询套餐列表 do := q.Resource.Where( - q.Resource.UserID.Eq(authContext.Payload.Id), + q.Resource.UserID.Eq(authCtx.User.ID), q.Resource.Type.Eq(int32(resource2.TypeLong)), ) if req.ResourceNo != nil && *req.ResourceNo != "" { @@ -182,7 +183,7 @@ type AllResourceReq struct { func AllActiveResource(c *fiber.Ctx) error { // 检查权限 - authCtx, err := auth.NewProtect(c).Payload(auth.PayloadUser).Do() + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -198,7 +199,7 @@ func AllActiveResource(c *fiber.Ctx) error { q.Resource.Long, ). Where( - q.Resource.UserID.Eq(authCtx.Payload.Id), + q.Resource.UserID.Eq(authCtx.User.ID), q.Resource.Active.Is(true), q.Resource.Where( q.Resource.Type.Eq(int32(resource2.TypeShort)), @@ -254,7 +255,7 @@ type StatisticLong struct { func StatisticResourceFree(c *fiber.Ctx) error { // 检查权限 - session, err := auth.NewProtect(c).Payload(auth.PayloadUser).Do() + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -266,7 +267,7 @@ func StatisticResourceFree(c *fiber.Ctx) error { q.Resource.Long, ). Where( - q.Resource.UserID.Eq(session.Payload.Id), + q.Resource.UserID.Eq(authCtx.User.ID), q.Resource.Active.Is(true), ). Select(q.Resource.ID, q.Resource.Type). @@ -347,7 +348,7 @@ type StatisticResourceUsageResp []struct { func StatisticResourceUsage(c *fiber.Ctx) error { // 检查权限 - session, err := auth.NewProtect(c).Payload(auth.PayloadUser).Do() + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -359,12 +360,12 @@ func StatisticResourceUsage(c *fiber.Ctx) error { } // 统计套餐提取数量 - do := q.LogsUserUsage.Where(q.LogsUserUsage.UserID.Eq(session.Payload.Id)) + do := q.LogsUserUsage.Where(q.LogsUserUsage.UserID.Eq(authCtx.User.ID)) if req.ResourceNo != nil && *req.ResourceNo != "" { var resourceID int32 err := q.Resource. Where( - q.Resource.UserID.Eq(session.Payload.Id), + q.Resource.UserID.Eq(authCtx.User.ID), q.Resource.ResourceNo.Eq(*req.ResourceNo), ). Select(q.Resource.ID). @@ -409,7 +410,7 @@ type CreateResourceReq struct { func CreateResource(c *fiber.Ctx) error { // 检查权限 - authCtx, err := auth.NewProtect(c).Payload(auth.PayloadUser).Do() + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -421,7 +422,7 @@ func CreateResource(c *fiber.Ctx) error { } // 创建套餐 - err = s.Resource.CreateResourceByBalance(authCtx.Payload.Id, time.Now(), req.CreateResourceData) + err = s.Resource.CreateResourceByBalance(authCtx.User.ID, time.Now(), req.CreateResourceData) if err != nil { return err } @@ -431,7 +432,7 @@ func CreateResource(c *fiber.Ctx) error { func ResourcePrice(c *fiber.Ctx) error { // 检查权限 - _, err := auth.NewProtect(c).Payload(auth.PayloadInternalServer).Do() + _, err := auth.GetAuthCtx(c).PermitSecretClient() if err != nil { return err } diff --git a/web/handlers/trade.go b/web/handlers/trade.go index ae95879..be87b7c 100644 --- a/web/handlers/trade.go +++ b/web/handlers/trade.go @@ -7,7 +7,6 @@ import ( trade2 "platform/web/domains/trade" g "platform/web/globals" s "platform/web/services" - "platform/web/tasks" "time" "github.com/gofiber/fiber/v2" @@ -27,7 +26,7 @@ type TradeCreateResp struct { func TradeCreate(c *fiber.Ctx) error { // 检查权限 - authCtx, err := auth.NewProtect(c).Payload(auth.PayloadUser).Do() + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -52,7 +51,7 @@ func TradeCreate(c *fiber.Ctx) error { } // 创建交易 - result, err := s.Trade.CreateTrade(authCtx.Payload.Id, time.Now(), &req.CreateTradeData) + result, err := s.Trade.CreateTrade(authCtx.User.ID, time.Now(), &req.CreateTradeData) if err != nil { slog.Error("创建交易失败", "error", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "创建交易失败"}) @@ -70,7 +69,7 @@ type TradeCompleteReq struct { func TradeComplete(c *fiber.Ctx) error { // 检查权限 - _, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) + _, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -99,7 +98,7 @@ type TradeCancelReq struct { func TradeCancel(c *fiber.Ctx) error { // 检查权限 - _, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) + _, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -119,29 +118,3 @@ func TradeCancel(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusNoContent) } - -type TradeCheckReq struct { - tasks.CancelTradeData -} - -func TradeCancelByTask(c *fiber.Ctx) error { - // 检查权限 - _, err := auth.Protect(c, []auth.PayloadType{auth.PayloadInternalServer}, []string{}) - if err != nil { - return err - } - - // 取消交易 - req := new(TradeCheckReq) - if err := c.BodyParser(req); err != nil { - return err - } - - // 检查订单状态 - err = s.Trade.CancelTrade(req.TradeNo, req.Method, time.Now()) - if err != nil { - return err - } - - return nil -} diff --git a/web/handlers/user.go b/web/handlers/user.go index 3b038e6..2577f47 100644 --- a/web/handlers/user.go +++ b/web/handlers/user.go @@ -1,12 +1,13 @@ package handlers import ( - "github.com/gofiber/fiber/v2" - "golang.org/x/crypto/bcrypt" "platform/web/auth" m "platform/web/models" q "platform/web/queries" s "platform/web/services" + + "github.com/gofiber/fiber/v2" + "golang.org/x/crypto/bcrypt" ) // region /update @@ -20,7 +21,7 @@ type UpdateUserReq struct { func UpdateUser(c *fiber.Ctx) error { // 检查权限 - authCtx, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -33,7 +34,7 @@ func UpdateUser(c *fiber.Ctx) error { // 更新用户信息 _, err = q.User. - Where(q.User.ID.Eq(authCtx.Payload.Id)). + Where(q.User.ID.Eq(authCtx.User.ID)). Updates(m.User{ Username: &req.Username, Email: &req.Email, @@ -59,7 +60,7 @@ type UpdateAccountReq struct { func UpdateAccount(c *fiber.Ctx) error { // 检查权限 - authCtx, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -72,7 +73,7 @@ func UpdateAccount(c *fiber.Ctx) error { // 更新用户信息 _, err = q.User. - Where(q.User.ID.Eq(authCtx.Payload.Id)). + Where(q.User.ID.Eq(authCtx.User.ID)). Updates(m.User{ Username: &req.Username, Password: &req.Password, @@ -97,7 +98,7 @@ type UpdatePasswordReq struct { func UpdatePassword(c *fiber.Ctx) error { // 检查权限 - authCtx, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -124,7 +125,7 @@ func UpdatePassword(c *fiber.Ctx) error { } _, err = q.User. - Where(q.User.ID.Eq(authCtx.Payload.Id)). + Where(q.User.ID.Eq(authCtx.User.ID)). UpdateColumn(q.User.Password, newHash) if err != nil { return err diff --git a/web/handlers/verifier.go b/web/handlers/verifier.go index 19639c7..e1465ce 100644 --- a/web/handlers/verifier.go +++ b/web/handlers/verifier.go @@ -2,12 +2,14 @@ package handlers import ( "errors" + "platform/pkg/env" "platform/web/auth" "platform/web/services" "regexp" "strconv" "github.com/gofiber/fiber/v2" + "github.com/redis/go-redis/v9" ) type VerifierReq struct { @@ -17,7 +19,7 @@ type VerifierReq struct { func SmsCode(c *fiber.Ctx) error { - _, err := auth.Protect(c, []auth.PayloadType{auth.PayloadInternalServer}, []string{}) + _, err := auth.GetAuthCtx(c).PermitInternalClient() if err != nil { return err } @@ -48,3 +50,19 @@ func SmsCode(c *fiber.Ctx) error { // 发送成功 return nil } + +func DebugGetSmsCode(c *fiber.Ctx) error { + if env.RunMode != env.RunModeDev { + return fiber.NewError(fiber.StatusForbidden, "not allowed") + } + + code, err := services.Verifier.GetSms(c.Context(), c.Params("phone")) + if err != nil { + if errors.Is(err, redis.Nil) { + return c.SendString("还没有验证码") + } + return err + } + + return c.SendString(code) +} diff --git a/web/handlers/whitelist.go b/web/handlers/whitelist.go index f3ea4e9..265e930 100644 --- a/web/handlers/whitelist.go +++ b/web/handlers/whitelist.go @@ -26,7 +26,7 @@ type ListWhitelistResp struct { func ListWhitelist(c *fiber.Ctx) error { // 检查权限 - authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -38,8 +38,7 @@ func ListWhitelist(c *fiber.Ctx) error { } // 获取白名单信息 - do := q.Whitelist. - Where(q.Whitelist.UserID.Eq(authContext.Payload.Id)) + do := q.Whitelist.Where(q.Whitelist.UserID.Eq(authCtx.User.ID)) list, err := q.Whitelist.Where(do). Offset(req.GetOffset()). @@ -77,7 +76,7 @@ type CreateWhitelistReq struct { func CreateWhitelist(c *fiber.Ctx) error { // 检查权限 - authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -96,7 +95,7 @@ func CreateWhitelist(c *fiber.Ctx) error { // 创建白名单 err = q.Whitelist.Create(&m.Whitelist{ - UserID: authContext.Payload.Id, + UserID: authCtx.User.ID, Host: req.Host, Remark: &req.Remark, }) @@ -111,7 +110,7 @@ type UpdateWhitelistReq struct { func UpdateWhitelist(c *fiber.Ctx) error { // 检查权限 - authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -129,7 +128,7 @@ func UpdateWhitelist(c *fiber.Ctx) error { _, err = q.Whitelist. Where( q.Whitelist.ID.Eq(req.ID), - q.Whitelist.UserID.Eq(authContext.Payload.Id), + q.Whitelist.UserID.Eq(authCtx.User.ID), ). Updates(&m.Whitelist{ ID: req.ID, @@ -149,7 +148,7 @@ type RemoveWhitelistReq struct { func RemoveWhitelist(c *fiber.Ctx) error { // 检查权限 - authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) + authCtx, err := auth.GetAuthCtx(c).PermitUser() if err != nil { return err } @@ -175,7 +174,7 @@ func RemoveWhitelist(c *fiber.Ctx) error { _, err = q.Whitelist. Where( q.Whitelist.ID.In(ids...), - q.Whitelist.UserID.Eq(authContext.Payload.Id), + q.Whitelist.UserID.Eq(authCtx.User.ID), ). Update( q.Whitelist.DeletedAt, time.Now(), diff --git a/web/middlewares.go b/web/middlewares.go new file mode 100644 index 0000000..84c5d30 --- /dev/null +++ b/web/middlewares.go @@ -0,0 +1,42 @@ +package web + +import ( + "platform/web/auth" + + "github.com/gofiber/contrib/otelfiber/v2" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/fiber/v2/middleware/requestid" + "github.com/google/uuid" + "github.com/jxskiss/base62" +) + +func ApplyMiddlewares(app *fiber.App) { + + // recover + app.Use(recover.New(recover.Config{ + EnableStackTrace: true, + })) + + // metric + app.Use(otelfiber.Middleware()) + + // logger + app.Use(logger.New(logger.Config{ + Next: func(c *fiber.Ctx) bool { + return c.Path() == "/favicon.ico" + }, + })) + + // request id + app.Use(requestid.New(requestid.Config{ + Generator: func() string { + binary, _ := uuid.New().MarshalBinary() + return base62.EncodeToString(binary) + }, + })) + + // authenticate + app.Use(auth.Authenticate()) +} diff --git a/web/models/channel.gen.go b/web/models/channel.gen.go index 8c66ac9..24211bc 100644 --- a/web/models/channel.gen.go +++ b/web/models/channel.gen.go @@ -17,10 +17,14 @@ type Channel struct { ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:通道ID" json:"id"` // 通道ID UserID int32 `gorm:"column:user_id;type:integer;not null;comment:用户ID" json:"user_id"` // 用户ID ProxyID int32 `gorm:"column:proxy_id;type:integer;not null;comment:代理ID" json:"proxy_id"` // 代理ID + EdgeID *int32 `gorm:"column:edge_id;type:integer;comment:节点ID" json:"edge_id"` // 节点ID + ResourceID int32 `gorm:"column:resource_id;type:integer;not null;comment:套餐ID" json:"resource_id"` // 套餐ID ProxyHost string `gorm:"column:proxy_host;type:character varying(255);not null;comment:代理地址" json:"proxy_host"` // 代理地址 ProxyPort int32 `gorm:"column:proxy_port;type:integer;not null;comment:转发端口" json:"proxy_port"` // 转发端口 + EdgeHost *string `gorm:"column:edge_host;type:character varying(255);comment:节点地址" json:"edge_host"` // 节点地址 Protocol *int32 `gorm:"column:protocol;type:integer;comment:协议类型:1-http,2-https,3-socks5" json:"protocol"` // 协议类型:1-http,2-https,3-socks5 AuthIP bool `gorm:"column:auth_ip;type:boolean;not null;comment:IP认证" json:"auth_ip"` // IP认证 + Whitelists *string `gorm:"column:whitelists;type:text;comment:IP白名单,逗号分隔" json:"whitelists"` // IP白名单,逗号分隔 AuthPass bool `gorm:"column:auth_pass;type:boolean;not null;comment:密码认证" json:"auth_pass"` // 密码认证 Username *string `gorm:"column:username;type:character varying(255);comment:用户名" json:"username"` // 用户名 Password *string `gorm:"column:password;type:character varying(255);comment:密码" json:"password"` // 密码 @@ -28,10 +32,6 @@ type Channel struct { CreatedAt *orm.LocalDateTime `gorm:"column:created_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 UpdatedAt *orm.LocalDateTime `gorm:"column:updated_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp without time zone;comment:删除时间" json:"deleted_at"` // 删除时间 - EdgeHost *string `gorm:"column:edge_host;type:character varying(255);comment:节点地址" json:"edge_host"` // 节点地址 - EdgeID *int32 `gorm:"column:edge_id;type:integer;comment:节点ID" json:"edge_id"` // 节点ID - Whitelists *string `gorm:"column:whitelists;type:text;comment:IP白名单,逗号分隔" json:"whitelists"` // IP白名单,逗号分隔 - ResourceID int32 `gorm:"column:resource_id;type:integer;not null;comment:套餐ID" json:"resource_id"` // 套餐ID } // TableName Channel's table name diff --git a/web/models/client.gen.go b/web/models/client.gen.go index d386e40..1039e2b 100644 --- a/web/models/client.gen.go +++ b/web/models/client.gen.go @@ -14,21 +14,18 @@ const TableNameClient = "client" // Client mapped from table type Client struct { - ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:客户端ID" json:"id"` // 客户端ID - ClientID string `gorm:"column:client_id;type:character varying(255);not null;comment:OAuth2客户端标识符" json:"client_id"` // OAuth2客户端标识符 - ClientSecret string `gorm:"column:client_secret;type:character varying(255);not null;comment:OAuth2客户端密钥" json:"client_secret"` // OAuth2客户端密钥 - RedirectURI *string `gorm:"column:redirect_uri;type:character varying(255);comment:OAuth2 重定向URI" json:"redirect_uri"` // OAuth2 重定向URI - GrantCode bool `gorm:"column:grant_code;type:boolean;not null;comment:允许授权码授予" json:"grant_code"` // 允许授权码授予 - GrantClient bool `gorm:"column:grant_client;type:boolean;not null;comment:允许客户端凭证授予" json:"grant_client"` // 允许客户端凭证授予 - GrantRefresh bool `gorm:"column:grant_refresh;type:boolean;not null;comment:允许刷新令牌授予" json:"grant_refresh"` // 允许刷新令牌授予 - GrantPassword bool `gorm:"column:grant_password;type:boolean;not null;comment:允许密码授予" json:"grant_password"` // 允许密码授予 - Spec int32 `gorm:"column:spec;type:integer;not null;comment:安全规范:1-native,2-browser,3-web,4-trusted" json:"spec"` // 安全规范:1-native,2-browser,3-web,4-trusted - Name string `gorm:"column:name;type:character varying(255);not null;comment:名称" json:"name"` // 名称 - Icon *string `gorm:"column:icon;type:character varying(255);comment:图标URL" json:"icon"` // 图标URL - Status int32 `gorm:"column:status;type:integer;not null;default:1;comment:状态:0-禁用,1-正常" json:"status"` // 状态:0-禁用,1-正常 - CreatedAt *orm.LocalDateTime `gorm:"column:created_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 - UpdatedAt *orm.LocalDateTime `gorm:"column:updated_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 - DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp without time zone;comment:删除时间" json:"deleted_at"` // 删除时间 + ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:客户端ID" json:"id"` // 客户端ID + ClientID string `gorm:"column:client_id;type:character varying(255);not null;comment:OAuth2客户端标识符" json:"client_id"` // OAuth2客户端标识符 + ClientSecret string `gorm:"column:client_secret;type:character varying(255);not null;comment:OAuth2客户端密钥" json:"client_secret"` // OAuth2客户端密钥 + RedirectURI *string `gorm:"column:redirect_uri;type:character varying(255);comment:OAuth2 重定向URI" json:"redirect_uri"` // OAuth2 重定向URI + Spec int32 `gorm:"column:spec;type:integer;not null;comment:安全规范:1-native,2-browser,3-web,4-api" json:"spec"` // 安全规范:1-native,2-browser,3-web,4-api + Name string `gorm:"column:name;type:character varying(255);not null;comment:名称" json:"name"` // 名称 + Icon *string `gorm:"column:icon;type:character varying(255);comment:图标URL" json:"icon"` // 图标URL + Status int32 `gorm:"column:status;type:integer;not null;default:1;comment:状态:0-禁用,1-正常" json:"status"` // 状态:0-禁用,1-正常 + Type int32 `gorm:"column:type;type:integer;not null;comment:类型:0-普通,1-官方" json:"type"` // 类型:0-普通,1-官方 + CreatedAt *orm.LocalDateTime `gorm:"column:created_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 + UpdatedAt *orm.LocalDateTime `gorm:"column:updated_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp without time zone;comment:删除时间" json:"deleted_at"` // 删除时间 } // TableName Client's table name diff --git a/web/models/logs_login.gen.go b/web/models/logs_login.gen.go index 9711081..b2111b9 100644 --- a/web/models/logs_login.gen.go +++ b/web/models/logs_login.gen.go @@ -12,12 +12,12 @@ const TableNameLogsLogin = "logs_login" type LogsLogin struct { ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:登录日志ID" json:"id"` // 登录日志ID IP string `gorm:"column:ip;type:character varying(45);not null;comment:IP地址" json:"ip"` // IP地址 - Ua string `gorm:"column:ua;type:character varying(255);not null;comment:用户代理" json:"ua"` // 用户代理 + UA string `gorm:"column:ua;type:character varying(255);not null;comment:用户代理" json:"ua"` // 用户代理 GrantType string `gorm:"column:grant_type;type:character varying(255);not null;comment:授权类型:authorization_code-授权码模式,client_credentials-客户端凭证模式,refresh_token-刷新令牌模式,password-密码模式" json:"grant_type"` // 授权类型:authorization_code-授权码模式,client_credentials-客户端凭证模式,refresh_token-刷新令牌模式,password-密码模式 PasswordGrantType string `gorm:"column:password_grant_type;type:character varying(255);not null;comment:密码模式子授权类型:password-账号密码,phone_code-手机验证码,email_code-邮箱验证码" json:"password_grant_type"` // 密码模式子授权类型:password-账号密码,phone_code-手机验证码,email_code-邮箱验证码 Success bool `gorm:"column:success;type:boolean;not null;comment:登录是否成功" json:"success"` // 登录是否成功 - Time orm.LocalDateTime `gorm:"column:time;type:timestamp without time zone;not null;comment:登录时间" json:"time"` // 登录时间 UserID *int32 `gorm:"column:user_id;type:integer;comment:用户ID" json:"user_id"` // 用户ID + Time orm.LocalDateTime `gorm:"column:time;type:timestamp without time zone;not null;comment:登录时间" json:"time"` // 登录时间 } // TableName LogsLogin's table name diff --git a/web/models/logs_request.gen.go b/web/models/logs_request.gen.go index 73add45..0c828ca 100644 --- a/web/models/logs_request.gen.go +++ b/web/models/logs_request.gen.go @@ -10,17 +10,17 @@ const TableNameLogsRequest = "logs_request" // LogsRequest mapped from table type LogsRequest struct { - ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:访问日志ID" json:"id"` // 访问日志ID - Identity int32 `gorm:"column:identity;type:integer;not null;comment:访客身份:0-游客,1-用户,2-管理员,3-公共服务,4-安全服务,5-内部服务" json:"identity"` // 访客身份:0-游客,1-用户,2-管理员,3-公共服务,4-安全服务,5-内部服务 - Visitor *int32 `gorm:"column:visitor;type:integer;comment:访客ID" json:"visitor"` // 访客ID - IP string `gorm:"column:ip;type:character varying(45);not null;comment:IP地址" json:"ip"` // IP地址 - Ua *string `gorm:"column:ua;type:character varying(255);comment:用户代理" json:"ua"` // 用户代理 - Method string `gorm:"column:method;type:character varying(10);not null;comment:请求方法" json:"method"` // 请求方法 - Path string `gorm:"column:path;type:character varying(255);not null;comment:请求路径" json:"path"` // 请求路径 - Latency string `gorm:"column:latency;type:character varying(255);not null;comment:请求延迟" json:"latency"` // 请求延迟 - Status int32 `gorm:"column:status;type:integer;not null;comment:响应状态码" json:"status"` // 响应状态码 - Error *string `gorm:"column:error;type:text;comment:错误信息" json:"error"` // 错误信息 - Time orm.LocalDateTime `gorm:"column:time;type:timestamp without time zone;not null;comment:请求时间" json:"time"` // 请求时间 + ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:访问日志ID" json:"id"` // 访问日志ID + IP string `gorm:"column:ip;type:character varying(45);not null;comment:IP地址" json:"ip"` // IP地址 + UA string `gorm:"column:ua;type:character varying(255);not null;comment:用户代理" json:"ua"` // 用户代理 + UserID *int32 `gorm:"column:user_id;type:integer;comment:用户ID" json:"user_id"` // 用户ID + ClientID *int32 `gorm:"column:client_id;type:integer;comment:客户端ID" json:"client_id"` // 客户端ID + Method string `gorm:"column:method;type:character varying(10);not null;comment:请求方法" json:"method"` // 请求方法 + Path string `gorm:"column:path;type:character varying(255);not null;comment:请求路径" json:"path"` // 请求路径 + Status int32 `gorm:"column:status;type:integer;not null;comment:响应状态码" json:"status"` // 响应状态码 + Error *string `gorm:"column:error;type:text;comment:错误信息" json:"error"` // 错误信息 + Time orm.LocalDateTime `gorm:"column:time;type:timestamp without time zone;not null;comment:请求时间" json:"time"` // 请求时间 + Latency string `gorm:"column:latency;type:character varying(255);not null;comment:请求延迟" json:"latency"` // 请求延迟 } // TableName LogsRequest's table name diff --git a/web/models/proxy.gen.go b/web/models/proxy.gen.go index d4e3783..2a78abe 100644 --- a/web/models/proxy.gen.go +++ b/web/models/proxy.gen.go @@ -18,12 +18,12 @@ type Proxy struct { Version int32 `gorm:"column:version;type:integer;not null;comment:代理服务版本" json:"version"` // 代理服务版本 Name string `gorm:"column:name;type:character varying(255);not null;comment:代理服务名称" json:"name"` // 代理服务名称 Host string `gorm:"column:host;type:character varying(255);not null;comment:代理服务地址" json:"host"` // 代理服务地址 - Type int32 `gorm:"column:type;type:integer;not null;comment:代理服务类型:1-三方,2-自有" json:"type"` // 代理服务类型:1-三方,2-自有 Secret *string `gorm:"column:secret;type:character varying(255);comment:代理服务密钥" json:"secret"` // 代理服务密钥 + Type int32 `gorm:"column:type;type:integer;not null;comment:代理服务类型:1-三方,2-自有" json:"type"` // 代理服务类型:1-三方,2-自有 + Status int32 `gorm:"column:status;type:integer;not null;comment:代理服务状态:0-离线,1-在线" json:"status"` // 代理服务状态:0-离线,1-在线 CreatedAt *orm.LocalDateTime `gorm:"column:created_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 UpdatedAt *orm.LocalDateTime `gorm:"column:updated_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp without time zone;comment:删除时间" json:"deleted_at"` // 删除时间 - Status int32 `gorm:"column:status;type:integer;not null;comment:代理服务状态:0-离线,1-在线" json:"status"` // 代理服务状态:0-离线,1-在线 Edges []Edge `gorm:"foreignKey:ProxyID;references:ID" json:"edges"` } diff --git a/web/models/session.gen.go b/web/models/session.gen.go index 8fa1d05..f4b2fe5 100644 --- a/web/models/session.gen.go +++ b/web/models/session.gen.go @@ -14,20 +14,23 @@ const TableNameSession = "session" // Session mapped from table type Session struct { - ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:会话ID" json:"id"` // 会话ID - UserID *int32 `gorm:"column:user_id;type:integer;comment:用户ID" json:"user_id"` // 用户ID - ClientID *int32 `gorm:"column:client_id;type:integer;comment:客户端ID" json:"client_id"` // 客户端ID - IP *string `gorm:"column:ip;type:character varying(45);comment:IP地址" json:"ip"` // IP地址 - Ua *string `gorm:"column:ua;type:character varying(255);comment:用户代理" json:"ua"` // 用户代理 - GrantType string `gorm:"column:grant_type;type:character varying(255);not null;default:0;comment:授权类型:authorization_code-授权码模式,client_credentials-客户端凭证模式,refresh_token-刷新令牌模式,password-密码模式" json:"grant_type"` // 授权类型:authorization_code-授权码模式,client_credentials-客户端凭证模式,refresh_token-刷新令牌模式,password-密码模式 - AccessToken string `gorm:"column:access_token;type:character varying(255);not null;comment:访问令牌" json:"access_token"` // 访问令牌 - AccessTokenExpires orm.LocalDateTime `gorm:"column:access_token_expires;type:timestamp without time zone;not null;comment:访问令牌过期时间" json:"access_token_expires"` // 访问令牌过期时间 - RefreshToken *string `gorm:"column:refresh_token;type:character varying(255);comment:刷新令牌" json:"refresh_token"` // 刷新令牌 - RefreshTokenExpires *orm.LocalDateTime `gorm:"column:refresh_token_expires;type:timestamp without time zone;comment:刷新令牌过期时间" json:"refresh_token_expires"` // 刷新令牌过期时间 - Scopes_ *string `gorm:"column:scopes;type:character varying(255);comment:权限范围" json:"scopes"` // 权限范围 - CreatedAt *orm.LocalDateTime `gorm:"column:created_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 - UpdatedAt *orm.LocalDateTime `gorm:"column:updated_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 - DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp without time zone;comment:删除时间" json:"deleted_at"` // 删除时间 + ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:会话ID" json:"id"` // 会话ID + UserID *int32 `gorm:"column:user_id;type:integer;comment:用户ID" json:"user_id"` // 用户ID + AdminID *int32 `gorm:"column:admin_id;type:integer;comment:管理员ID" json:"admin_id"` // 管理员ID + ClientID *int32 `gorm:"column:client_id;type:integer;comment:客户端ID" json:"client_id"` // 客户端ID + IP *string `gorm:"column:ip;type:character varying(45);comment:IP地址" json:"ip"` // IP地址 + UA *string `gorm:"column:ua;type:character varying(255);comment:用户代理" json:"ua"` // 用户代理 + AccessToken string `gorm:"column:access_token;type:character varying(255);not null;comment:访问令牌" json:"access_token"` // 访问令牌 + AccessTokenExpires orm.LocalDateTime `gorm:"column:access_token_expires;type:timestamp without time zone;not null;comment:访问令牌过期时间" json:"access_token_expires"` // 访问令牌过期时间 + RefreshToken *string `gorm:"column:refresh_token;type:character varying(255);comment:刷新令牌" json:"refresh_token"` // 刷新令牌 + RefreshTokenExpires *orm.LocalDateTime `gorm:"column:refresh_token_expires;type:timestamp without time zone;comment:刷新令牌过期时间" json:"refresh_token_expires"` // 刷新令牌过期时间 + Scopes_ *string `gorm:"column:scopes;type:character varying(255);comment:权限范围" json:"scopes"` // 权限范围 + CreatedAt *orm.LocalDateTime `gorm:"column:created_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 + UpdatedAt *orm.LocalDateTime `gorm:"column:updated_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp without time zone;comment:删除时间" json:"deleted_at"` // 删除时间 + User *User `gorm:"foreignKey:UserID" json:"user"` + Admin *Admin `gorm:"foreignKey:UserID" json:"admin"` + Client *Client `gorm:"belongsTo:ID;foreignKey:ClientID" json:"client"` } // TableName Session's table name diff --git a/web/models/trade.gen.go b/web/models/trade.gen.go index 38d9a81..d5b0f76 100644 --- a/web/models/trade.gen.go +++ b/web/models/trade.gen.go @@ -15,26 +15,26 @@ const TableNameTrade = "trade" // Trade mapped from table type Trade struct { - ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:订单ID" json:"id"` // 订单ID - UserID int32 `gorm:"column:user_id;type:integer;not null;comment:用户ID" json:"user_id"` // 用户ID - InnerNo string `gorm:"column:inner_no;type:character varying(255);not null;comment:内部订单号" json:"inner_no"` // 内部订单号 - OuterNo *string `gorm:"column:outer_no;type:character varying(255);comment:外部订单号" json:"outer_no"` // 外部订单号 - Type int32 `gorm:"column:type;type:integer;not null;comment:订单类型:1-购买产品,2-充值余额" json:"type"` // 订单类型:1-购买产品,2-充值余额 - Subject string `gorm:"column:subject;type:character varying(255);not null;comment:订单主题" json:"subject"` // 订单主题 - Remark *string `gorm:"column:remark;type:character varying(255);comment:订单备注" json:"remark"` // 订单备注 - Amount decimal.Decimal `gorm:"column:amount;type:numeric(12,2);not null;comment:订单总金额" json:"amount"` // 订单总金额 - Payment decimal.Decimal `gorm:"column:payment;type:numeric(12,2);not null;comment:支付金额" json:"payment"` // 支付金额 - Method int32 `gorm:"column:method;type:integer;not null;comment:支付方式:1-支付宝,2-微信,3-商福通渠道支付宝,4-商福通渠道微信" json:"method"` // 支付方式:1-支付宝,2-微信,3-商福通渠道支付宝,4-商福通渠道微信 - Status int32 `gorm:"column:status;type:integer;not null;comment:订单状态:0-待支付,1-已支付,2-已取消" json:"status"` // 订单状态:0-待支付,1-已支付,2-已取消 - CreatedAt *orm.LocalDateTime `gorm:"column:created_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 - UpdatedAt *orm.LocalDateTime `gorm:"column:updated_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 - DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp without time zone;comment:删除时间" json:"deleted_at"` // 删除时间 - Acquirer *int32 `gorm:"column:acquirer;type:integer;comment:收单机构:1-支付宝,2-微信,3-银联" json:"acquirer"` // 收单机构:1-支付宝,2-微信,3-银联 - Platform int32 `gorm:"column:platform;type:integer;not null;comment:支付平台:1-电脑网站,2-手机网站" json:"platform"` // 支付平台:1-电脑网站,2-手机网站 + ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:订单ID" json:"id"` // 订单ID + UserID int32 `gorm:"column:user_id;type:integer;not null;comment:用户ID" json:"user_id"` // 用户ID + InnerNo string `gorm:"column:inner_no;type:character varying(255);not null;comment:内部订单号" json:"inner_no"` // 内部订单号 + OuterNo *string `gorm:"column:outer_no;type:character varying(255);comment:外部订单号" json:"outer_no"` // 外部订单号 + Type int32 `gorm:"column:type;type:integer;not null;comment:订单类型:1-购买产品,2-充值余额" json:"type"` // 订单类型:1-购买产品,2-充值余额 + Subject string `gorm:"column:subject;type:character varying(255);not null;comment:订单主题" json:"subject"` // 订单主题 + Remark *string `gorm:"column:remark;type:character varying(255);comment:订单备注" json:"remark"` // 订单备注 + Amount decimal.Decimal `gorm:"column:amount;type:numeric(12,2);not null;comment:订单总金额" json:"amount"` // 订单总金额 + Payment decimal.Decimal `gorm:"column:payment;type:numeric(12,2);not null;comment:实际支付金额" json:"payment"` // 实际支付金额 + Method int32 `gorm:"column:method;type:integer;not null;comment:支付方式:1-支付宝,2-微信,3-商福通,4-商福通渠道支付宝,5-商福通渠道微信" json:"method"` // 支付方式:1-支付宝,2-微信,3-商福通,4-商福通渠道支付宝,5-商福通渠道微信 + Platform int32 `gorm:"column:platform;type:integer;not null;comment:支付平台:1-电脑网站,2-手机网站" json:"platform"` // 支付平台:1-电脑网站,2-手机网站 + Acquirer *int32 `gorm:"column:acquirer;type:integer;comment:收单机构:1-支付宝,2-微信,3-银联" json:"acquirer"` // 收单机构:1-支付宝,2-微信,3-银联 + Status int32 `gorm:"column:status;type:integer;not null;comment:订单状态:0-待支付,1-已支付,2-已取消" json:"status"` // 订单状态:0-待支付,1-已支付,2-已取消 + Refunded bool `gorm:"column:refunded;type:boolean;not null" json:"refunded"` PaymentURL *string `gorm:"column:payment_url;type:text;comment:支付链接" json:"payment_url"` // 支付链接 CompletedAt *orm.LocalDateTime `gorm:"column:completed_at;type:timestamp without time zone;comment:支付时间" json:"completed_at"` // 支付时间 CanceledAt *orm.LocalDateTime `gorm:"column:canceled_at;type:timestamp without time zone;comment:取消时间" json:"canceled_at"` // 取消时间 - Refunded bool `gorm:"column:refunded;type:boolean;not null" json:"refunded"` + CreatedAt *orm.LocalDateTime `gorm:"column:created_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 + UpdatedAt *orm.LocalDateTime `gorm:"column:updated_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp without time zone;comment:删除时间" json:"deleted_at"` // 删除时间 } // TableName Trade's table name diff --git a/web/queries/channel.gen.go b/web/queries/channel.gen.go index e860d12..fdb750c 100644 --- a/web/queries/channel.gen.go +++ b/web/queries/channel.gen.go @@ -30,10 +30,14 @@ func newChannel(db *gorm.DB, opts ...gen.DOOption) channel { _channel.ID = field.NewInt32(tableName, "id") _channel.UserID = field.NewInt32(tableName, "user_id") _channel.ProxyID = field.NewInt32(tableName, "proxy_id") + _channel.EdgeID = field.NewInt32(tableName, "edge_id") + _channel.ResourceID = field.NewInt32(tableName, "resource_id") _channel.ProxyHost = field.NewString(tableName, "proxy_host") _channel.ProxyPort = field.NewInt32(tableName, "proxy_port") + _channel.EdgeHost = field.NewString(tableName, "edge_host") _channel.Protocol = field.NewInt32(tableName, "protocol") _channel.AuthIP = field.NewBool(tableName, "auth_ip") + _channel.Whitelists = field.NewString(tableName, "whitelists") _channel.AuthPass = field.NewBool(tableName, "auth_pass") _channel.Username = field.NewString(tableName, "username") _channel.Password = field.NewString(tableName, "password") @@ -41,10 +45,6 @@ func newChannel(db *gorm.DB, opts ...gen.DOOption) channel { _channel.CreatedAt = field.NewField(tableName, "created_at") _channel.UpdatedAt = field.NewField(tableName, "updated_at") _channel.DeletedAt = field.NewField(tableName, "deleted_at") - _channel.EdgeHost = field.NewString(tableName, "edge_host") - _channel.EdgeID = field.NewInt32(tableName, "edge_id") - _channel.Whitelists = field.NewString(tableName, "whitelists") - _channel.ResourceID = field.NewInt32(tableName, "resource_id") _channel.fillFieldMap() @@ -58,10 +58,14 @@ type channel struct { ID field.Int32 // 通道ID UserID field.Int32 // 用户ID ProxyID field.Int32 // 代理ID + EdgeID field.Int32 // 节点ID + ResourceID field.Int32 // 套餐ID ProxyHost field.String // 代理地址 ProxyPort field.Int32 // 转发端口 + EdgeHost field.String // 节点地址 Protocol field.Int32 // 协议类型:1-http,2-https,3-socks5 AuthIP field.Bool // IP认证 + Whitelists field.String // IP白名单,逗号分隔 AuthPass field.Bool // 密码认证 Username field.String // 用户名 Password field.String // 密码 @@ -69,10 +73,6 @@ type channel struct { CreatedAt field.Field // 创建时间 UpdatedAt field.Field // 更新时间 DeletedAt field.Field // 删除时间 - EdgeHost field.String // 节点地址 - EdgeID field.Int32 // 节点ID - Whitelists field.String // IP白名单,逗号分隔 - ResourceID field.Int32 // 套餐ID fieldMap map[string]field.Expr } @@ -92,10 +92,14 @@ func (c *channel) updateTableName(table string) *channel { c.ID = field.NewInt32(table, "id") c.UserID = field.NewInt32(table, "user_id") c.ProxyID = field.NewInt32(table, "proxy_id") + c.EdgeID = field.NewInt32(table, "edge_id") + c.ResourceID = field.NewInt32(table, "resource_id") c.ProxyHost = field.NewString(table, "proxy_host") c.ProxyPort = field.NewInt32(table, "proxy_port") + c.EdgeHost = field.NewString(table, "edge_host") c.Protocol = field.NewInt32(table, "protocol") c.AuthIP = field.NewBool(table, "auth_ip") + c.Whitelists = field.NewString(table, "whitelists") c.AuthPass = field.NewBool(table, "auth_pass") c.Username = field.NewString(table, "username") c.Password = field.NewString(table, "password") @@ -103,10 +107,6 @@ func (c *channel) updateTableName(table string) *channel { c.CreatedAt = field.NewField(table, "created_at") c.UpdatedAt = field.NewField(table, "updated_at") c.DeletedAt = field.NewField(table, "deleted_at") - c.EdgeHost = field.NewString(table, "edge_host") - c.EdgeID = field.NewInt32(table, "edge_id") - c.Whitelists = field.NewString(table, "whitelists") - c.ResourceID = field.NewInt32(table, "resource_id") c.fillFieldMap() @@ -127,10 +127,14 @@ func (c *channel) fillFieldMap() { c.fieldMap["id"] = c.ID c.fieldMap["user_id"] = c.UserID c.fieldMap["proxy_id"] = c.ProxyID + c.fieldMap["edge_id"] = c.EdgeID + c.fieldMap["resource_id"] = c.ResourceID c.fieldMap["proxy_host"] = c.ProxyHost c.fieldMap["proxy_port"] = c.ProxyPort + c.fieldMap["edge_host"] = c.EdgeHost c.fieldMap["protocol"] = c.Protocol c.fieldMap["auth_ip"] = c.AuthIP + c.fieldMap["whitelists"] = c.Whitelists c.fieldMap["auth_pass"] = c.AuthPass c.fieldMap["username"] = c.Username c.fieldMap["password"] = c.Password @@ -138,10 +142,6 @@ func (c *channel) fillFieldMap() { c.fieldMap["created_at"] = c.CreatedAt c.fieldMap["updated_at"] = c.UpdatedAt c.fieldMap["deleted_at"] = c.DeletedAt - c.fieldMap["edge_host"] = c.EdgeHost - c.fieldMap["edge_id"] = c.EdgeID - c.fieldMap["whitelists"] = c.Whitelists - c.fieldMap["resource_id"] = c.ResourceID } func (c channel) clone(db *gorm.DB) channel { diff --git a/web/queries/client.gen.go b/web/queries/client.gen.go index 8f3332e..6c0247f 100644 --- a/web/queries/client.gen.go +++ b/web/queries/client.gen.go @@ -31,14 +31,11 @@ func newClient(db *gorm.DB, opts ...gen.DOOption) client { _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.GrantPassword = field.NewBool(tableName, "grant_password") _client.Spec = field.NewInt32(tableName, "spec") _client.Name = field.NewString(tableName, "name") _client.Icon = field.NewString(tableName, "icon") _client.Status = field.NewInt32(tableName, "status") + _client.Type = field.NewInt32(tableName, "type") _client.CreatedAt = field.NewField(tableName, "created_at") _client.UpdatedAt = field.NewField(tableName, "updated_at") _client.DeletedAt = field.NewField(tableName, "deleted_at") @@ -51,22 +48,19 @@ func newClient(db *gorm.DB, opts ...gen.DOOption) 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 // 允许刷新令牌授予 - GrantPassword field.Bool // 允许密码授予 - Spec field.Int32 // 安全规范:1-native,2-browser,3-web,4-trusted - Name field.String // 名称 - Icon field.String // 图标URL - Status field.Int32 // 状态:0-禁用,1-正常 - CreatedAt field.Field // 创建时间 - UpdatedAt field.Field // 更新时间 - DeletedAt field.Field // 删除时间 + ALL field.Asterisk + ID field.Int32 // 客户端ID + ClientID field.String // OAuth2客户端标识符 + ClientSecret field.String // OAuth2客户端密钥 + RedirectURI field.String // OAuth2 重定向URI + Spec field.Int32 // 安全规范:1-native,2-browser,3-web,4-api + Name field.String // 名称 + Icon field.String // 图标URL + Status field.Int32 // 状态:0-禁用,1-正常 + Type field.Int32 // 类型:0-普通,1-官方 + CreatedAt field.Field // 创建时间 + UpdatedAt field.Field // 更新时间 + DeletedAt field.Field // 删除时间 fieldMap map[string]field.Expr } @@ -87,14 +81,11 @@ func (c *client) updateTableName(table string) *client { 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.GrantPassword = field.NewBool(table, "grant_password") c.Spec = field.NewInt32(table, "spec") c.Name = field.NewString(table, "name") c.Icon = field.NewString(table, "icon") c.Status = field.NewInt32(table, "status") + c.Type = field.NewInt32(table, "type") c.CreatedAt = field.NewField(table, "created_at") c.UpdatedAt = field.NewField(table, "updated_at") c.DeletedAt = field.NewField(table, "deleted_at") @@ -114,19 +105,16 @@ func (c *client) GetFieldByName(fieldName string) (field.OrderExpr, bool) { } func (c *client) fillFieldMap() { - c.fieldMap = make(map[string]field.Expr, 15) + c.fieldMap = make(map[string]field.Expr, 12) 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["grant_password"] = c.GrantPassword c.fieldMap["spec"] = c.Spec c.fieldMap["name"] = c.Name c.fieldMap["icon"] = c.Icon c.fieldMap["status"] = c.Status + c.fieldMap["type"] = c.Type c.fieldMap["created_at"] = c.CreatedAt c.fieldMap["updated_at"] = c.UpdatedAt c.fieldMap["deleted_at"] = c.DeletedAt diff --git a/web/queries/logs_login.gen.go b/web/queries/logs_login.gen.go index f6a2bcd..7822fd0 100644 --- a/web/queries/logs_login.gen.go +++ b/web/queries/logs_login.gen.go @@ -29,12 +29,12 @@ func newLogsLogin(db *gorm.DB, opts ...gen.DOOption) logsLogin { _logsLogin.ALL = field.NewAsterisk(tableName) _logsLogin.ID = field.NewInt32(tableName, "id") _logsLogin.IP = field.NewString(tableName, "ip") - _logsLogin.Ua = field.NewString(tableName, "ua") + _logsLogin.UA = field.NewString(tableName, "ua") _logsLogin.GrantType = field.NewString(tableName, "grant_type") _logsLogin.PasswordGrantType = field.NewString(tableName, "password_grant_type") _logsLogin.Success = field.NewBool(tableName, "success") - _logsLogin.Time = field.NewField(tableName, "time") _logsLogin.UserID = field.NewInt32(tableName, "user_id") + _logsLogin.Time = field.NewField(tableName, "time") _logsLogin.fillFieldMap() @@ -47,12 +47,12 @@ type logsLogin struct { ALL field.Asterisk ID field.Int32 // 登录日志ID IP field.String // IP地址 - Ua field.String // 用户代理 + UA field.String // 用户代理 GrantType field.String // 授权类型:authorization_code-授权码模式,client_credentials-客户端凭证模式,refresh_token-刷新令牌模式,password-密码模式 PasswordGrantType field.String // 密码模式子授权类型:password-账号密码,phone_code-手机验证码,email_code-邮箱验证码 Success field.Bool // 登录是否成功 - Time field.Field // 登录时间 UserID field.Int32 // 用户ID + Time field.Field // 登录时间 fieldMap map[string]field.Expr } @@ -71,12 +71,12 @@ func (l *logsLogin) updateTableName(table string) *logsLogin { l.ALL = field.NewAsterisk(table) l.ID = field.NewInt32(table, "id") l.IP = field.NewString(table, "ip") - l.Ua = field.NewString(table, "ua") + l.UA = field.NewString(table, "ua") l.GrantType = field.NewString(table, "grant_type") l.PasswordGrantType = field.NewString(table, "password_grant_type") l.Success = field.NewBool(table, "success") - l.Time = field.NewField(table, "time") l.UserID = field.NewInt32(table, "user_id") + l.Time = field.NewField(table, "time") l.fillFieldMap() @@ -96,12 +96,12 @@ func (l *logsLogin) fillFieldMap() { l.fieldMap = make(map[string]field.Expr, 8) l.fieldMap["id"] = l.ID l.fieldMap["ip"] = l.IP - l.fieldMap["ua"] = l.Ua + l.fieldMap["ua"] = l.UA l.fieldMap["grant_type"] = l.GrantType l.fieldMap["password_grant_type"] = l.PasswordGrantType l.fieldMap["success"] = l.Success - l.fieldMap["time"] = l.Time l.fieldMap["user_id"] = l.UserID + l.fieldMap["time"] = l.Time } func (l logsLogin) clone(db *gorm.DB) logsLogin { diff --git a/web/queries/logs_request.gen.go b/web/queries/logs_request.gen.go index 336b504..4368b38 100644 --- a/web/queries/logs_request.gen.go +++ b/web/queries/logs_request.gen.go @@ -28,16 +28,16 @@ func newLogsRequest(db *gorm.DB, opts ...gen.DOOption) logsRequest { tableName := _logsRequest.logsRequestDo.TableName() _logsRequest.ALL = field.NewAsterisk(tableName) _logsRequest.ID = field.NewInt32(tableName, "id") - _logsRequest.Identity = field.NewInt32(tableName, "identity") - _logsRequest.Visitor = field.NewInt32(tableName, "visitor") _logsRequest.IP = field.NewString(tableName, "ip") - _logsRequest.Ua = field.NewString(tableName, "ua") + _logsRequest.UA = field.NewString(tableName, "ua") + _logsRequest.UserID = field.NewInt32(tableName, "user_id") + _logsRequest.ClientID = field.NewInt32(tableName, "client_id") _logsRequest.Method = field.NewString(tableName, "method") _logsRequest.Path = field.NewString(tableName, "path") - _logsRequest.Latency = field.NewString(tableName, "latency") _logsRequest.Status = field.NewInt32(tableName, "status") _logsRequest.Error = field.NewString(tableName, "error") _logsRequest.Time = field.NewField(tableName, "time") + _logsRequest.Latency = field.NewString(tableName, "latency") _logsRequest.fillFieldMap() @@ -49,16 +49,16 @@ type logsRequest struct { ALL field.Asterisk ID field.Int32 // 访问日志ID - Identity field.Int32 // 访客身份:0-游客,1-用户,2-管理员,3-公共服务,4-安全服务,5-内部服务 - Visitor field.Int32 // 访客ID IP field.String // IP地址 - Ua field.String // 用户代理 + UA field.String // 用户代理 + UserID field.Int32 // 用户ID + ClientID field.Int32 // 客户端ID Method field.String // 请求方法 Path field.String // 请求路径 - Latency field.String // 请求延迟 Status field.Int32 // 响应状态码 Error field.String // 错误信息 Time field.Field // 请求时间 + Latency field.String // 请求延迟 fieldMap map[string]field.Expr } @@ -76,16 +76,16 @@ func (l logsRequest) As(alias string) *logsRequest { func (l *logsRequest) updateTableName(table string) *logsRequest { l.ALL = field.NewAsterisk(table) l.ID = field.NewInt32(table, "id") - l.Identity = field.NewInt32(table, "identity") - l.Visitor = field.NewInt32(table, "visitor") l.IP = field.NewString(table, "ip") - l.Ua = field.NewString(table, "ua") + l.UA = field.NewString(table, "ua") + l.UserID = field.NewInt32(table, "user_id") + l.ClientID = field.NewInt32(table, "client_id") l.Method = field.NewString(table, "method") l.Path = field.NewString(table, "path") - l.Latency = field.NewString(table, "latency") l.Status = field.NewInt32(table, "status") l.Error = field.NewString(table, "error") l.Time = field.NewField(table, "time") + l.Latency = field.NewString(table, "latency") l.fillFieldMap() @@ -104,16 +104,16 @@ func (l *logsRequest) GetFieldByName(fieldName string) (field.OrderExpr, bool) { func (l *logsRequest) fillFieldMap() { l.fieldMap = make(map[string]field.Expr, 11) l.fieldMap["id"] = l.ID - l.fieldMap["identity"] = l.Identity - l.fieldMap["visitor"] = l.Visitor l.fieldMap["ip"] = l.IP - l.fieldMap["ua"] = l.Ua + l.fieldMap["ua"] = l.UA + l.fieldMap["user_id"] = l.UserID + l.fieldMap["client_id"] = l.ClientID l.fieldMap["method"] = l.Method l.fieldMap["path"] = l.Path - l.fieldMap["latency"] = l.Latency l.fieldMap["status"] = l.Status l.fieldMap["error"] = l.Error l.fieldMap["time"] = l.Time + l.fieldMap["latency"] = l.Latency } func (l logsRequest) clone(db *gorm.DB) logsRequest { diff --git a/web/queries/proxy.gen.go b/web/queries/proxy.gen.go index f55683a..9cd4e2c 100644 --- a/web/queries/proxy.gen.go +++ b/web/queries/proxy.gen.go @@ -31,12 +31,12 @@ func newProxy(db *gorm.DB, opts ...gen.DOOption) proxy { _proxy.Version = field.NewInt32(tableName, "version") _proxy.Name = field.NewString(tableName, "name") _proxy.Host = field.NewString(tableName, "host") - _proxy.Type = field.NewInt32(tableName, "type") _proxy.Secret = field.NewString(tableName, "secret") + _proxy.Type = field.NewInt32(tableName, "type") + _proxy.Status = field.NewInt32(tableName, "status") _proxy.CreatedAt = field.NewField(tableName, "created_at") _proxy.UpdatedAt = field.NewField(tableName, "updated_at") _proxy.DeletedAt = field.NewField(tableName, "deleted_at") - _proxy.Status = field.NewInt32(tableName, "status") _proxy.Edges = proxyHasManyEdges{ db: db.Session(&gorm.Session{}), @@ -56,12 +56,12 @@ type proxy struct { Version field.Int32 // 代理服务版本 Name field.String // 代理服务名称 Host field.String // 代理服务地址 - Type field.Int32 // 代理服务类型:1-三方,2-自有 Secret field.String // 代理服务密钥 + Type field.Int32 // 代理服务类型:1-三方,2-自有 + Status field.Int32 // 代理服务状态:0-离线,1-在线 CreatedAt field.Field // 创建时间 UpdatedAt field.Field // 更新时间 DeletedAt field.Field // 删除时间 - Status field.Int32 // 代理服务状态:0-离线,1-在线 Edges proxyHasManyEdges fieldMap map[string]field.Expr @@ -83,12 +83,12 @@ func (p *proxy) updateTableName(table string) *proxy { p.Version = field.NewInt32(table, "version") p.Name = field.NewString(table, "name") p.Host = field.NewString(table, "host") - p.Type = field.NewInt32(table, "type") p.Secret = field.NewString(table, "secret") + p.Type = field.NewInt32(table, "type") + p.Status = field.NewInt32(table, "status") p.CreatedAt = field.NewField(table, "created_at") p.UpdatedAt = field.NewField(table, "updated_at") p.DeletedAt = field.NewField(table, "deleted_at") - p.Status = field.NewInt32(table, "status") p.fillFieldMap() @@ -110,12 +110,12 @@ func (p *proxy) fillFieldMap() { p.fieldMap["version"] = p.Version p.fieldMap["name"] = p.Name p.fieldMap["host"] = p.Host - p.fieldMap["type"] = p.Type p.fieldMap["secret"] = p.Secret + p.fieldMap["type"] = p.Type + p.fieldMap["status"] = p.Status p.fieldMap["created_at"] = p.CreatedAt p.fieldMap["updated_at"] = p.UpdatedAt p.fieldMap["deleted_at"] = p.DeletedAt - p.fieldMap["status"] = p.Status } diff --git a/web/queries/session.gen.go b/web/queries/session.gen.go index b432344..3a135ec 100644 --- a/web/queries/session.gen.go +++ b/web/queries/session.gen.go @@ -29,10 +29,10 @@ func newSession(db *gorm.DB, opts ...gen.DOOption) session { _session.ALL = field.NewAsterisk(tableName) _session.ID = field.NewInt32(tableName, "id") _session.UserID = field.NewInt32(tableName, "user_id") + _session.AdminID = field.NewInt32(tableName, "admin_id") _session.ClientID = field.NewInt32(tableName, "client_id") _session.IP = field.NewString(tableName, "ip") - _session.Ua = field.NewString(tableName, "ua") - _session.GrantType = field.NewString(tableName, "grant_type") + _session.UA = field.NewString(tableName, "ua") _session.AccessToken = field.NewString(tableName, "access_token") _session.AccessTokenExpires = field.NewField(tableName, "access_token_expires") _session.RefreshToken = field.NewString(tableName, "refresh_token") @@ -41,6 +41,23 @@ func newSession(db *gorm.DB, opts ...gen.DOOption) session { _session.CreatedAt = field.NewField(tableName, "created_at") _session.UpdatedAt = field.NewField(tableName, "updated_at") _session.DeletedAt = field.NewField(tableName, "deleted_at") + _session.User = sessionBelongsToUser{ + db: db.Session(&gorm.Session{}), + + RelationField: field.NewRelation("User", "models.User"), + } + + _session.Admin = sessionBelongsToAdmin{ + db: db.Session(&gorm.Session{}), + + RelationField: field.NewRelation("Admin", "models.Admin"), + } + + _session.Client = sessionBelongsToClient{ + db: db.Session(&gorm.Session{}), + + RelationField: field.NewRelation("Client", "models.Client"), + } _session.fillFieldMap() @@ -53,10 +70,10 @@ type session struct { ALL field.Asterisk ID field.Int32 // 会话ID UserID field.Int32 // 用户ID + AdminID field.Int32 // 管理员ID ClientID field.Int32 // 客户端ID IP field.String // IP地址 - Ua field.String // 用户代理 - GrantType field.String // 授权类型:authorization_code-授权码模式,client_credentials-客户端凭证模式,refresh_token-刷新令牌模式,password-密码模式 + UA field.String // 用户代理 AccessToken field.String // 访问令牌 AccessTokenExpires field.Field // 访问令牌过期时间 RefreshToken field.String // 刷新令牌 @@ -65,6 +82,11 @@ type session struct { CreatedAt field.Field // 创建时间 UpdatedAt field.Field // 更新时间 DeletedAt field.Field // 删除时间 + User sessionBelongsToUser + + Admin sessionBelongsToAdmin + + Client sessionBelongsToClient fieldMap map[string]field.Expr } @@ -83,10 +105,10 @@ func (s *session) updateTableName(table string) *session { s.ALL = field.NewAsterisk(table) s.ID = field.NewInt32(table, "id") s.UserID = field.NewInt32(table, "user_id") + s.AdminID = field.NewInt32(table, "admin_id") s.ClientID = field.NewInt32(table, "client_id") s.IP = field.NewString(table, "ip") - s.Ua = field.NewString(table, "ua") - s.GrantType = field.NewString(table, "grant_type") + s.UA = field.NewString(table, "ua") s.AccessToken = field.NewString(table, "access_token") s.AccessTokenExpires = field.NewField(table, "access_token_expires") s.RefreshToken = field.NewString(table, "refresh_token") @@ -111,13 +133,13 @@ func (s *session) GetFieldByName(fieldName string) (field.OrderExpr, bool) { } func (s *session) fillFieldMap() { - s.fieldMap = make(map[string]field.Expr, 14) + s.fieldMap = make(map[string]field.Expr, 17) s.fieldMap["id"] = s.ID s.fieldMap["user_id"] = s.UserID + s.fieldMap["admin_id"] = s.AdminID s.fieldMap["client_id"] = s.ClientID s.fieldMap["ip"] = s.IP - s.fieldMap["ua"] = s.Ua - s.fieldMap["grant_type"] = s.GrantType + s.fieldMap["ua"] = s.UA s.fieldMap["access_token"] = s.AccessToken s.fieldMap["access_token_expires"] = s.AccessTokenExpires s.fieldMap["refresh_token"] = s.RefreshToken @@ -126,18 +148,271 @@ func (s *session) fillFieldMap() { s.fieldMap["created_at"] = s.CreatedAt s.fieldMap["updated_at"] = s.UpdatedAt s.fieldMap["deleted_at"] = s.DeletedAt + } func (s session) clone(db *gorm.DB) session { s.sessionDo.ReplaceConnPool(db.Statement.ConnPool) + s.User.db = db.Session(&gorm.Session{Initialized: true}) + s.User.db.Statement.ConnPool = db.Statement.ConnPool + s.Admin.db = db.Session(&gorm.Session{Initialized: true}) + s.Admin.db.Statement.ConnPool = db.Statement.ConnPool + s.Client.db = db.Session(&gorm.Session{Initialized: true}) + s.Client.db.Statement.ConnPool = db.Statement.ConnPool return s } func (s session) replaceDB(db *gorm.DB) session { s.sessionDo.ReplaceDB(db) + s.User.db = db.Session(&gorm.Session{}) + s.Admin.db = db.Session(&gorm.Session{}) + s.Client.db = db.Session(&gorm.Session{}) return s } +type sessionBelongsToUser struct { + db *gorm.DB + + field.RelationField +} + +func (a sessionBelongsToUser) Where(conds ...field.Expr) *sessionBelongsToUser { + if len(conds) == 0 { + return &a + } + + exprs := make([]clause.Expression, 0, len(conds)) + for _, cond := range conds { + exprs = append(exprs, cond.BeCond().(clause.Expression)) + } + a.db = a.db.Clauses(clause.Where{Exprs: exprs}) + return &a +} + +func (a sessionBelongsToUser) WithContext(ctx context.Context) *sessionBelongsToUser { + a.db = a.db.WithContext(ctx) + return &a +} + +func (a sessionBelongsToUser) Session(session *gorm.Session) *sessionBelongsToUser { + a.db = a.db.Session(session) + return &a +} + +func (a sessionBelongsToUser) Model(m *models.Session) *sessionBelongsToUserTx { + return &sessionBelongsToUserTx{a.db.Model(m).Association(a.Name())} +} + +func (a sessionBelongsToUser) Unscoped() *sessionBelongsToUser { + a.db = a.db.Unscoped() + return &a +} + +type sessionBelongsToUserTx struct{ tx *gorm.Association } + +func (a sessionBelongsToUserTx) Find() (result *models.User, err error) { + return result, a.tx.Find(&result) +} + +func (a sessionBelongsToUserTx) Append(values ...*models.User) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Append(targetValues...) +} + +func (a sessionBelongsToUserTx) Replace(values ...*models.User) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Replace(targetValues...) +} + +func (a sessionBelongsToUserTx) Delete(values ...*models.User) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Delete(targetValues...) +} + +func (a sessionBelongsToUserTx) Clear() error { + return a.tx.Clear() +} + +func (a sessionBelongsToUserTx) Count() int64 { + return a.tx.Count() +} + +func (a sessionBelongsToUserTx) Unscoped() *sessionBelongsToUserTx { + a.tx = a.tx.Unscoped() + return &a +} + +type sessionBelongsToAdmin struct { + db *gorm.DB + + field.RelationField +} + +func (a sessionBelongsToAdmin) Where(conds ...field.Expr) *sessionBelongsToAdmin { + if len(conds) == 0 { + return &a + } + + exprs := make([]clause.Expression, 0, len(conds)) + for _, cond := range conds { + exprs = append(exprs, cond.BeCond().(clause.Expression)) + } + a.db = a.db.Clauses(clause.Where{Exprs: exprs}) + return &a +} + +func (a sessionBelongsToAdmin) WithContext(ctx context.Context) *sessionBelongsToAdmin { + a.db = a.db.WithContext(ctx) + return &a +} + +func (a sessionBelongsToAdmin) Session(session *gorm.Session) *sessionBelongsToAdmin { + a.db = a.db.Session(session) + return &a +} + +func (a sessionBelongsToAdmin) Model(m *models.Session) *sessionBelongsToAdminTx { + return &sessionBelongsToAdminTx{a.db.Model(m).Association(a.Name())} +} + +func (a sessionBelongsToAdmin) Unscoped() *sessionBelongsToAdmin { + a.db = a.db.Unscoped() + return &a +} + +type sessionBelongsToAdminTx struct{ tx *gorm.Association } + +func (a sessionBelongsToAdminTx) Find() (result *models.Admin, err error) { + return result, a.tx.Find(&result) +} + +func (a sessionBelongsToAdminTx) Append(values ...*models.Admin) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Append(targetValues...) +} + +func (a sessionBelongsToAdminTx) Replace(values ...*models.Admin) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Replace(targetValues...) +} + +func (a sessionBelongsToAdminTx) Delete(values ...*models.Admin) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Delete(targetValues...) +} + +func (a sessionBelongsToAdminTx) Clear() error { + return a.tx.Clear() +} + +func (a sessionBelongsToAdminTx) Count() int64 { + return a.tx.Count() +} + +func (a sessionBelongsToAdminTx) Unscoped() *sessionBelongsToAdminTx { + a.tx = a.tx.Unscoped() + return &a +} + +type sessionBelongsToClient struct { + db *gorm.DB + + field.RelationField +} + +func (a sessionBelongsToClient) Where(conds ...field.Expr) *sessionBelongsToClient { + if len(conds) == 0 { + return &a + } + + exprs := make([]clause.Expression, 0, len(conds)) + for _, cond := range conds { + exprs = append(exprs, cond.BeCond().(clause.Expression)) + } + a.db = a.db.Clauses(clause.Where{Exprs: exprs}) + return &a +} + +func (a sessionBelongsToClient) WithContext(ctx context.Context) *sessionBelongsToClient { + a.db = a.db.WithContext(ctx) + return &a +} + +func (a sessionBelongsToClient) Session(session *gorm.Session) *sessionBelongsToClient { + a.db = a.db.Session(session) + return &a +} + +func (a sessionBelongsToClient) Model(m *models.Session) *sessionBelongsToClientTx { + return &sessionBelongsToClientTx{a.db.Model(m).Association(a.Name())} +} + +func (a sessionBelongsToClient) Unscoped() *sessionBelongsToClient { + a.db = a.db.Unscoped() + return &a +} + +type sessionBelongsToClientTx struct{ tx *gorm.Association } + +func (a sessionBelongsToClientTx) Find() (result *models.Client, err error) { + return result, a.tx.Find(&result) +} + +func (a sessionBelongsToClientTx) Append(values ...*models.Client) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Append(targetValues...) +} + +func (a sessionBelongsToClientTx) Replace(values ...*models.Client) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Replace(targetValues...) +} + +func (a sessionBelongsToClientTx) Delete(values ...*models.Client) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Delete(targetValues...) +} + +func (a sessionBelongsToClientTx) Clear() error { + return a.tx.Clear() +} + +func (a sessionBelongsToClientTx) Count() int64 { + return a.tx.Count() +} + +func (a sessionBelongsToClientTx) Unscoped() *sessionBelongsToClientTx { + a.tx = a.tx.Unscoped() + return &a +} + type sessionDo struct{ gen.DO } func (s sessionDo) Debug() *sessionDo { diff --git a/web/queries/trade.gen.go b/web/queries/trade.gen.go index c3d379a..db0ac36 100644 --- a/web/queries/trade.gen.go +++ b/web/queries/trade.gen.go @@ -37,16 +37,16 @@ func newTrade(db *gorm.DB, opts ...gen.DOOption) trade { _trade.Amount = field.NewField(tableName, "amount") _trade.Payment = field.NewField(tableName, "payment") _trade.Method = field.NewInt32(tableName, "method") - _trade.Status = field.NewInt32(tableName, "status") - _trade.CreatedAt = field.NewField(tableName, "created_at") - _trade.UpdatedAt = field.NewField(tableName, "updated_at") - _trade.DeletedAt = field.NewField(tableName, "deleted_at") - _trade.Acquirer = field.NewInt32(tableName, "acquirer") _trade.Platform = field.NewInt32(tableName, "platform") + _trade.Acquirer = field.NewInt32(tableName, "acquirer") + _trade.Status = field.NewInt32(tableName, "status") + _trade.Refunded = field.NewBool(tableName, "refunded") _trade.PaymentURL = field.NewString(tableName, "payment_url") _trade.CompletedAt = field.NewField(tableName, "completed_at") _trade.CanceledAt = field.NewField(tableName, "canceled_at") - _trade.Refunded = field.NewBool(tableName, "refunded") + _trade.CreatedAt = field.NewField(tableName, "created_at") + _trade.UpdatedAt = field.NewField(tableName, "updated_at") + _trade.DeletedAt = field.NewField(tableName, "deleted_at") _trade.fillFieldMap() @@ -65,18 +65,18 @@ type trade struct { Subject field.String // 订单主题 Remark field.String // 订单备注 Amount field.Field // 订单总金额 - Payment field.Field // 支付金额 - Method field.Int32 // 支付方式:1-支付宝,2-微信,3-商福通渠道支付宝,4-商福通渠道微信 - Status field.Int32 // 订单状态:0-待支付,1-已支付,2-已取消 - CreatedAt field.Field // 创建时间 - UpdatedAt field.Field // 更新时间 - DeletedAt field.Field // 删除时间 - Acquirer field.Int32 // 收单机构:1-支付宝,2-微信,3-银联 + Payment field.Field // 实际支付金额 + Method field.Int32 // 支付方式:1-支付宝,2-微信,3-商福通,4-商福通渠道支付宝,5-商福通渠道微信 Platform field.Int32 // 支付平台:1-电脑网站,2-手机网站 + Acquirer field.Int32 // 收单机构:1-支付宝,2-微信,3-银联 + Status field.Int32 // 订单状态:0-待支付,1-已支付,2-已取消 + Refunded field.Bool PaymentURL field.String // 支付链接 CompletedAt field.Field // 支付时间 CanceledAt field.Field // 取消时间 - Refunded field.Bool + CreatedAt field.Field // 创建时间 + UpdatedAt field.Field // 更新时间 + DeletedAt field.Field // 删除时间 fieldMap map[string]field.Expr } @@ -103,16 +103,16 @@ func (t *trade) updateTableName(table string) *trade { t.Amount = field.NewField(table, "amount") t.Payment = field.NewField(table, "payment") t.Method = field.NewInt32(table, "method") - t.Status = field.NewInt32(table, "status") - t.CreatedAt = field.NewField(table, "created_at") - t.UpdatedAt = field.NewField(table, "updated_at") - t.DeletedAt = field.NewField(table, "deleted_at") - t.Acquirer = field.NewInt32(table, "acquirer") t.Platform = field.NewInt32(table, "platform") + t.Acquirer = field.NewInt32(table, "acquirer") + t.Status = field.NewInt32(table, "status") + t.Refunded = field.NewBool(table, "refunded") t.PaymentURL = field.NewString(table, "payment_url") t.CompletedAt = field.NewField(table, "completed_at") t.CanceledAt = field.NewField(table, "canceled_at") - t.Refunded = field.NewBool(table, "refunded") + t.CreatedAt = field.NewField(table, "created_at") + t.UpdatedAt = field.NewField(table, "updated_at") + t.DeletedAt = field.NewField(table, "deleted_at") t.fillFieldMap() @@ -140,16 +140,16 @@ func (t *trade) fillFieldMap() { t.fieldMap["amount"] = t.Amount t.fieldMap["payment"] = t.Payment t.fieldMap["method"] = t.Method - t.fieldMap["status"] = t.Status - t.fieldMap["created_at"] = t.CreatedAt - t.fieldMap["updated_at"] = t.UpdatedAt - t.fieldMap["deleted_at"] = t.DeletedAt - t.fieldMap["acquirer"] = t.Acquirer t.fieldMap["platform"] = t.Platform + t.fieldMap["acquirer"] = t.Acquirer + t.fieldMap["status"] = t.Status + t.fieldMap["refunded"] = t.Refunded t.fieldMap["payment_url"] = t.PaymentURL t.fieldMap["completed_at"] = t.CompletedAt t.fieldMap["canceled_at"] = t.CanceledAt - t.fieldMap["refunded"] = t.Refunded + t.fieldMap["created_at"] = t.CreatedAt + t.fieldMap["updated_at"] = t.UpdatedAt + t.fieldMap["deleted_at"] = t.DeletedAt } func (t trade) clone(db *gorm.DB) trade { diff --git a/web/router.go b/web/routes.go similarity index 85% rename from web/router.go rename to web/routes.go index 2aea85a..bf73560 100644 --- a/web/router.go +++ b/web/routes.go @@ -1,7 +1,7 @@ package web import ( - "platform/web/core" + auth2 "platform/web/auth" "platform/web/handlers" "github.com/gofiber/fiber/v2" @@ -12,7 +12,7 @@ func ApplyRouters(app *fiber.App) { // 认证 auth := api.Group("/auth") - auth.Post("/token", handlers.Token) + auth.Post("/token", auth2.Token) auth.Post("/revoke", handlers.Revoke) auth.Post("/introspect", handlers.Introspect) auth.Post("/verify/sms", handlers.SmsCode) @@ -47,7 +47,6 @@ func ApplyRouters(app *fiber.App) { channel.Post("/list", handlers.ListChannels) channel.Post("/create", handlers.CreateChannel) channel.Post("/remove", handlers.RemoveChannels) - channel.Post("/remove/by-task", handlers.RemoveChannelByTask) // 交易 trade := api.Group("/trade") @@ -75,12 +74,6 @@ func ApplyRouters(app *fiber.App) { edge.Post("/all", handlers.AllEdgesAvailable) // 临时 - app.Get("/test", func(c *fiber.Ctx) error { - return core.NewBizErr("测试错误") - }) - - // 异步任务客户端 - tasks := api.Group("/tasks") - tasks.Post("/channel/remove", handlers.RemoveChannelByTask) - tasks.Post("/trade/cancel", handlers.TradeCancelByTask) + debug := app.Group("/debug") + debug.Get("/sms/:phone", handlers.DebugGetSmsCode) } diff --git a/web/services/auth.go b/web/services/auth.go deleted file mode 100644 index 0d21551..0000000 --- a/web/services/auth.go +++ /dev/null @@ -1,201 +0,0 @@ -package services - -import ( - "context" - "errors" - "golang.org/x/crypto/bcrypt" - "log/slog" - "platform/pkg/u" - auth2 "platform/web/auth" - "platform/web/core" - client2 "platform/web/domains/client" - user2 "platform/web/domains/user" - "platform/web/globals/orm" - m "platform/web/models" - q "platform/web/queries" - "time" - - "gorm.io/gorm" -) - -var Auth = &authService{} - -type authService struct{} - -// OauthAuthorizationCode 验证授权码 -func (s *authService) OauthAuthorizationCode(ctx context.Context, client *m.Client, code, redirectURI, codeVerifier string) (*auth2.TokenDetails, error) { - return nil, errors.New("TODO") -} - -// OauthClientCredentials 验证客户端凭证 -func (s *authService) OauthClientCredentials(ctx context.Context, client *m.Client, scope ...string) (*auth2.TokenDetails, error) { - - var clientType = auth2.PayloadTypeFromClientSpec(client2.Spec(client.Spec)) - - var permissions = make(map[string]struct{}, len(scope)) - for _, item := range scope { - permissions[item] = struct{}{} - } - - // 保存会话并返回令牌 - authCtx := auth2.Context{ - Permissions: permissions, - Payload: auth2.Payload{ - Id: client.ID, - Type: clientType, - Name: client.Name, - }, - } - - token, err := auth2.CreateSession(ctx, &authCtx, false) - if err != nil { - return nil, err - } - - return token, nil -} - -// OauthRefreshToken 验证刷新令牌 -func (s *authService) OauthRefreshToken(ctx context.Context, _ *m.Client, refreshToken string, scope ...[]string) (*auth2.TokenDetails, error) { - details, err := auth2.RefreshSession(ctx, refreshToken, true) - if err != nil { - return nil, err - } - - return details, nil -} - -// OauthPassword 验证密码 -func (s *authService) OauthPassword(ctx context.Context, _ *m.Client, data *GrantPasswordData, ip, agent string) (*auth2.TokenDetails, error) { - var user *m.User - err := q.Q.Transaction(func(tx *q.Query) error { - - switch data.LoginType { - case auth2.GrantPasswordPhone: - // 验证验证码 - err := Verifier.VerifySms(ctx, data.Username, data.Password) - if err != nil { - if errors.Is(err, ErrVerifierServiceInvalid) { - return ErrOauthInvalidRequest - } - return err - } - - // 查找用户 - user, err = - tx.User.Where(tx.User.Phone.Eq(data.Username)).Take() - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return err - } - case auth2.GrantPasswordEmail: - return core.NewServErr("邮箱登录暂不可用") - case auth2.GrantPasswordSecret: - var err error - user, err = tx.User. - Where(tx.User.Phone.Eq(data.Username)). - Or(tx.User.Email.Eq(data.Username)). - Or(tx.User.Username.Eq(data.Username)). - Take() - if err != nil { - slog.Debug("查找用户失败", "error", err) - return core.NewBizErr("用户不存在或密码错误") - } - - // 账户状态 - if user2.Status(user.Status) == user2.StatusDisabled { - slog.Debug("账户状态异常", "username", data.Username, "status", user.Status) - return core.NewBizErr("用户不存在或密码错误") - } - - // 验证密码 - if user.Password == nil || *user.Password == "" { - slog.Debug("用户未设置密码", "username", data.Username) - return core.NewBizErr("用户不存在或密码错误") - } - if bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(data.Password)) != nil { - slog.Debug("密码验证失败", "username", data.Username) - return core.NewBizErr("用户不存在或密码错误") - } - - default: - return ErrOauthInvalidRequest - } - - // 如果用户不存在,初始化用户 todo 初始化默认权限信息 - if user == nil { - user = &m.User{ - Phone: data.Username, - Username: u.P(data.Username), - } - } - - // 更新用户的登录时间 - user.LastLogin = u.P(orm.LocalDateTime(time.Now())) - user.LastLoginHost = u.P(ip) - user.LastLoginAgent = u.P(agent) - if err := tx.User.Save(user); err != nil { - return err - } - - return nil - }) - if err != nil { - return nil, err - } - - // 保存到会话 - var name = "" - if user.Name != nil { - name = *user.Name - } - authCtx := auth2.Context{ - Payload: auth2.Payload{ - Id: user.ID, - Type: auth2.PayloadUser, - Name: name, - Avatar: user.Avatar, - }, - } - - token, err := auth2.CreateSession(ctx, &authCtx, data.Remember) - if err != nil { - return nil, err - } - - return token, nil -} - -type GrantCodeData struct { - Code string `json:"code" form:"code"` - RedirectURI string `json:"redirect_uri" form:"redirect_uri"` - CodeVerifier string `json:"code_verifier" form:"code_verifier"` -} - -type GrantClientData struct { -} - -type GrantRefreshData struct { - RefreshToken string `json:"refresh_token" form:"refresh_token"` -} - -type GrantPasswordData struct { - LoginType auth2.PasswordGrantType `json:"login_type" form:"login_type"` - Username string `json:"username" form:"username"` - Password string `json:"password" form:"password"` - Remember bool `json:"remember" form:"remember"` -} - -type AuthServiceError string - -func (e AuthServiceError) Error() string { - return string(e) -} - -const ( - ErrOauthInvalidRequest = AuthServiceError("invalid_request") - ErrOauthInvalidClient = AuthServiceError("invalid_client") - ErrOauthInvalidGrant = AuthServiceError("invalid_grant") - ErrOauthInvalidScope = AuthServiceError("invalid_scope") - ErrOauthUnauthorizedClient = AuthServiceError("unauthorized_client") - ErrOauthUnsupportedGrantType = AuthServiceError("unsupported_grant_type") -) diff --git a/web/services/channel.go b/web/services/channel.go index 8b9e658..e1dfc20 100644 --- a/web/services/channel.go +++ b/web/services/channel.go @@ -4,7 +4,6 @@ import ( "context" "database/sql" "fmt" - "github.com/gofiber/fiber/v2" "log/slog" "math" "math/rand/v2" @@ -15,15 +14,17 @@ import ( edge2 "platform/web/domains/edge" proxy2 "platform/web/domains/proxy" resource2 "platform/web/domains/resource" + "platform/web/events" g "platform/web/globals" "platform/web/globals/orm" m "platform/web/models" q "platform/web/queries" - "platform/web/tasks" "strconv" "strings" "time" + "github.com/gofiber/fiber/v2" + "github.com/hibiken/asynq" "gorm.io/gen/field" @@ -296,7 +297,7 @@ func (s *channelService) CreateChannel( ids[i] = channels[i].ID } _, err = g.Asynq.Enqueue( - tasks.NewRemoveChannel(ids), + events.NewRemoveChannel(ids), asynq.ProcessIn(duration), ) if err != nil { diff --git a/web/services/trade.go b/web/services/trade.go index c3ab058..48b47ea 100644 --- a/web/services/trade.go +++ b/web/services/trade.go @@ -12,11 +12,11 @@ import ( "platform/web/core" coupon2 "platform/web/domains/coupon" trade2 "platform/web/domains/trade" + "platform/web/events" g "platform/web/globals" "platform/web/globals/orm" m "platform/web/models" q "platform/web/queries" - "platform/web/tasks" "time" "github.com/shopspring/decimal" @@ -240,7 +240,7 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa } // 提交异步关闭事件 - _, err = g.Asynq.Enqueue(tasks.NewCancelTrade(tasks.CancelTradeData{ + _, err = g.Asynq.Enqueue(events.NewCancelTrade(events.CancelTradeData{ TradeNo: tradeNo, Method: method, })) @@ -417,7 +417,7 @@ func (s *tradeService) CancelTrade(tradeNo string, method trade2.Method, now tim MchOrderNo: &tradeNo, }) if err != nil { - slog.Debug(fmt.Sprintf("订单无需关闭:%s", err.Error())) + slog.Debug(fmt.Sprintf("订单无需关闭: %s", err.Error())) return nil } @@ -546,11 +546,11 @@ func (s *tradeService) CheckTrade(data *ModifyTradeData) (*CheckTradeResult, err return nil, core.NewBizErr("订单不存在") } return nil, core.NewServErr( - fmt.Sprintf("微信上游接口异常:code=%v,message=%v", apiErr.Code, apiErr.Message), + fmt.Sprintf("微信上游接口异常: code=%v,message=%v", apiErr.Code, apiErr.Message), apiErr, ) } - return nil, core.NewServErr(fmt.Sprintf("微信上游支付接口异常:%s", err.Error())) + return nil, core.NewServErr(fmt.Sprintf("微信上游支付接口异常: %s", err.Error())) } // 填充返回值 diff --git a/web/services/verifier.go b/web/services/verifier.go index 2839e41..19b00e6 100644 --- a/web/services/verifier.go +++ b/web/services/verifier.go @@ -19,28 +19,6 @@ import ( var Verifier = &verifierService{} -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 ( - VerifierSmsPurposeLogin VerifierSmsPurpose = iota -) - type verifierService struct { } @@ -148,6 +126,43 @@ func (s *verifierService) VerifySms(ctx context.Context, phone, code string) err return nil } +func (s *verifierService) GetSms(ctx context.Context, phone string) (string, error) { + key := smsKey(phone, VerifierSmsPurposeLogin) + + val, err := g.Redis.Get(ctx, key).Result() + if err != nil { + return "", fmt.Errorf("验证码获取失败: %w", err) + } + + return val, nil +} + func smsKey(phone string, purpose VerifierSmsPurpose) string { return fmt.Sprintf("verify:sms:%d:%s", purpose, phone) } + +// region 短信目的 + +type VerifierSmsPurpose int + +const ( + VerifierSmsPurposeLogin VerifierSmsPurpose = iota // 登录 +) + +// region 服务异常 + +type VerifierServiceError string + +func (e VerifierServiceError) Error() string { + return string(e) +} + +var ( + ErrVerifierServiceInvalid = VerifierServiceError("验证码错误") +) + +type VerifierServiceSendLimitErr int + +func (e VerifierServiceSendLimitErr) Error() string { + return "发送频率过快" +} diff --git a/web/tasks/task.go b/web/tasks/task.go new file mode 100644 index 0000000..1009dfa --- /dev/null +++ b/web/tasks/task.go @@ -0,0 +1,41 @@ +package tasks + +import ( + "context" + "encoding/json" + "fmt" + "platform/web/events" + s "platform/web/services" + "time" + + "github.com/hibiken/asynq" +) + +func HandleCancelTrade(_ context.Context, task *asynq.Task) (err error) { + data := new(events.CancelTradeData) + err = json.Unmarshal(task.Payload(), data) + if err != nil { + return fmt.Errorf("解析任务参数失败: %w", err) + } + + err = s.Trade.CancelTrade(data.TradeNo, data.Method, time.Now()) + if err != nil { + return fmt.Errorf("取消交易失败: %w", err) + } + return nil +} + +func HandleRemoveChannel(_ context.Context, task *asynq.Task) (err error) { + data := make([]int32, 0) + err = json.Unmarshal(task.Payload(), &data) + if err != nil { + return fmt.Errorf("解析任务参数失败: %w", err) + } + + err = s.Channel.RemoveChannels(data) + if err != nil { + return fmt.Errorf("删除通道失败: %w", err) + } + + return nil +} diff --git a/web/web.go b/web/web.go index d6ddc6f..dae2abc 100644 --- a/web/web.go +++ b/web/web.go @@ -1,166 +1,89 @@ package web import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/logger" - "github.com/gofiber/fiber/v2/middleware/recover" - "github.com/gofiber/fiber/v2/middleware/requestid" - "github.com/google/uuid" - "github.com/jxskiss/base62" + "context" + "fmt" "log/slog" - "net/http" _ "net/http/pprof" - "platform/web/auth" - g "platform/web/globals" - q "platform/web/queries" - "runtime" - "strings" - "time" + "platform/web/events" + base "platform/web/globals" + "platform/web/tasks" + + "github.com/gofiber/fiber/v2" + "github.com/hibiken/asynq" + "golang.org/x/sync/errgroup" ) -// region web +func RunApp(pCtx context.Context) error { + g, ctx := errgroup.WithContext(pCtx) -type Config struct { - Listen string -} - -type Server struct { - config *Config - fiber *fiber.App -} - -func New(config *Config) (*Server, error) { - _config := config - if config == nil { - _config = &Config{} + // 初始化依赖 + err := base.Init(ctx) + if err != nil { + return fmt.Errorf("初始化依赖失败: %w", err) } - return &Server{ - config: _config, - }, nil + // 运行服务 + g.Go(func() error { + return RunWeb(ctx) + }) + + g.Go(func() error { + return RunTask(ctx) + }) + + return g.Wait() } -func (s *Server) Run() error { +func RunWeb(ctx context.Context) error { - // inits - g.Init() - q.SetDefault(g.DB) - - // config - s.fiber = fiber.New(fiber.Config{ + fiber := fiber.New(fiber.Config{ ProxyHeader: fiber.HeaderXForwardedFor, ErrorHandler: ErrorHandler, }) - // middlewares - s.fiber.Use(newRecover()) - s.fiber.Use(newRequestId()) - s.fiber.Use(newLogger()) + ApplyMiddlewares(fiber) + ApplyRouters(fiber) - // routes - ApplyRouters(s.fiber) - - // pprof + // 停止服务 go func() { - runtime.SetBlockProfileRate(1) - err := http.ListenAndServe(":6060", nil) + <-ctx.Done() + err := fiber.Shutdown() if err != nil { - slog.Error("pprof 服务错误", slog.Any("err", err)) + slog.Error("服务停止失败", "error", err) } }() - // listen - slog.Info("服务开始监听 :8080") - err := s.fiber.Listen("0.0.0.0:8080") + // 启动服务 + slog.Info("web 服务开始监听 :8080") + err := fiber.Listen("0.0.0.0:8080") if err != nil { - slog.Error("Failed to start server", slog.Any("err", err)) + return fmt.Errorf("web 服务监听失败: %w", err) } - slog.Info("服务已停止") + slog.Info("web 服务已停止") return nil } -func (s *Server) Stop() { - err := g.ExitRedis() +func RunTask(ctx context.Context) error { + + var server = asynq.NewServerFromRedisClient(base.Redis, asynq.Config{}) + + var mux = asynq.NewServeMux() + mux.HandleFunc(events.RemoveChannel, tasks.HandleRemoveChannel) + mux.HandleFunc(events.CancelTrade, tasks.HandleCancelTrade) + + // 停止服务 + go func() { + <-ctx.Done() + server.Shutdown() + }() + + // 启动服务 + err := server.Run(mux) if err != nil { - slog.Error("Failed to close Redis connection", slog.Any("err", err)) + return fmt.Errorf("任务服务运行失败: %w", err) } - err = g.ExitOrm() - if err != nil { - slog.Error("Failed to close database connection", slog.Any("err", err)) - } - - err = s.fiber.Shutdown() - if err != nil { - slog.Error("Failed to shutdown server", slog.Any("err", err)) - } + return nil } - -// endregion - -// region middlewares - -func newRequestId() fiber.Handler { - return requestid.New(requestid.Config{ - Generator: func() string { - binary, _ := uuid.New().MarshalBinary() - return base62.EncodeToString(binary) - }, - }) -} - -func newLogger() fiber.Handler { - return logger.New(logger.Config{ - DisableColors: true, - Format: "🚀 ${time} | ${locals:authtype} ${locals:authid} | ${method} ${path} | ${status} | ${latency} | ${error}\n", - TimeFormat: "2006-01-02 15:04:05", - TimeZone: "Asia/Shanghai", - Next: func(c *fiber.Ctx) bool { - authCtx, ok := c.Locals("auth").(*auth.Context) - if ok { - c.Locals("authtype", authCtx.Payload.Type.ToStr()) - c.Locals("authid", authCtx.Payload.Id) - } else { - c.Locals("authtype", auth.PayloadNone.ToStr()) - c.Locals("authid", 0) - } - return false - }, - Done: func(c *fiber.Ctx, logBytes []byte) { - var logStr = strings.TrimPrefix(string(logBytes), "🚀") - var logVars = strings.Split(logStr, "|") - - var reqTimeStr = strings.TrimSpace(logVars[0]) - reqTime, err := time.ParseInLocation("2006-01-02 15:04:05", reqTimeStr, time.Local) - if err != nil { - slog.Error("时间解析错误", slog.Any("err", err)) - return - } - - var latency = strings.TrimSpace(logVars[4]) - var errStr = strings.TrimSpace(logVars[5]) - - slog.Info("接口请求", - slog.String("identity", c.Locals("authtype").(string)), - slog.Int("visitor", c.Locals("authid").(int)), - slog.String("ip", c.IP()), - slog.String("ua", c.Get("User-Agent")), - slog.String("method", c.Method()), - slog.String("path", c.Path()), - slog.Int("status", c.Response().StatusCode()), - slog.String("error", errStr), - slog.String("latency", latency), - slog.Time("time", reqTime), - ) - }, - }) -} - -func newRecover() fiber.Handler { - return recover.New(recover.Config{ - EnableStackTrace: true, - }) -} - -// endregion