重构代码结构与认证体系,集成异步任务消费者

This commit is contained in:
2025-11-17 18:38:10 +08:00
parent a97c970166
commit a245229bc2
70 changed files with 2000 additions and 2334 deletions

2
.gitignore vendored
View File

@@ -7,7 +7,7 @@
!.env.example !.env.example
bin/ bin/
*.exe *.exe*
*.pem *.pem
*.http *.http

28
.vscode/launch.json vendored
View File

@@ -1,16 +1,16 @@
{ {
// 使用 IntelliSense 了解相关属性。 // 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。 // 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "main", "name": "main",
"type": "go", "type": "go",
"request": "launch", "request": "launch",
"mode": "auto", "mode": "debug",
"program": "${workspaceFolder}/cmd/main", "program": "${workspaceFolder}/cmd/main",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}"
} }
] ]
} }

View File

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

View File

@@ -1,12 +1,13 @@
package main package main
import ( import (
"strings"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
"gorm.io/gen" "gorm.io/gen"
"gorm.io/gen/field" "gorm.io/gen/field"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/schema" "gorm.io/gorm/schema"
"strings"
) )
var g *gen.Generator var g *gen.Generator
@@ -15,7 +16,7 @@ func main() {
// 初始化 // 初始化
db, _ := gorm.Open( db, _ := gorm.Open(
postgres.Open("host=localhost user=test password=test dbname=app port=5432 sslmode=disable TimeZone=Asia/Shanghai"), postgres.Open("host=localhost user=dev password=dev dbname=app port=5432 sslmode=disable TimeZone=Asia/Shanghai"),
&gorm.Config{ &gorm.Config{
NamingStrategy: schema.NamingStrategy{ NamingStrategy: schema.NamingStrategy{
SingularTable: true, SingularTable: true,
@@ -47,11 +48,13 @@ func main() {
return field return field
}), }),
gen.FieldRename("contact_qq", "ContactQQ"), gen.FieldRename("contact_qq", "ContactQQ"),
gen.FieldRename("ua", "UA"),
} }
// 生成模型 // 生成模型
customs := make(map[string]any) customs := make(map[string]any)
// resource
resourceShort := g.GenerateModel("resource_short", common...) resourceShort := g.GenerateModel("resource_short", common...)
customs["resource_short"] = resourceShort customs["resource_short"] = resourceShort
@@ -76,6 +79,7 @@ func main() {
)...) )...)
customs["resource"] = resource customs["resource"] = resource
// trade
trade := g.GenerateModel("trade", common...) trade := g.GenerateModel("trade", common...)
customs["trade"] = trade customs["trade"] = trade
@@ -104,6 +108,7 @@ func main() {
)...) )...)
customs["bill"] = bill customs["bill"] = bill
// proxy
edge := g.GenerateModel("edge", common...) edge := g.GenerateModel("edge", common...)
customs["edge"] = edge customs["edge"] = edge
@@ -117,9 +122,42 @@ func main() {
)...) )...)
customs["proxy"] = proxy 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() tables, _ := db.Migrator().GetTables()
models := make([]interface{}, len(tables)) models := make([]any, len(tables))
for i, name := range tables { for i, name := range tables {
if customs[name] != nil { if customs[name] != nil {
models[i] = customs[name] models[i] = customs[name]

View File

@@ -1,6 +1,8 @@
package main package main
import ( import (
"context"
"fmt"
"log/slog" "log/slog"
"os" "os"
"os/signal" "os/signal"
@@ -11,45 +13,16 @@ import (
) )
func main() { func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
// 退出信号 defer cancel()
shutdown := make(chan os.Signal, 1)
signal.Notify(shutdown, syscall.SIGINT, syscall.SIGTERM)
// 初始化应用 // 初始化应用
env.Init() env.Init()
logs.Init() logs.Init()
// 创建服务 // 创建服务
app, err := web.New(&web.Config{ err := web.RunApp(ctx)
Listen: ":8080",
})
if err != nil { if err != nil {
slog.Error("Failed to create server", slog.Any("err", err)) slog.Error(fmt.Sprintf("%v", 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))
} }
} }

View File

@@ -1,7 +1,6 @@
name: server-dev name: server-dev
services: services:
postgres: postgres:
image: postgres:17 image: postgres:17
environment: environment:
@@ -9,24 +8,19 @@ services:
POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME} POSTGRES_DB: ${DB_NAME}
ports: ports:
- "5432:5432" - "${DB_PORT}:5432"
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
restart: unless-stopped
postgres-migration:
image: postgres:17
environment:
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
ports:
- "5433:5432"
redis: redis:
image: redis:7.4 image: redis:7.4
restart: always
ports: ports:
- "6379:6379" - "${REDIS_PORT}:6379"
volumes:
- redis_data:/data
restart: unless-stopped
volumes: volumes:
postgres_data: postgres_data:
redis_data:

48
go.mod
View File

@@ -1,6 +1,6 @@
module platform module platform
go 1.24.5 go 1.25.3
require ( require (
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.7 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/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.26.0 github.com/go-playground/validator/v10 v10.26.0
github.com/go-redsync/redsync/v4 v4.13.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/google/uuid v1.6.0
github.com/hibiken/asynq v0.25.1 github.com/hibiken/asynq v0.25.1
github.com/jdcloud-api/jdcloud-sdk-go v1.64.0 github.com/jdcloud-api/jdcloud-sdk-go v1.64.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/jxskiss/base62 v1.1.0 github.com/jxskiss/base62 v1.1.0
github.com/lmittmann/tint v1.0.7 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/shopspring/decimal v1.4.0
github.com/smartwalle/alipay/v3 v3.2.25 github.com/smartwalle/alipay/v3 v3.2.25
github.com/wechatpay-apiv3/wechatpay-go v0.2.20 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/driver/postgres v1.5.11
gorm.io/gen v0.3.27 gorm.io/gen v0.3.27
gorm.io/gorm v1.25.12 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 v1.3.8 // indirect
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect
github.com/aliyun/credentials-go v1.4.5 // 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/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clbanning/mxj/v2 v2.7.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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // 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/gofrs/uuid v4.4.0+incompatible // indirect
github.com/gomodule/redigo v2.0.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/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // 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/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // 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/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // 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/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/ncrypto v1.0.4 // indirect
github.com/smartwalle/ngx v1.0.9 // indirect github.com/smartwalle/ngx v1.0.9 // indirect
github.com/smartwalle/nsign v1.0.9 // indirect github.com/smartwalle/nsign v1.0.9 // indirect
github.com/spf13/cast v1.7.0 // 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/tjfoc/gmsm v1.4.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.59.0 // indirect github.com/valyala/fasthttp v1.68.0 // indirect
golang.org/x/mod v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
golang.org/x/net v0.37.0 // indirect go.opentelemetry.io/contrib v1.20.0 // indirect
golang.org/x/sync v0.12.0 // indirect go.opentelemetry.io/otel v1.35.0 // indirect
golang.org/x/sys v0.31.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect
golang.org/x/text v0.23.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/time v0.8.0 // indirect
golang.org/x/tools v0.31.0 // indirect golang.org/x/tools v0.37.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gorm.io/datatypes v1.2.5 // indirect gorm.io/datatypes v1.2.5 // indirect
gorm.io/driver/mysql v1.5.7 // indirect gorm.io/driver/mysql v1.5.7 // indirect

96
go.sum
View File

@@ -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.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
github.com/aliyun/credentials-go v1.4.5 h1:O76WYKgdy1oQYYiJkERjlA2dxGuvLRrzuO2ScrtGWSk= 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/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.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 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 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10 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 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= 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/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/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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -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/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 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= 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 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 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 h1:49X6GJfnbLGaIpBBREM/zA4uIMDXKAh1NDkvQ1EkZKA=
github.com/go-redsync/redsync/v4 v4.13.0/go.mod h1:HMW4Q224GZQz6x1Xc7040Yfgacukdzu7ifTDAKiyErQ= 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.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.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= github.com/gofiber/contrib/otelfiber/v2 v2.2.3 h1:WKW1XezHFAoohGZwnvC0R8TFJcNkabQwB5YIpdKmz00=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= 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 h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 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= 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/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 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc= 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.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 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 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/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-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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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.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.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 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 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= 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= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 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.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg=
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/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo= github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo=
github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo= 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 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 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 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= 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= 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.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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM=
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= 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= 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/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 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 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.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFnok=
github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU= 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 h1:gS8oFn1bHGnyapR2Zb4aqTV6l4kJWgbtqjCq6k1L9DQ=
github.com/wechatpay-apiv3/wechatpay-go v0.2.20/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q= github.com/wechatpay-apiv3/wechatpay-go v0.2.20/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 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.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.30/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= 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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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= 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.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 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.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 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/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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 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.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.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.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/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.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 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.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 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/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-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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.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.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.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.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-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.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.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.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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/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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.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.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 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 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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= 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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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.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.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= 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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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.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.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 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.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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 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/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=

552
pkg/env/env.go vendored
View File

@@ -1,274 +1,54 @@
package env package env
import ( import (
"fmt"
"log/slog" "log/slog"
"os" "os"
"platform/pkg/u"
"slices"
"strconv" "strconv"
"github.com/gofiber/fiber/v2/log" "github.com/gofiber/fiber/v2/log"
"github.com/joho/godotenv" "github.com/joho/godotenv"
) )
// region app
const ( const (
RunModeDev = "debug" RunModeDev = "development"
RunModeProd = "production" RunModeProd = "production"
) )
var ( var (
RunMode = RunModeDev RunMode = RunModeProd
TradeExpire = 30 * 60 // 交易过期时间,单位秒 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" DbHost = "localhost"
DbPort = "5432" DbPort = "5432"
DbName string DbName string
DbUserName string DbUserName string
DbPassword string DbPassword string
)
func loadDb() { RedisHost = "localhost"
_DbHost := os.Getenv("DB_HOST") RedisPort = "6379"
if _DbHost != "" { RedisPassword = ""
DbHost = _DbHost
}
_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" BaiyinAddr = "http://103.139.212.110:9989"
BaiyinTokenUrl string BaiyinTokenUrl string
)
var (
IdenCallbackUrl string IdenCallbackUrl string
IdenAccessKey string IdenAccessKey string
IdenSecretKey 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 AlipayAppId string
AlipayAppPrivateKey string AlipayAppPrivateKey string
AlipayPublicKey string AlipayPublicKey string
AlipayApiCert string AlipayApiCert string
AlipayProduction = false 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 WechatPayAppId string
WechatPayMchId string WechatPayMchId string
WechatPayMchPrivateKeySerial string WechatPayMchPrivateKeySerial string
@@ -277,185 +57,21 @@ var (
WechatPayPublicKey string WechatPayPublicKey string
WechatPayApiCert string WechatPayApiCert string
WechatPayCallbackUrl 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 AliyunAccessKey string
AliyunAccessKeySecret string AliyunAccessKeySecret string
AliyunSmsSignature string AliyunSmsSignature string
AliyunSmsTemplateLogin 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 SftPayEnable = false
SftPayAppId string SftPayAppId string
SftPayRouteId string SftPayRouteId string
SftPayAppPrivateKey string SftPayAppPrivateKey string
SftPayPublicKey string SftPayPublicKey string
SftReturnUrl *string SftReturnUrl string
SftNotifyUrl *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() { func Init() {
err := godotenv.Load() err := godotenv.Load()
if err != nil { if err != nil {
@@ -464,15 +80,129 @@ func Init() {
log.Debug("✔ 加载本地环境变量") log.Debug("✔ 加载本地环境变量")
} }
loadApp() // 收集所有错误
loadAuth() var errs []error
loadDb()
loadRedis() errs = append(errs, parse(&RunMode, "RUN_MODE", true, &[]string{RunModeDev, RunModeProd}))
loadLog() errs = append(errs, parse(&LogLevel, "LOG_LEVEL", true, nil, func(value string) (slog.Level, error) {
loadDebug() switch value {
loadRemote() case "debug":
loadAlipay() return slog.LevelDebug, nil
loadWechatPay() case "info":
loadAliyun() return slog.LevelInfo, nil
loadSftPay() 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
} }

View File

@@ -1,10 +1,11 @@
package logs package logs
import ( import (
"github.com/lmittmann/tint"
"log/slog" "log/slog"
"os" "os"
"platform/pkg/env" "platform/pkg/env"
"github.com/lmittmann/tint"
) )
func Init() { func Init() {
@@ -14,7 +15,7 @@ func Init() {
var handler slog.Handler var handler slog.Handler
switch env.RunMode { switch env.RunMode {
case "debug": case env.RunModeDev:
handler = tint.NewHandler(writer, &tint.Options{ handler = tint.NewHandler(writer, &tint.Options{
Level: env.LogLevel, Level: env.LogLevel,
TimeFormat: timeFormat, TimeFormat: timeFormat,
@@ -26,7 +27,7 @@ func Init() {
return attr return attr
}, },
}) })
case "production": case env.RunModeProd:
handler = slog.NewJSONHandler(writer, &slog.HandlerOptions{ handler = slog.NewJSONHandler(writer, &slog.HandlerOptions{
Level: env.LogLevel, Level: env.LogLevel,
ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {

View File

@@ -1,12 +1,31 @@
package u package u
import "time" import (
"fmt"
"time"
)
// P 是一个工具函数,用于在表达式内原地创建一个指针 // P 是一个工具函数,用于在表达式内原地创建一个指针
func P[T any](v T) *T { func P[T any](v T) *T {
return &v 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 { func Today() time.Time {
var now = time.Now() var now = time.Now()
return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) 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() 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 { func Or[T any](v *T, or T) T {
if v == nil { if v == nil {
return or return or
@@ -36,3 +47,17 @@ func Or[T any](v *T, or T) T {
return *v 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
}

View File

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

View File

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

View File

@@ -5,32 +5,32 @@
-- logs_request -- logs_request
drop table if exists logs_request cascade; drop table if exists logs_request cascade;
create table logs_request ( create table logs_request (
id serial primary key, id serial primary key,
identity int not null, ip varchar(45) not null,
visitor int, ua varchar(255) not null,
ip varchar(45) not null, user_id int,
ua varchar(255), client_id int,
method varchar(10) not null, method varchar(10) not null,
path varchar(255) not null, path varchar(255) not null,
status int not null, status int not null,
error text, error text,
time timestamp not null, time timestamp not null,
latency varchar(255) not null latency varchar(255) not null
); );
create index logs_request_identity_index on logs_request (identity); create index logs_request_user_id_index on logs_request (user_id);
create index logs_request_visitor_index on logs_request (visitor); create index logs_request_client_id_index on logs_request (client_id);
-- logs_access表字段注释 -- logs_access表字段注释
comment on table logs_request is '访问日志表'; comment on table logs_request is '访问日志表';
comment on column logs_request.id is '访问日志ID'; 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.ip is 'IP地址';
comment on column logs_request.ua is '用户代理'; 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.method is '请求方法';
comment on column logs_request.path is '请求路径'; comment on column logs_request.path is '请求路径';
comment on column logs_request.status is '响应状态码'; comment on column logs_request.status is '响应状态码';
@@ -131,8 +131,8 @@ create table admin (
last_login timestamp, last_login timestamp,
last_login_host varchar(45), last_login_host varchar(45),
last_login_agent varchar(255), last_login_agent varchar(255),
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
); );
create index admin_status_index on admin (status); create index admin_status_index on admin (status);
@@ -161,8 +161,8 @@ create table admin_role (
id serial primary key, id serial primary key,
name varchar(255) not null unique, name varchar(255) not null unique,
description varchar(255), description varchar(255),
active bool default true, active bool default true,
sort int default 0, sort int default 0,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
@@ -190,8 +190,8 @@ create table announcement (
pin bool not null default false, pin bool not null default false,
status int not null default 1, status int not null default 1,
sort int not null default 0, sort int not null default 0,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
); );
create index announcement_status_index on announcement (status); 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; drop table if exists "user" cascade;
create table "user" ( create table "user" (
id serial primary key, id serial primary key,
admin_id int references admin (id) -- admin_id int,
on update cascade --
on delete set null,
phone varchar(255) not null unique, phone varchar(255) not null unique,
username varchar(255), username varchar(255),
email varchar(255), email varchar(255),
@@ -239,8 +237,8 @@ create table "user" (
last_login timestamp, last_login timestamp,
last_login_host varchar(45), last_login_host varchar(45),
last_login_agent varchar(255), last_login_agent varchar(255),
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
); );
create index user_admin_id_index on "user" (admin_id); create index user_admin_id_index on "user" (admin_id);
@@ -278,8 +276,8 @@ create table user_role (
id serial primary key, id serial primary key,
name varchar(255) not null unique, name varchar(255) not null unique,
description varchar(255), description varchar(255),
active bool default true, active bool default true,
sort int default 0, sort int default 0,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
@@ -305,21 +303,18 @@ comment on column user_role.deleted_at is '删除时间';
drop table if exists client cascade; drop table if exists client cascade;
create table client ( create table client (
id serial primary key, id serial primary key,
client_id varchar(255) not null unique, client_id varchar(255) not null unique,
client_secret varchar(255) not null, client_secret varchar(255) not null,
redirect_uri varchar(255), redirect_uri varchar(255),
grant_code bool not null default false, spec int not null,
grant_client bool not null default false, name varchar(255) not null,
grant_refresh bool not null default false, icon varchar(255),
grant_password bool not null default false, status int not null default 1,
spec int not null, type int not null default 0,
name varchar(255) not null, created_at timestamp default current_timestamp,
icon varchar(255), updated_at timestamp default current_timestamp,
status int not null default 1, deleted_at timestamp
created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp,
deleted_at timestamp
); );
create index client_client_id_index on client (client_id); create index client_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_id is 'OAuth2客户端标识符';
comment on column client.client_secret is 'OAuth2客户端密钥'; comment on column client.client_secret is 'OAuth2客户端密钥';
comment on column client.redirect_uri is 'OAuth2 重定向URI'; comment on column client.redirect_uri is 'OAuth2 重定向URI';
comment on column client.grant_code is '允许授权码授予'; comment on column client.spec is '安全规范1-native2-browser3-web4-api';
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-native2-browser3-web4-trusted';
comment on column client.name is '名称'; comment on column client.name is '名称';
comment on column client.icon is '图标URL'; comment on column client.icon is '图标URL';
comment on column client.status is '状态0-禁用1-正常'; 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.created_at is '创建时间';
comment on column client.updated_at is '更新时间'; comment on column client.updated_at is '更新时间';
comment on column client.deleted_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; drop table if exists session cascade;
create table session ( create table session (
id serial primary key, id serial primary key,
user_id int references "user" (id) user_id int,
on update cascade admin_id int,
on delete cascade, client_id int,
client_id int references client (id)
on update cascade
on delete cascade,
ip varchar(45), ip varchar(45),
ua varchar(255), ua varchar(255),
grant_type varchar(255) not null default 0,
access_token varchar(255) not null unique, access_token varchar(255) not null unique,
access_token_expires timestamp not null, access_token_expires timestamp not null,
refresh_token varchar(255) unique, refresh_token varchar(255) unique,
refresh_token_expires timestamp, refresh_token_expires timestamp,
scopes varchar(255), scopes varchar(255),
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
); );
create index session_user_id_index on session (user_id); 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_client_id_index on session (client_id);
create index session_access_token_index on session (access_token); create index session_access_token_index on session (access_token);
create index session_refresh_token_index on session (refresh_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 table session is '会话表';
comment on column session.id is '会话ID'; comment on column session.id is '会话ID';
comment on column session.user_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.client_id is '客户端ID';
comment on column session.ip is 'IP地址'; comment on column session.ip is 'IP地址';
comment on column session.ua is '用户代理'; 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 is '访问令牌';
comment on column session.access_token_expires is '访问令牌过期时间'; comment on column session.access_token_expires is '访问令牌过期时间';
comment on column session.refresh_token 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; drop table if exists permission cascade;
create table permission ( create table permission (
id serial primary key, id serial primary key,
parent_id int references permission (id) parent_id int,
on update cascade
on delete cascade,
name varchar(255) not null unique, name varchar(255) not null unique,
description varchar(255), description varchar(255),
created_at timestamp default current_timestamp, 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; drop table if exists user_role_link cascade;
create table user_role_link ( create table user_role_link (
id serial primary key, id serial primary key,
user_id int not null references "user" (id) user_id int not null,
on update cascade role_id int not null,
on delete cascade,
role_id int not null references user_role (id)
on update cascade
on delete cascade,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
@@ -455,12 +438,8 @@ comment on column user_role_link.deleted_at is '删除时间';
drop table if exists admin_role_link cascade; drop table if exists admin_role_link cascade;
create table admin_role_link ( create table admin_role_link (
id serial primary key, id serial primary key,
admin_id int not null references admin (id) admin_id int not null,
on update cascade role_id int not null,
on delete cascade,
role_id int not null references admin_role (id)
on update cascade
on delete cascade,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
@@ -482,12 +461,8 @@ comment on column admin_role_link.deleted_at is '删除时间';
drop table if exists user_role_permission_link cascade; drop table if exists user_role_permission_link cascade;
create table user_role_permission_link ( create table user_role_permission_link (
id serial primary key, id serial primary key,
role_id int not null references user_role (id) role_id int not null,
on update cascade permission_id int not null,
on delete cascade,
permission_id int not null references permission (id)
on update cascade
on delete cascade,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
@@ -509,12 +484,8 @@ comment on column user_role_permission_link.deleted_at is '删除时间';
drop table if exists admin_role_permission_link cascade; drop table if exists admin_role_permission_link cascade;
create table admin_role_permission_link ( create table admin_role_permission_link (
id serial primary key, id serial primary key,
role_id int not null references admin_role (id) role_id int not null,
on update cascade permission_id int not null,
on delete cascade,
permission_id int not null references permission (id)
on update cascade
on delete cascade,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
@@ -536,12 +507,8 @@ comment on column admin_role_permission_link.deleted_at is '删除时间';
drop table if exists client_permission_link cascade; drop table if exists client_permission_link cascade;
create table client_permission_link ( create table client_permission_link (
id serial primary key, id serial primary key,
client_id int not null references client (id) client_id int not null,
on update cascade permission_id int not null,
on delete cascade,
permission_id int not null references permission (id)
on update cascade
on delete cascade,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
@@ -602,9 +569,7 @@ comment on column proxy.deleted_at is '删除时间';
drop table if exists edge cascade; drop table if exists edge cascade;
create table edge ( create table edge (
id serial primary key, id serial primary key,
proxy_id int references proxy (id) proxy_id int,
on update cascade
on delete cascade,
type int not null, type int not null,
version int not null, version int not null,
name varchar(255) not null unique, name varchar(255) not null unique,
@@ -614,10 +579,10 @@ create table edge (
city varchar(255) not null, city varchar(255) not null,
proxy_port int, proxy_port int,
status int not null default 0, status int not null default 0,
rtt int default 0, rtt int default 0,
loss int default 0, loss int default 0,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
); );
create index edge_proxy_id_index on edge (proxy_id); 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; drop table if exists whitelist cascade;
create table whitelist ( create table whitelist (
id serial primary key, id serial primary key,
user_id int not null references "user" (id) user_id int not null,
on update cascade
on delete cascade,
host varchar(45) not null, host varchar(45) not null,
remark varchar(255), remark varchar(255),
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
@@ -677,18 +640,10 @@ comment on column whitelist.deleted_at is '删除时间';
drop table if exists channel cascade; drop table if exists channel cascade;
create table channel ( create table channel (
id serial primary key, id serial primary key,
user_id int not null references "user" (id) user_id int not null,
on update cascade proxy_id int not null,
on delete cascade, edge_id int,
proxy_id int not null references proxy (id) -- resource_id int not null,
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,
proxy_host varchar(255) not null default '', proxy_host varchar(255) not null default '',
proxy_port int not null, proxy_port int not null,
edge_host varchar(255), edge_host varchar(255),
@@ -699,8 +654,8 @@ create table channel (
username varchar(255), username varchar(255),
password varchar(255), password varchar(255),
expiration timestamp not null, expiration timestamp not null,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
); );
create index channel_user_id_index on channel (user_id); create index channel_user_id_index on channel (user_id);
@@ -748,8 +703,8 @@ create table product (
description varchar(255), description varchar(255),
sort int not null default 0, sort int not null default 0,
status int not null default 1, status int not null default 1,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
); );
create index product_deleted_at_index on product (deleted_at); 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; drop table if exists resource cascade;
create table resource ( create table resource (
id serial primary key, id serial primary key,
user_id int not null references "user" (id) user_id int not null,
on update cascade
on delete cascade,
resource_no varchar(255) unique, resource_no varchar(255) unique,
active bool not null default true, active bool not null default true,
type int not null, type int not null,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
); );
create index resource_user_id_index on resource (user_id); create index resource_user_id_index on resource (user_id);
@@ -801,9 +754,7 @@ comment on column resource.deleted_at is '删除时间';
drop table if exists resource_short cascade; drop table if exists resource_short cascade;
create table resource_short ( create table resource_short (
id serial primary key, id serial primary key,
resource_id int not null references resource (id) resource_id int not null,
on update cascade
on delete cascade,
type int not null, type int not null,
live int not null, live int not null,
expire timestamp, expire timestamp,
@@ -832,9 +783,7 @@ comment on column resource_short.daily_last is '今日最后使用时间';
drop table if exists resource_long cascade; drop table if exists resource_long cascade;
create table resource_long ( create table resource_long (
id serial primary key, id serial primary key,
resource_id int not null references resource (id) resource_id int not null,
on update cascade
on delete cascade,
type int not null, type int not null,
live int not null, live int not null,
expire timestamp, expire timestamp,
@@ -869,9 +818,7 @@ comment on column resource_long.daily_last is '今日最后使用时间';
drop table if exists trade cascade; drop table if exists trade cascade;
create table trade ( create table trade (
id serial primary key, id serial primary key,
user_id int not null references "user" (id) user_id int not null,
on update cascade
on delete cascade,
inner_no varchar(255) not null unique, inner_no varchar(255) not null unique,
outer_no varchar(255), outer_no varchar(255),
type int not null, type int not null,
@@ -887,8 +834,8 @@ create table trade (
payment_url text, payment_url text,
completed_at timestamp, completed_at timestamp,
canceled_at timestamp, canceled_at timestamp,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
); );
create index trade_user_id_index on trade (user_id); 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; drop table if exists refund cascade;
create table refund ( create table refund (
id serial primary key, id serial primary key,
trade_id int not null references trade (id) trade_id int not null,
on update cascade product_id int,
on delete cascade,
product_id int references product (id) --
on update cascade --
on delete set null,
amount decimal(12, 2) not null default 0, amount decimal(12, 2) not null default 0,
reason varchar(255), reason varchar(255),
status int not null default 0, status int not null default 0,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
); );
create index refund_trade_id_index on refund (trade_id); 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; drop table if exists bill cascade;
create table bill ( create table bill (
id serial primary key, id serial primary key,
user_id int not null references "user" (id) user_id int not null,
on update cascade trade_id int,
on delete cascade, resource_id int,
trade_id int references trade (id) -- refund_id int,
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,
bill_no varchar(255) not null unique, bill_no varchar(255) not null unique,
info varchar(255), info varchar(255),
type int not null, type int not null,
amount decimal(12, 2) not null default 0, amount decimal(12, 2) not null default 0,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
); );
create index bill_user_id_index on bill (user_id); 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; drop table if exists coupon cascade;
create table coupon ( create table coupon (
id serial primary key, id serial primary key,
user_id int references "user" (id) user_id int,
on update cascade
on delete cascade,
code varchar(255) not null unique, code varchar(255) not null unique,
remark varchar(255), remark varchar(255),
amount decimal(12, 2) not null default 0, amount decimal(12, 2) not null default 0,
min_amount decimal(12, 2) not null default 0, min_amount decimal(12, 2) not null default 0,
status int not null default 0, status int not null default 0,
expire_at timestamp, expire_at timestamp,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
); );
create index coupon_user_id_index on coupon (user_id); create index coupon_user_id_index on coupon (user_id);
@@ -1036,3 +969,118 @@ comment on column coupon.updated_at is '更新时间';
comment on column coupon.deleted_at is '删除时间'; comment on column coupon.deleted_at is '删除时间';
-- endregion -- 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

View File

@@ -4,112 +4,101 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt"
"log/slog" "log/slog"
"platform/web/core"
client2 "platform/web/domains/client" client2 "platform/web/domains/client"
m "platform/web/models"
q "platform/web/queries" q "platform/web/queries"
"slices" s "platform/web/services"
"strings" "strings"
"time"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
type ProtectBuilder struct { func Authenticate() fiber.Handler {
c *fiber.Ctx return func(ctx *fiber.Ctx) error {
types []PayloadType header := ctx.Get(fiber.HeaderAuthorization)
scopes []string 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 { func authHeader(ctx context.Context, header string) (*AuthCtx, error) {
return &ProtectBuilder{c, []PayloadType{}, []string{}} 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, " ") var split = strings.Split(header, " ")
if len(split) != 2 { if len(split) != 2 {
slog.Debug("Authorization 头格式不正确") slog.Debug("Authorization 头格式不正确")
return nil, ErrUnauthorize return nil, ErrAuthenticateUnauthorize
} }
var token = strings.TrimSpace(split[1]) var token = strings.TrimSpace(split[1])
if token == "" { if token == "" {
slog.Debug("提供的令牌为空") slog.Debug("提供的令牌为空")
return nil, ErrUnauthorize return nil, ErrAuthenticateUnauthorize
} }
var auth *Context var authCtx *AuthCtx
var err error var err error
switch split[0] { switch split[0] {
case "Bearer": case "Bearer":
auth, err = authBearer(c.Context(), token) authCtx, err = authBearer(ctx, token)
if err != nil { if err != nil {
slog.Debug("Bearer 认证失败", "err", err) slog.Debug("Bearer 认证失败", "err", err)
return nil, ErrUnauthorize return nil, ErrAuthenticateUnauthorize
} }
case "Basic": case "Basic":
if !slices.Contains(types, PayloadInternalServer) { authCtx, err = authBasic(ctx, token)
slog.Debug("禁止使用 Basic 认证方式")
return nil, ErrUnauthorize
}
auth, err = authBasic(c.Context(), token)
if err != nil { if err != nil {
slog.Debug("Basic 认证失败", "err", err) slog.Debug("Basic 认证失败", "err", err)
return nil, ErrUnauthorize return nil, ErrAuthenticateUnauthorize
} }
default: default:
slog.Debug("无效的认证方式", "method", split[0]) slog.Debug("无效的认证方式", "method", split[0])
return nil, ErrUnauthorize return nil, ErrAuthenticateUnauthorize
} }
// 检查权限 return authCtx, err
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
} }
func Locals(c *fiber.Ctx, auth *Context) { func authBearer(_ context.Context, token string) (*AuthCtx, error) {
c.Locals("auth", auth) session, err := FindSession(token, time.Now())
}
func authBearer(ctx context.Context, token string) (*Context, error) {
auth, err := FindSession(ctx, token)
if err != nil { if err != nil {
slog.Debug(err.Error()) slog.Debug("Bearer 认证失败", "err", err)
return nil, 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 认证信息 // 解析 Basic 认证信息
var base, err = base64.RawURLEncoding.DecodeString(token) var base, err = base64.RawURLEncoding.DecodeString(token)
@@ -125,14 +114,23 @@ func authBasic(_ context.Context, token string) (*Context, error) {
return nil, errors.New("令牌格式错误,必须是 <client_id>:<client_secret> 格式") return nil, errors.New("令牌格式错误,必须是 <client_id>:<client_secret> 格式")
} }
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. client, err := q.Client.
Where( Where(
q.Client.ClientID.Eq(clientID), q.Client.ClientID.Eq(clientId),
q.Client.Spec.In(int32(client2.SpecWeb), int32(client2.SpecTrusted)),
q.Client.GrantClient.Is(true),
q.Client.Status.Eq(1)). q.Client.Status.Eq(1)).
Take() Take()
if err != nil { if err != nil {
@@ -140,33 +138,57 @@ func authBasic(_ context.Context, token string) (*Context, error) {
} }
// 检查客户端密钥 // 检查客户端密钥
var clientSecret = split[1] spec := client2.Spec(client.Spec)
if bcrypt.CompareHashAndPassword([]byte(client.ClientSecret), []byte(clientSecret)) != nil { if spec == client2.SpecWeb || spec == client2.SpecApi {
return nil, errors.New("客户端密钥错误") if bcrypt.CompareHashAndPassword([]byte(client.ClientSecret), []byte(clientSecret)) != nil {
return nil, errors.New("客户端密钥错误")
}
} }
// todo 查询客户端关联权限 // todo 查询客户端关联权限
// 组织授权信息(一次性请求) // 组织授权信息(一次性请求)
return &Context{ return client, nil
Payload: Payload{
Id: client.ID,
Type: PayloadTypeFromClientSpec(client2.Spec(client.Spec)),
Name: client.Name,
Avatar: client.Icon,
},
Permissions: nil,
Metadata: nil,
}, 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 ( func authUserByEmail(tx *q.Query, username, code string) (*m.User, error) {
ErrUnauthorize = AuthenticationErr("令牌无效") return nil, core.NewServErr("邮箱登录不可用")
ErrForbidden = AuthenticationErr("没有权限") }
)
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
}

View File

@@ -1,5 +1,28 @@
package auth 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 type GrantType string
const ( const (
@@ -17,36 +40,352 @@ const (
GrantPasswordEmail = PasswordGrantType("email_code") // 邮箱验证码 GrantPasswordEmail = PasswordGrantType("email_code") // 邮箱验证码
) )
func Token(grant GrantType) error { type TokenReq struct {
return nil 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 { func Revoke() error {
@@ -56,3 +395,12 @@ func Revoke() error {
func Introspect() error { func Introspect() error {
return nil 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"`
}

99
web/auth/check.go Normal file
View File

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

View File

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

24
web/auth/errors.go Normal file
View File

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

View File

@@ -2,160 +2,36 @@ package auth
import ( import (
"context" "context"
"encoding/json"
"errors"
"fmt" "fmt"
"github.com/google/uuid"
"github.com/redis/go-redis/v9"
"platform/pkg/env"
g "platform/web/globals" g "platform/web/globals"
"platform/web/globals/orm"
m "platform/web/models"
q "platform/web/queries"
"time" "time"
"gorm.io/gen/field"
) )
type Session struct { func FindSession(accessToken string, now time.Time) (*m.Session, error) {
// 认证主体 return q.Session.
Payload *Payload Preload(field.Associations).
// 令牌信息 Where(
TokenDetails *TokenDetails q.Session.AccessToken.Eq(accessToken),
q.Session.AccessTokenExpires.Gt(orm.LocalDateTime(now)),
).First()
} }
func FindSession(ctx context.Context, token string) (*Context, error) { func FindSessionByRefresh(refreshToken string, now time.Time) (*m.Session, error) {
return q.Session.
// 读取认证数据 Preload(field.Associations).
authJSON, err := g.Redis.Get(ctx, accessKey(token)).Result() Where(
if err != nil { q.Session.RefreshToken.Eq(refreshToken),
if errors.Is(err, redis.Nil) { q.Session.RefreshTokenExpires.Gt(orm.LocalDateTime(now)),
return nil, errors.New("invalid_token") ).First()
}
return nil, err
}
// 反序列化
auth := new(Context)
if err := json.Unmarshal([]byte(authJSON), auth); err != nil {
return nil, err
}
return auth, nil
} }
func CreateSession(ctx context.Context, authCtx *Context, remember bool) (*TokenDetails, error) { func SaveSession(session *m.Session) error {
var now = time.Now() return q.Session.Save(session)
// 生成令牌组
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 RemoveSession(ctx context.Context, accessToken string, refreshToken string) error { 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 return nil
} }
// 生成一个新的令牌
func genToken() string {
return uuid.NewString()
}
// 令牌键的格式为 "session:<token>" // 令牌键的格式为 "session:<token>"
func accessKey(token string) string { func accessKey(token string) string {
return fmt.Sprintf("session:%s", token) return fmt.Sprintf("session:%s", token)
@@ -177,32 +48,3 @@ func accessKey(token string) string {
func refreshKey(token string) string { func refreshKey(token string) string {
return fmt.Sprintf("session:refresh:%s", token) 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("无效的刷新令牌")
)

View File

@@ -60,7 +60,7 @@ type Err struct {
func (e *Err) Error() string { func (e *Err) Error() string {
if e.err != nil { if e.err != nil {
return e.msg + "" + e.err.Error() return e.msg + ": " + e.err.Error()
} }
return e.msg return e.msg
} }

View File

@@ -6,5 +6,12 @@ const (
SpecNative Spec = iota + 1 // 原生客户端 SpecNative Spec = iota + 1 // 原生客户端
SpecBrowser // 浏览器客户端 SpecBrowser // 浏览器客户端
SpecWeb // Web 服务 SpecWeb // Web 服务
SpecTrusted // 可信服务 SpecApi // Api 服务
)
type Type int32
const (
TypeNormal Type = iota // 普通客户端
TypeInternal // 内部客户端
) )

View File

@@ -16,7 +16,7 @@ func ErrorHandler(c *fiber.Ctx, err error) error {
var message = "服务器异常" var message = "服务器异常"
var fiberErr *fiber.Error var fiberErr *fiber.Error
var authErr auth.AuthenticationErr var authErr auth.AuthErr
var bizErr *core.BizErr var bizErr *core.BizErr
var servErr *core.ServErr var servErr *core.ServErr
@@ -30,9 +30,9 @@ func ErrorHandler(c *fiber.Ctx, err error) error {
// 认证授权错误 // 认证授权错误
case errors.As(err, &authErr): case errors.As(err, &authErr):
switch { switch {
case errors.Is(err, auth.ErrUnauthorize): case errors.Is(err, auth.ErrAuthenticateUnauthorize):
code = fiber.StatusUnauthorized code = fiber.StatusUnauthorized
case errors.Is(err, auth.ErrForbidden): case errors.Is(err, auth.ErrAuthenticateForbidden):
code = fiber.StatusForbidden code = fiber.StatusForbidden
default: default:
code = fiber.StatusBadRequest code = fiber.StatusBadRequest

View File

@@ -1,4 +1,4 @@
package tasks package events
import ( import (
"encoding/json" "encoding/json"

View File

@@ -1,4 +1,4 @@
package tasks package events
import ( import (
"encoding/json" "encoding/json"

View File

@@ -1,10 +1,11 @@
package tasks package events
import ( import (
"encoding/json" "encoding/json"
"github.com/hibiken/asynq"
"log/slog" "log/slog"
trade2 "platform/web/domains/trade" trade2 "platform/web/domains/trade"
"github.com/hibiken/asynq"
) )
const CancelTrade = "trade:update" const CancelTrade = "trade:update"

View File

@@ -1,6 +1,7 @@
package globals package globals
import ( import (
"fmt"
"platform/pkg/env" "platform/pkg/env"
"github.com/smartwalle/alipay/v3" "github.com/smartwalle/alipay/v3"
@@ -8,25 +9,26 @@ import (
var Alipay *alipay.Client var Alipay *alipay.Client
func initAlipay() { func initAlipay() error {
var client, err = alipay.New( var client, err = alipay.New(
env.AlipayAppId, env.AlipayAppId,
env.AlipayAppPrivateKey, env.AlipayAppPrivateKey,
env.AlipayProduction, env.AlipayProduction,
) )
if err != nil { if err != nil {
panic("初始化支付宝客户端失败: " + err.Error()) return fmt.Errorf("初始化支付宝客户端失败: %w", err)
} }
err = client.LoadAliPayPublicKey(env.AlipayPublicKey) err = client.LoadAliPayPublicKey(env.AlipayPublicKey)
if err != nil { if err != nil {
panic("加载支付宝公钥失败: " + err.Error()) return fmt.Errorf("加载支付宝公钥失败: %w", err)
} }
err = client.SetEncryptKey(env.AlipayApiCert) err = client.SetEncryptKey(env.AlipayApiCert)
if err != nil { if err != nil {
panic("设置支付宝加密密钥失败: " + err.Error()) return fmt.Errorf("设置支付宝加密证书失败: %w", err)
} }
Alipay = client Alipay = client
return nil
} }

View File

@@ -1,6 +1,7 @@
package globals package globals
import ( import (
"fmt"
"platform/pkg/env" "platform/pkg/env"
"platform/pkg/u" "platform/pkg/u"
@@ -14,17 +15,18 @@ type aliyunClient struct {
Sms *sms.Client Sms *sms.Client
} }
func initAliyun() { func initAliyun() error {
client, err := sms.NewClient(&openapi.Config{ client, err := sms.NewClient(&openapi.Config{
AccessKeyId: &env.AliyunAccessKey, AccessKeyId: &env.AliyunAccessKey,
AccessKeySecret: &env.AliyunAccessKeySecret, AccessKeySecret: &env.AliyunAccessKeySecret,
Endpoint: u.P("dysmsapi.aliyuncs.com"), Endpoint: u.P("dysmsapi.aliyuncs.com"),
}) })
if err != nil { if err != nil {
panic(err) return fmt.Errorf("初始化阿里云客户端失败: %w", err)
} }
Aliyun = &aliyunClient{ Aliyun = &aliyunClient{
Sms: client, Sms: client,
} }
return nil
} }

View File

@@ -35,10 +35,11 @@ type cloud struct {
var Cloud CloudClient var Cloud CloudClient
func initBaiyin() { func initBaiyin() error {
Cloud = &cloud{ Cloud = &cloud{
url: env.BaiyinAddr, url: env.BaiyinAddr,
} }
return nil
} }
type AutoConfig struct { type AutoConfig struct {

View File

@@ -1,14 +1,38 @@
package globals package globals
func Init() { import (
initBaiyin() "context"
initAlipay() "platform/pkg/u"
initWechatPay() )
initAliyun()
initValidator() func Init(ctx context.Context) error {
initRedis() errs := make([]error, 0)
initOrm()
initProxy() errs = append(errs, initBaiyin())
initAsynq() errs = append(errs, initAlipay())
initSft() 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)
} }

View File

@@ -1,17 +1,20 @@
package globals package globals
import ( import (
"database/sql"
"fmt" "fmt"
"platform/pkg/env"
"platform/web/queries"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/schema" "gorm.io/gorm/schema"
"log/slog"
"platform/pkg/env"
) )
var DB *gorm.DB var DB *gorm.DB
var Conn *sql.DB
func initOrm() { func initOrm() error {
// 连接数据库 // 连接数据库
dsn := fmt.Sprintf( dsn := fmt.Sprintf(
@@ -25,27 +28,29 @@ func initOrm() {
}, },
}) })
if err != nil { if err != nil {
slog.Error("gorm 初始化数据库失败:", slog.Any("err", err)) return fmt.Errorf("连接数据库失败: %w", err)
panic(err)
} }
// 连接池 // 连接池
conn, err := db.DB() conn, err := db.DB()
if err != nil { if err != nil {
slog.Error("gorm 初始化数据库失败:", slog.Any("err", err)) return fmt.Errorf("配置连接池失败: %w", err)
panic(err)
} }
conn.SetMaxIdleConns(10) conn.SetMaxIdleConns(10)
conn.SetMaxOpenConns(100) conn.SetMaxOpenConns(100)
queries.SetDefault(db)
DB = db DB = db
Conn = conn
return nil
} }
func ExitOrm() error { func stopOrm() error {
if DB != nil { if DB != nil {
conn, err := DB.DB() conn, err := DB.DB()
if err != nil { if err != nil {
return err return fmt.Errorf("关闭数据库连接失败: %w", err)
} }
return conn.Close() return conn.Close()
} }

View File

@@ -23,8 +23,9 @@ var Proxy *ProxyClient
type ProxyClient struct { type ProxyClient struct {
} }
func initProxy() { func initProxy() error {
Proxy = &ProxyClient{} Proxy = &ProxyClient{}
return nil
} }
type ProxyPermitConfig struct { type ProxyPermitConfig struct {

View File

@@ -1,12 +1,13 @@
package globals package globals
import ( import (
"github.com/go-redsync/redsync/v4/redis/goredis/v9"
"log/slog" "log/slog"
"net" "net"
"platform/pkg/env" "platform/pkg/env"
"platform/web/core" "platform/web/core"
"github.com/go-redsync/redsync/v4/redis/goredis/v9"
"github.com/go-redsync/redsync/v4" "github.com/go-redsync/redsync/v4"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
) )
@@ -18,11 +19,10 @@ type ExtendRedSync struct {
*redsync.Redsync *redsync.Redsync
} }
func initRedis() { func initRedis() error {
client := redis.NewClient(&redis.Options{ client := redis.NewClient(&redis.Options{
Addr: net.JoinHostPort(env.RedisHost, env.RedisPort), Addr: net.JoinHostPort(env.RedisHost, env.RedisPort),
DB: env.RedisDb, Password: env.RedisPassword,
Password: env.RedisPass,
}) })
pool := goredis.NewPool(client) pool := goredis.NewPool(client)
@@ -30,9 +30,11 @@ func initRedis() {
Redis = client Redis = client
Redsync = &ExtendRedSync{sync} Redsync = &ExtendRedSync{sync}
return nil
} }
func ExitRedis() error { func stopRedis() error {
if Redis != nil { if Redis != nil {
return Redis.Close() return Redis.Close()
} }

View File

@@ -28,9 +28,9 @@ type SftClient struct {
publicKey *rsa.PublicKey publicKey *rsa.PublicKey
} }
func initSft() { func initSft() error {
if !env.SftPayEnable { if !env.SftPayEnable {
panic("商福通支付未启用,请检查环境变量 SFTPAY_ENABLE") return fmt.Errorf("商福通支付未启用,请检查环境变量 SFTPAY_ENABLE")
} }
SFTPay = SftClient{ SFTPay = SftClient{
@@ -41,7 +41,7 @@ func initSft() {
// 加载私钥 // 加载私钥
private, err := base64.StdEncoding.DecodeString(env.SftPayAppPrivateKey) private, err := base64.StdEncoding.DecodeString(env.SftPayAppPrivateKey)
if err != nil { if err != nil {
panic("解析商福通私钥失败: " + err.Error()) return fmt.Errorf("解析商福通私钥失败: %w", err)
} }
var privateKey *rsa.PrivateKey var privateKey *rsa.PrivateKey
@@ -49,13 +49,13 @@ func initSft() {
if err != nil { if err != nil {
pkcs8, err := x509.ParsePKCS8PrivateKey(private) pkcs8, err := x509.ParsePKCS8PrivateKey(private)
if err != nil { if err != nil {
panic("解析商福通私钥失败: " + err.Error()) return fmt.Errorf("解析商福通私钥失败: %w", err)
} }
var ok bool var ok bool
privateKey, ok = pkcs8.(*rsa.PrivateKey) privateKey, ok = pkcs8.(*rsa.PrivateKey)
if !ok { if !ok {
panic("解析商福通私钥失败") return fmt.Errorf("解析商福通私钥失败")
} }
} }
SFTPay.privateKey = privateKey SFTPay.privateKey = privateKey
@@ -63,35 +63,36 @@ func initSft() {
// 加载公钥 // 加载公钥
public, err := base64.StdEncoding.DecodeString(env.SftPayPublicKey) public, err := base64.StdEncoding.DecodeString(env.SftPayPublicKey)
if err != nil { if err != nil {
panic("解析商福通公钥失败: " + err.Error()) return fmt.Errorf("解析商福通公钥失败: %w", err)
} }
var publicKey *rsa.PublicKey var publicKey *rsa.PublicKey
pkix, err := x509.ParsePKIXPublicKey(public) pkix, err := x509.ParsePKIXPublicKey(public)
if err != nil { if err != nil {
panic("解析商福通公钥失败: " + err.Error()) return fmt.Errorf("解析商福通公钥失败: %w", err)
} }
var ok bool var ok bool
publicKey, ok = pkix.(*rsa.PublicKey) publicKey, ok = pkix.(*rsa.PublicKey)
if !ok { if !ok {
panic("解析商福通公钥失败") return fmt.Errorf("解析商福通公钥失败")
} }
SFTPay.publicKey = publicKey SFTPay.publicKey = publicKey
return nil
} }
func (s *SftClient) PaymentScanPay(req *PaymentScanPayReq) (*PaymentScanPayResp, error) { func (s *SftClient) PaymentScanPay(req *PaymentScanPayReq) (*PaymentScanPayResp, error) {
const url = "https://pay.rscygroup.com/api/open/payment/scanpay" const url = "https://pay.rscygroup.com/api/open/payment/scanpay"
req.ReturnUrl = env.SftReturnUrl req.ReturnUrl = u.X(env.SftReturnUrl)
req.NotifyUrl = env.SftNotifyUrl req.NotifyUrl = u.X(env.SftNotifyUrl)
req.RouteNo = u.P(s.routeId) req.RouteNo = u.P(s.routeId)
return call[PaymentScanPayResp](s, url, req) return call[PaymentScanPayResp](s, url, req)
} }
func (s *SftClient) PaymentH5Pay(req *PaymentH5PayReq) (*PaymentH5PayResp, error) { func (s *SftClient) PaymentH5Pay(req *PaymentH5PayReq) (*PaymentH5PayResp, error) {
const url = "https://pay.rscygroup.com/api/open/payment/h5pay" const url = "https://pay.rscygroup.com/api/open/payment/h5pay"
req.ReturnUrl = env.SftReturnUrl req.ReturnUrl = u.X(env.SftReturnUrl)
req.NotifyUrl = env.SftNotifyUrl req.NotifyUrl = u.X(env.SftNotifyUrl)
req.RouteNo = u.P(s.routeId) req.RouteNo = u.P(s.routeId)
return call[PaymentH5PayResp](s, url, req) 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) encode, err := s.sign(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("加密请求内容失败%w", err) return nil, fmt.Errorf("加密请求内容失败: %w", err)
} }
bytes, err := json.Marshal(encode) 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))) request, err := http.NewRequest("POST", url, strings.NewReader(string(bytes)))
if err != nil { if err != nil {
return nil, fmt.Errorf("创建请求失败%w", err) return nil, fmt.Errorf("创建请求失败: %w", err)
} }
request.Header.Set("Content-Type", "application/json") request.Header.Set("Content-Type", "application/json")
if env.DebugHttpDump == true { if env.DebugHttpDump == true {
reqDump, err := httputil.DumpRequest(request, true) reqDump, err := httputil.DumpRequest(request, true)
if err != nil { if err != nil {
return nil, fmt.Errorf("请求内容转储失败%w", err) return nil, fmt.Errorf("请求内容转储失败: %w", err)
} }
println(string(reqDump) + "\n\n") println(string(reqDump) + "\n\n")
} }
response, err := http.DefaultClient.Do(request) response, err := http.DefaultClient.Do(request)
if err != nil { if err != nil {
return nil, fmt.Errorf("请求失败%w", err) return nil, fmt.Errorf("请求失败: %w", err)
} }
if env.DebugHttpDump == true { if env.DebugHttpDump == true {
respDump, err := httputil.DumpResponse(response, true) respDump, err := httputil.DumpResponse(response, true)
if err != nil { if err != nil {
return nil, fmt.Errorf("响应内容转储失败%w", err) return nil, fmt.Errorf("响应内容转储失败: %w", err)
} }
println(string(respDump) + "\n\n") println(string(respDump) + "\n\n")
} }
if response.StatusCode != http.StatusOK { if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("请求响应失败%d", response.StatusCode) return nil, fmt.Errorf("请求响应失败: %d", response.StatusCode)
} }
defer func(body io.ReadCloser) { defer func(body io.ReadCloser) {
_ = body.Close() _ = body.Close()
@@ -300,18 +301,18 @@ func call[T any](s *SftClient, url string, req any) (*T, error) {
body, err := io.ReadAll(response.Body) body, err := io.ReadAll(response.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("读取响应内容失败%w", err) return nil, fmt.Errorf("读取响应内容失败: %w", err)
} }
decode, err := s.verify(body) decode, err := s.verify(body)
if err != nil { if err != nil {
return nil, fmt.Errorf("解密响应内容失败%w", err) return nil, fmt.Errorf("解密响应内容失败: %w", err)
} }
var resp = new(T) var resp = new(T)
err = json.Unmarshal([]byte(decode), resp) err = json.Unmarshal([]byte(decode), resp)
if err != nil { if err != nil {
return nil, fmt.Errorf("响应正文解析失败%w", err) return nil, fmt.Errorf("响应正文解析失败: %w", err)
} }
return resp, nil return resp, nil
@@ -321,7 +322,7 @@ func (s *SftClient) sign(msg any) (*request, error) {
bytes, err := json.Marshal(msg) bytes, err := json.Marshal(msg)
if err != nil { if err != nil {
return nil, fmt.Errorf("格式化加密正文失败%w", err) return nil, fmt.Errorf("格式化加密正文失败: %w", err)
} }
if env.DebugHttpDump { if env.DebugHttpDump {
@@ -341,7 +342,7 @@ func (s *SftClient) sign(msg any) (*request, error) {
hashed := sha256.Sum256([]byte(body.String())) hashed := sha256.Sum256([]byte(body.String()))
signature, err := rsa.SignPKCS1v15(nil, s.privateKey, crypto.SHA256, hashed[:]) signature, err := rsa.SignPKCS1v15(nil, s.privateKey, crypto.SHA256, hashed[:])
if err != nil { if err != nil {
return nil, fmt.Errorf("签名失败%w", err) return nil, fmt.Errorf("签名失败: %w", err)
} }
body.Sign = base64.StdEncoding.EncodeToString(signature) body.Sign = base64.StdEncoding.EncodeToString(signature)
@@ -353,11 +354,11 @@ func (s *SftClient) verify(str []byte) (string, error) {
var resp = new(response) var resp = new(response)
err := json.Unmarshal(str, resp) err := json.Unmarshal(str, resp)
if err != nil { if err != nil {
return "", fmt.Errorf("解析响应正文失败%w", err) return "", fmt.Errorf("解析响应正文失败: %w", err)
} }
if resp.Code != "000000" { if resp.Code != "000000" {
return "", fmt.Errorf("请求业务响应失败%s", u.Z(resp.Msg)) return "", fmt.Errorf("请求业务响应失败: %s", u.Z(resp.Msg))
} }
if resp.Sign == nil { if resp.Sign == nil {
@@ -371,13 +372,13 @@ func (s *SftClient) verify(str []byte) (string, error) {
ser, err := resp.String() ser, err := resp.String()
if err != nil { if err != nil {
return "", fmt.Errorf("格式化响应内容失败%w", err) return "", fmt.Errorf("格式化响应内容失败: %w", err)
} }
hashed := sha256.Sum256([]byte(ser)) hashed := sha256.Sum256([]byte(ser))
err = rsa.VerifyPKCS1v15(s.publicKey, crypto.SHA256, hashed[:], sign) err = rsa.VerifyPKCS1v15(s.publicKey, crypto.SHA256, hashed[:], sign)
if err != nil { if err != nil {
return "", fmt.Errorf("验签失败%w", err) return "", fmt.Errorf("验签失败: %w", err)
} }
return *resp.BizData, nil return *resp.BizData, nil
@@ -412,7 +413,7 @@ type response struct {
func (r response) String() (string, error) { func (r response) String() (string, error) {
if r.BizData == nil || r.Msg == nil || r.SignType == nil { if r.BizData == nil || r.Msg == nil || r.SignType == nil {
return "", core.NewServErr(fmt.Sprintf( return "", core.NewServErr(fmt.Sprintf(
"上游数据返回有空值BizData %vMsg %v, SignType %v", "上游数据返回有空值: BizData %vMsg %v, SignType %v",
r.BizData == nil, r.Msg == nil, r.SignType == nil, r.BizData == nil, r.Msg == nil, r.SignType == nil,
)) ))
} }

View File

@@ -2,12 +2,14 @@ package globals
import ( import (
"errors" "errors"
"fmt"
"strings"
"github.com/go-playground/locales/zh" "github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator" ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
zhtrans "github.com/go-playground/validator/v10/translations/zh" zhtrans "github.com/go-playground/validator/v10/translations/zh"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"strings"
) )
var Validator *ValidatorClient var Validator *ValidatorClient
@@ -38,17 +40,18 @@ func (v *ValidatorClient) Validate(c *fiber.Ctx, data any) error {
return nil return nil
} }
func initValidator() { func initValidator() error {
var validate = validator.New(validator.WithRequiredStructEnabled()) var validate = validator.New(validator.WithRequiredStructEnabled())
var translator = ut.New(zh.New()).GetFallback() var translator = ut.New(zh.New()).GetFallback()
err := zhtrans.RegisterDefaultTranslations(validate, translator) err := zhtrans.RegisterDefaultTranslations(validate, translator)
if err != nil { if err != nil {
panic(err) return fmt.Errorf("初始化验证器失败: %w", err)
} }
Validator = &ValidatorClient{ Validator = &ValidatorClient{
validator: validate, validator: validate,
translator: translator, translator: translator,
} }
return nil
} }

View File

@@ -3,6 +3,7 @@ package globals
import ( import (
"context" "context"
"encoding/base64" "encoding/base64"
"fmt"
"platform/pkg/env" "platform/pkg/env"
"github.com/wechatpay-apiv3/wechatpay-go/core" "github.com/wechatpay-apiv3/wechatpay-go/core"
@@ -20,28 +21,28 @@ type WechatPayClient struct {
Notify *notify.Handler Notify *notify.Handler
} }
func initWechatPay() { func initWechatPay() error {
// 加载商户私钥 // 加载商户私钥
private, err := base64.StdEncoding.DecodeString(env.WechatPayMchPrivateKey) private, err := base64.StdEncoding.DecodeString(env.WechatPayMchPrivateKey)
if err != nil { if err != nil {
panic(err) return fmt.Errorf("加载微信支付商户私钥失败: %w", err)
} }
appPrivateKey, err := utils.LoadPrivateKey(string(private)) appPrivateKey, err := utils.LoadPrivateKey(string(private))
if err != nil { if err != nil {
panic(err) return fmt.Errorf("解析微信支付商户私钥失败: %w", err)
} }
// 加载微信支付公钥 // 加载微信支付公钥
public, err := base64.StdEncoding.DecodeString(env.WechatPayPublicKey) public, err := base64.StdEncoding.DecodeString(env.WechatPayPublicKey)
if err != nil { if err != nil {
panic(err) return fmt.Errorf("加载微信支付公钥失败: %w", err)
} }
wechatPublicKey, err := utils.LoadPublicKey(string(public)) wechatPublicKey, err := utils.LoadPublicKey(string(public))
if err != nil { if err != nil {
panic(err) return fmt.Errorf("解析微信支付公钥失败: %w", err)
} }
// 创建 WechatPay 客户端 // 创建 WechatPay 客户端
@@ -55,7 +56,7 @@ func initWechatPay() {
), ),
) )
if err != nil { if err != nil {
panic(err) return fmt.Errorf("创建微信支付客户端失败: %w", err)
} }
// 创建 WechatPay 通知处理器 // 创建 WechatPay 通知处理器
@@ -64,7 +65,7 @@ func initWechatPay() {
*wechatPublicKey, *wechatPublicKey,
)) ))
if err != nil { if err != nil {
panic(err) return fmt.Errorf("创建微信支付通知处理器失败: %w", err)
} }
// 创建 WechatPay 服务 // 创建 WechatPay 服务
@@ -72,4 +73,5 @@ func initWechatPay() {
Native: &native.NativeApiService{Client: client}, Native: &native.NativeApiService{Client: client},
Notify: handler, Notify: handler,
} }
return nil
} }

View File

@@ -1,10 +1,11 @@
package handlers package handlers
import ( import (
"github.com/gofiber/fiber/v2"
"platform/web/auth" "platform/web/auth"
"platform/web/core" "platform/web/core"
q "platform/web/queries" q "platform/web/queries"
"github.com/gofiber/fiber/v2"
) )
// region ListAnnouncements // region ListAnnouncements
@@ -16,7 +17,7 @@ type ListAnnouncementsRequest struct {
func ListAnnouncements(c *fiber.Ctx) error { func ListAnnouncements(c *fiber.Ctx) error {
// 检查权限 // 检查权限
_, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) _, err := auth.GetAuthCtx(c).PermitUser()
if err != nil { if err != nil {
return err return err
} }

View File

@@ -1,277 +1,14 @@
package handlers package handlers
import ( import (
"encoding/base64"
"errors"
"log/slog"
"platform/pkg/u" "platform/pkg/u"
auth2 "platform/web/auth" auth2 "platform/web/auth"
client2 "platform/web/domains/client"
m "platform/web/models" m "platform/web/models"
q "platform/web/queries" q "platform/web/queries"
s "platform/web/services"
"strings"
"time"
"github.com/gofiber/fiber/v2" "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 // region /revoke
type RevokeReq struct { type RevokeReq struct {
@@ -280,7 +17,7 @@ type RevokeReq struct {
} }
func Revoke(c *fiber.Ctx) error { func Revoke(c *fiber.Ctx) error {
_, err := auth2.Protect(c, []auth2.PayloadType{auth2.PayloadUser}, []string{}) _, err := auth2.GetAuthCtx(c).PermitUser()
if err != nil { if err != nil {
// 用户未登录 // 用户未登录
return nil return nil
@@ -312,14 +49,14 @@ type IntrospectResp struct {
func Introspect(c *fiber.Ctx) error { 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 { if err != nil {
return err return err
} }
// 获取用户信息 // 获取用户信息
profile, err := q.User. profile, err := q.User.
Where(q.User.ID.Eq(authCtx.Payload.Id)). Where(q.User.ID.Eq(authCtx.User.ID)).
Omit(q.User.DeletedAt). Omit(q.User.DeletedAt).
Take() Take()
if err != nil { if err != nil {

View File

@@ -23,7 +23,7 @@ type ListBillReq struct {
// ListBill 获取账单列表 // ListBill 获取账单列表
func ListBill(c *fiber.Ctx) error { 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 { if err != nil {
return err return err
} }
@@ -36,7 +36,7 @@ func ListBill(c *fiber.Ctx) error {
// 查询账单列表 // 查询账单列表
do := q.Bill. do := q.Bill.
Where(q.Bill.UserID.Eq(authContext.Payload.Id)) Where(q.Bill.UserID.Eq(authCtx.User.ID))
if req.Type != nil { if req.Type != nil {
do.Where(q.Bill.Type.Eq(int32(*req.Type))) do.Where(q.Bill.Type.Eq(int32(*req.Type)))

View File

@@ -24,7 +24,7 @@ type ListChannelsReq struct {
func ListChannels(c *fiber.Ctx) error { 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 { if err != nil {
return err return err
} }
@@ -37,7 +37,7 @@ func ListChannels(c *fiber.Ctx) error {
// 构造查询条件 // 构造查询条件
cond := q.Channel. cond := q.Channel.
Where(q.Channel.UserID.Eq(authContext.Payload.Id)) Where(q.Channel.UserID.Eq(authContext.User.ID))
switch req.AuthType { switch req.AuthType {
case s.ChannelAuthTypeIp: case s.ChannelAuthTypeIp:
cond.Where(q.Channel.AuthIP.Is(true)) cond.Where(q.Channel.AuthIP.Is(true))
@@ -110,24 +110,19 @@ type CreateChannelRespItem struct {
func CreateChannel(c *fiber.Ctx) error { 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 { if err != nil {
return err return err
} }
// 检查用户其他权限 // 检查用户其他权限
user, err := q.User. user := authCtx.User
Where(q.User.ID.Eq(authContext.Payload.Id)).
Take()
if err != nil {
return err
}
if user.IDToken == nil || *user.IDToken == "" { if user.IDToken == nil || *user.IDToken == "" {
return fiber.NewError(fiber.StatusForbidden, "账号未实名") return fiber.NewError(fiber.StatusForbidden, "账号未实名")
} }
count, err := q.Whitelist.Where( count, err := q.Whitelist.Where(
q.Whitelist.UserID.Eq(authContext.Payload.Id), q.Whitelist.UserID.Eq(user.ID),
q.Whitelist.Host.Eq(c.IP()), q.Whitelist.Host.Eq(c.IP()),
).Count() ).Count()
if err != nil { if err != nil {
@@ -155,7 +150,7 @@ func CreateChannel(c *fiber.Ctx) error {
// 创建通道 // 创建通道
result, err := s.Channel.CreateChannel( result, err := s.Channel.CreateChannel(
c, c,
authContext.Payload.Id, user.ID,
req.ResourceId, req.ResourceId,
req.Protocol, req.Protocol,
req.AuthType, req.AuthType,
@@ -198,7 +193,7 @@ type RemoveChannelsReq struct {
func RemoveChannels(c *fiber.Ctx) error { func RemoveChannels(c *fiber.Ctx) error {
// 检查权限 // 检查权限
authCtx, err := auth.NewProtect(c).Payload(auth.PayloadUser).Do() authCtx, err := auth.GetAuthCtx(c).PermitUser()
if err != nil { if err != nil {
return err return err
} }
@@ -210,31 +205,7 @@ func RemoveChannels(c *fiber.Ctx) error {
} }
// 删除通道 // 删除通道
err = s.Channel.RemoveChannels(req.ByIds, authCtx.Payload.Id) err = s.Channel.RemoveChannels(req.ByIds, authCtx.User.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)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -2,8 +2,6 @@ package handlers
import ( import (
"errors" "errors"
"gorm.io/gen/field"
"gorm.io/gorm"
"log/slog" "log/slog"
"platform/pkg/u" "platform/pkg/u"
"platform/web/auth" "platform/web/auth"
@@ -14,6 +12,9 @@ import (
q "platform/web/queries" q "platform/web/queries"
s "platform/web/services" s "platform/web/services"
"gorm.io/gen/field"
"gorm.io/gorm"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
@@ -120,7 +121,7 @@ type AllEdgesAvailableRespItem struct {
func AllEdgesAvailable(c *fiber.Ctx) (err error) { func AllEdgesAvailable(c *fiber.Ctx) (err error) {
// 检查权限 // 检查权限
_, err = auth.NewProtect(c).Payload(auth.PayloadInternalServer).Do() _, err = auth.GetAuthCtx(c).PermitSecretClient()
if err != nil { if err != nil {
return err return err
} }

View File

@@ -37,17 +37,11 @@ type IdentifyRes struct {
func Identify(c *fiber.Ctx) error { func Identify(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
}
user, err := q.User.
Where(q.User.ID.Eq(authCtx.Payload.Id)).
Select(q.User.ID, q.User.IDToken).
Take()
if err != nil { if err != nil {
return err return err
} }
user := authCtx.User
if user.IDToken != nil && *user.IDToken != "" { if user.IDToken != nil && *user.IDToken != "" {
// 用户已实名认证 // 用户已实名认证
return c.JSON(IdentifyRes{ return c.JSON(IdentifyRes{
@@ -86,7 +80,7 @@ func Identify(c *fiber.Ctx) error {
// 保存认证中间状态 // 保存认证中间状态
info := idenInfo{ info := idenInfo{
Uid: authCtx.Payload.Id, Uid: user.ID,
Type: req.Type, Type: req.Type,
Name: req.Name, Name: req.Name,
IdNo: req.IdenNo, IdNo: req.IdenNo,

View File

@@ -40,9 +40,7 @@ type ProxyReportOnlineResp struct {
func ProxyReportOnline(c *fiber.Ctx) (err error) { func ProxyReportOnline(c *fiber.Ctx) (err error) {
// 检查接口权限 // 检查接口权限
_, err = auth2.NewProtect(c).Payload( _, err = auth2.GetAuthCtx(c).PermitSecretClient()
auth2.PayloadInternalServer,
).Do()
if err != nil { if err != nil {
return err return err
} }
@@ -149,9 +147,7 @@ type ProxyReportOfflineReq struct {
func ProxyReportOffline(c *fiber.Ctx) (err error) { func ProxyReportOffline(c *fiber.Ctx) (err error) {
// 检查接口权限 // 检查接口权限
_, err = auth2.NewProtect(c).Payload( _, err = auth2.GetAuthCtx(c).PermitSecretClient()
auth2.PayloadInternalServer,
).Do()
if err != nil { if err != nil {
return err return err
} }
@@ -193,9 +189,7 @@ type ProxyReportUpdateReq struct {
func ProxyReportUpdate(c *fiber.Ctx) (err error) { func ProxyReportUpdate(c *fiber.Ctx) (err error) {
// 检查接口权限 // 检查接口权限
_, err = auth2.NewProtect(c).Payload( _, err = auth2.GetAuthCtx(c).PermitSecretClient()
auth2.PayloadInternalServer,
).Do()
if err != nil { if err != nil {
return err return err
} }

View File

@@ -1,7 +1,6 @@
package handlers package handlers
import ( import (
"gorm.io/gen/field"
"platform/pkg/u" "platform/pkg/u"
"platform/web/auth" "platform/web/auth"
"platform/web/core" "platform/web/core"
@@ -12,6 +11,8 @@ import (
s "platform/web/services" s "platform/web/services"
"time" "time"
"gorm.io/gen/field"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
@@ -28,7 +29,7 @@ type ListResourceShortReq struct {
func ListResourceShort(c *fiber.Ctx) error { 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 { if err != nil {
return err return err
} }
@@ -41,7 +42,7 @@ func ListResourceShort(c *fiber.Ctx) error {
// 查询套餐列表 // 查询套餐列表
do := q.Resource.Where( 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)), q.Resource.Type.Eq(int32(resource2.TypeShort)),
) )
if req.ResourceNo != nil && *req.ResourceNo != "" { if req.ResourceNo != nil && *req.ResourceNo != "" {
@@ -109,7 +110,7 @@ type ListResourceLongReq struct {
func ListResourceLong(c *fiber.Ctx) error { 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 { if err != nil {
return err return err
} }
@@ -122,7 +123,7 @@ func ListResourceLong(c *fiber.Ctx) error {
// 查询套餐列表 // 查询套餐列表
do := q.Resource.Where( 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)), q.Resource.Type.Eq(int32(resource2.TypeLong)),
) )
if req.ResourceNo != nil && *req.ResourceNo != "" { if req.ResourceNo != nil && *req.ResourceNo != "" {
@@ -182,7 +183,7 @@ type AllResourceReq struct {
func AllActiveResource(c *fiber.Ctx) error { func AllActiveResource(c *fiber.Ctx) error {
// 检查权限 // 检查权限
authCtx, err := auth.NewProtect(c).Payload(auth.PayloadUser).Do() authCtx, err := auth.GetAuthCtx(c).PermitUser()
if err != nil { if err != nil {
return err return err
} }
@@ -198,7 +199,7 @@ func AllActiveResource(c *fiber.Ctx) error {
q.Resource.Long, q.Resource.Long,
). ).
Where( Where(
q.Resource.UserID.Eq(authCtx.Payload.Id), q.Resource.UserID.Eq(authCtx.User.ID),
q.Resource.Active.Is(true), q.Resource.Active.Is(true),
q.Resource.Where( q.Resource.Where(
q.Resource.Type.Eq(int32(resource2.TypeShort)), q.Resource.Type.Eq(int32(resource2.TypeShort)),
@@ -254,7 +255,7 @@ type StatisticLong struct {
func StatisticResourceFree(c *fiber.Ctx) error { func StatisticResourceFree(c *fiber.Ctx) error {
// 检查权限 // 检查权限
session, err := auth.NewProtect(c).Payload(auth.PayloadUser).Do() authCtx, err := auth.GetAuthCtx(c).PermitUser()
if err != nil { if err != nil {
return err return err
} }
@@ -266,7 +267,7 @@ func StatisticResourceFree(c *fiber.Ctx) error {
q.Resource.Long, q.Resource.Long,
). ).
Where( Where(
q.Resource.UserID.Eq(session.Payload.Id), q.Resource.UserID.Eq(authCtx.User.ID),
q.Resource.Active.Is(true), q.Resource.Active.Is(true),
). ).
Select(q.Resource.ID, q.Resource.Type). Select(q.Resource.ID, q.Resource.Type).
@@ -347,7 +348,7 @@ type StatisticResourceUsageResp []struct {
func StatisticResourceUsage(c *fiber.Ctx) error { func StatisticResourceUsage(c *fiber.Ctx) error {
// 检查权限 // 检查权限
session, err := auth.NewProtect(c).Payload(auth.PayloadUser).Do() authCtx, err := auth.GetAuthCtx(c).PermitUser()
if err != nil { if err != nil {
return err 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 != "" { if req.ResourceNo != nil && *req.ResourceNo != "" {
var resourceID int32 var resourceID int32
err := q.Resource. err := q.Resource.
Where( Where(
q.Resource.UserID.Eq(session.Payload.Id), q.Resource.UserID.Eq(authCtx.User.ID),
q.Resource.ResourceNo.Eq(*req.ResourceNo), q.Resource.ResourceNo.Eq(*req.ResourceNo),
). ).
Select(q.Resource.ID). Select(q.Resource.ID).
@@ -409,7 +410,7 @@ type CreateResourceReq struct {
func CreateResource(c *fiber.Ctx) error { func CreateResource(c *fiber.Ctx) error {
// 检查权限 // 检查权限
authCtx, err := auth.NewProtect(c).Payload(auth.PayloadUser).Do() authCtx, err := auth.GetAuthCtx(c).PermitUser()
if err != nil { if err != nil {
return err 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 { if err != nil {
return err return err
} }
@@ -431,7 +432,7 @@ func CreateResource(c *fiber.Ctx) error {
func ResourcePrice(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 { if err != nil {
return err return err
} }

View File

@@ -7,7 +7,6 @@ import (
trade2 "platform/web/domains/trade" trade2 "platform/web/domains/trade"
g "platform/web/globals" g "platform/web/globals"
s "platform/web/services" s "platform/web/services"
"platform/web/tasks"
"time" "time"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
@@ -27,7 +26,7 @@ type TradeCreateResp struct {
func TradeCreate(c *fiber.Ctx) error { func TradeCreate(c *fiber.Ctx) error {
// 检查权限 // 检查权限
authCtx, err := auth.NewProtect(c).Payload(auth.PayloadUser).Do() authCtx, err := auth.GetAuthCtx(c).PermitUser()
if err != nil { if err != nil {
return err 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 { if err != nil {
slog.Error("创建交易失败", "error", err) slog.Error("创建交易失败", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "创建交易失败"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "创建交易失败"})
@@ -70,7 +69,7 @@ type TradeCompleteReq struct {
func TradeComplete(c *fiber.Ctx) error { func TradeComplete(c *fiber.Ctx) error {
// 检查权限 // 检查权限
_, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) _, err := auth.GetAuthCtx(c).PermitUser()
if err != nil { if err != nil {
return err return err
} }
@@ -99,7 +98,7 @@ type TradeCancelReq struct {
func TradeCancel(c *fiber.Ctx) error { func TradeCancel(c *fiber.Ctx) error {
// 检查权限 // 检查权限
_, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{}) _, err := auth.GetAuthCtx(c).PermitUser()
if err != nil { if err != nil {
return err return err
} }
@@ -119,29 +118,3 @@ func TradeCancel(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusNoContent) 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
}

View File

@@ -1,12 +1,13 @@
package handlers package handlers
import ( import (
"github.com/gofiber/fiber/v2"
"golang.org/x/crypto/bcrypt"
"platform/web/auth" "platform/web/auth"
m "platform/web/models" m "platform/web/models"
q "platform/web/queries" q "platform/web/queries"
s "platform/web/services" s "platform/web/services"
"github.com/gofiber/fiber/v2"
"golang.org/x/crypto/bcrypt"
) )
// region /update // region /update
@@ -20,7 +21,7 @@ type UpdateUserReq struct {
func UpdateUser(c *fiber.Ctx) error { 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 { if err != nil {
return err return err
} }
@@ -33,7 +34,7 @@ func UpdateUser(c *fiber.Ctx) error {
// 更新用户信息 // 更新用户信息
_, err = q.User. _, err = q.User.
Where(q.User.ID.Eq(authCtx.Payload.Id)). Where(q.User.ID.Eq(authCtx.User.ID)).
Updates(m.User{ Updates(m.User{
Username: &req.Username, Username: &req.Username,
Email: &req.Email, Email: &req.Email,
@@ -59,7 +60,7 @@ type UpdateAccountReq struct {
func UpdateAccount(c *fiber.Ctx) error { 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 { if err != nil {
return err return err
} }
@@ -72,7 +73,7 @@ func UpdateAccount(c *fiber.Ctx) error {
// 更新用户信息 // 更新用户信息
_, err = q.User. _, err = q.User.
Where(q.User.ID.Eq(authCtx.Payload.Id)). Where(q.User.ID.Eq(authCtx.User.ID)).
Updates(m.User{ Updates(m.User{
Username: &req.Username, Username: &req.Username,
Password: &req.Password, Password: &req.Password,
@@ -97,7 +98,7 @@ type UpdatePasswordReq struct {
func UpdatePassword(c *fiber.Ctx) error { 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 { if err != nil {
return err return err
} }
@@ -124,7 +125,7 @@ func UpdatePassword(c *fiber.Ctx) error {
} }
_, err = q.User. _, err = q.User.
Where(q.User.ID.Eq(authCtx.Payload.Id)). Where(q.User.ID.Eq(authCtx.User.ID)).
UpdateColumn(q.User.Password, newHash) UpdateColumn(q.User.Password, newHash)
if err != nil { if err != nil {
return err return err

View File

@@ -2,12 +2,14 @@ package handlers
import ( import (
"errors" "errors"
"platform/pkg/env"
"platform/web/auth" "platform/web/auth"
"platform/web/services" "platform/web/services"
"regexp" "regexp"
"strconv" "strconv"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/redis/go-redis/v9"
) )
type VerifierReq struct { type VerifierReq struct {
@@ -17,7 +19,7 @@ type VerifierReq struct {
func SmsCode(c *fiber.Ctx) error { func SmsCode(c *fiber.Ctx) error {
_, err := auth.Protect(c, []auth.PayloadType{auth.PayloadInternalServer}, []string{}) _, err := auth.GetAuthCtx(c).PermitInternalClient()
if err != nil { if err != nil {
return err return err
} }
@@ -48,3 +50,19 @@ func SmsCode(c *fiber.Ctx) error {
// 发送成功 // 发送成功
return nil 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)
}

View File

@@ -26,7 +26,7 @@ type ListWhitelistResp struct {
func ListWhitelist(c *fiber.Ctx) error { 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 { if err != nil {
return err return err
} }
@@ -38,8 +38,7 @@ func ListWhitelist(c *fiber.Ctx) error {
} }
// 获取白名单信息 // 获取白名单信息
do := q.Whitelist. do := q.Whitelist.Where(q.Whitelist.UserID.Eq(authCtx.User.ID))
Where(q.Whitelist.UserID.Eq(authContext.Payload.Id))
list, err := q.Whitelist.Where(do). list, err := q.Whitelist.Where(do).
Offset(req.GetOffset()). Offset(req.GetOffset()).
@@ -77,7 +76,7 @@ type CreateWhitelistReq struct {
func CreateWhitelist(c *fiber.Ctx) error { 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 { if err != nil {
return err return err
} }
@@ -96,7 +95,7 @@ func CreateWhitelist(c *fiber.Ctx) error {
// 创建白名单 // 创建白名单
err = q.Whitelist.Create(&m.Whitelist{ err = q.Whitelist.Create(&m.Whitelist{
UserID: authContext.Payload.Id, UserID: authCtx.User.ID,
Host: req.Host, Host: req.Host,
Remark: &req.Remark, Remark: &req.Remark,
}) })
@@ -111,7 +110,7 @@ type UpdateWhitelistReq struct {
func UpdateWhitelist(c *fiber.Ctx) error { 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 { if err != nil {
return err return err
} }
@@ -129,7 +128,7 @@ func UpdateWhitelist(c *fiber.Ctx) error {
_, err = q.Whitelist. _, err = q.Whitelist.
Where( Where(
q.Whitelist.ID.Eq(req.ID), q.Whitelist.ID.Eq(req.ID),
q.Whitelist.UserID.Eq(authContext.Payload.Id), q.Whitelist.UserID.Eq(authCtx.User.ID),
). ).
Updates(&m.Whitelist{ Updates(&m.Whitelist{
ID: req.ID, ID: req.ID,
@@ -149,7 +148,7 @@ type RemoveWhitelistReq struct {
func RemoveWhitelist(c *fiber.Ctx) error { 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 { if err != nil {
return err return err
} }
@@ -175,7 +174,7 @@ func RemoveWhitelist(c *fiber.Ctx) error {
_, err = q.Whitelist. _, err = q.Whitelist.
Where( Where(
q.Whitelist.ID.In(ids...), q.Whitelist.ID.In(ids...),
q.Whitelist.UserID.Eq(authContext.Payload.Id), q.Whitelist.UserID.Eq(authCtx.User.ID),
). ).
Update( Update(
q.Whitelist.DeletedAt, time.Now(), q.Whitelist.DeletedAt, time.Now(),

42
web/middlewares.go Normal file
View File

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

View File

@@ -17,10 +17,14 @@ type Channel struct {
ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:通道ID" json:"id"` // 通道ID 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 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 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"` // 代理地址 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"` // 转发端口 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-http2-https3-socks5" json:"protocol"` // 协议类型1-http2-https3-socks5 Protocol *int32 `gorm:"column:protocol;type:integer;comment:协议类型1-http2-https3-socks5" json:"protocol"` // 协议类型1-http2-https3-socks5
AuthIP bool `gorm:"column:auth_ip;type:boolean;not null;comment:IP认证" json:"auth_ip"` // IP认证 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"` // 密码认证 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"` // 用户名 Username *string `gorm:"column:username;type:character varying(255);comment:用户名" json:"username"` // 用户名
Password *string `gorm:"column:password;type:character varying(255);comment:密码" json:"password"` // 密码 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"` // 创建时间 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"` // 更新时间 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"` // 删除时间 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 // TableName Channel's table name

View File

@@ -14,21 +14,18 @@ const TableNameClient = "client"
// Client mapped from table <client> // Client mapped from table <client>
type Client struct { type Client struct {
ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:客户端ID" json:"id"` // 客户端ID 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客户端标识符 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客户端密钥 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 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"` // 允许授权码授予 Spec int32 `gorm:"column:spec;type:integer;not null;comment:安全规范1-native2-browser3-web4-api" json:"spec"` // 安全规范1-native2-browser3-web4-api
GrantClient bool `gorm:"column:grant_client;type:boolean;not null;comment:允许客户端凭证授予" json:"grant_client"` // 允许客户端凭证授予 Name string `gorm:"column:name;type:character varying(255);not null;comment:名称" json:"name"` // 名称
GrantRefresh bool `gorm:"column:grant_refresh;type:boolean;not null;comment:允许刷新令牌授予" json:"grant_refresh"` // 允许刷新令牌授予 Icon *string `gorm:"column:icon;type:character varying(255);comment:图标URL" json:"icon"` // 图标URL
GrantPassword bool `gorm:"column:grant_password;type:boolean;not null;comment:允许密码授予" json:"grant_password"` // 允许密码授予 Status int32 `gorm:"column:status;type:integer;not null;default:1;comment:状态0-禁用1-正常" json:"status"` // 状态0-禁用1-正常
Spec int32 `gorm:"column:spec;type:integer;not null;comment:安全规范1-native2-browser3-web4-trusted" json:"spec"` // 安全规范1-native2-browser3-web4-trusted Type int32 `gorm:"column:type;type:integer;not null;comment:类型0-普通1-官方" json:"type"` // 类型0-普通1-官方
Name string `gorm:"column:name;type:character varying(255);not null;comment:名称" json:"name"` // 名称 CreatedAt *orm.LocalDateTime `gorm:"column:created_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
Icon *string `gorm:"column:icon;type:character varying(255);comment:图标URL" json:"icon"` // 图标URL UpdatedAt *orm.LocalDateTime `gorm:"column:updated_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
Status int32 `gorm:"column:status;type:integer;not null;default:1;comment:状态0-禁用1-正常" json:"status"` // 状态0-禁用1-正常 DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp without time zone;comment:删除时间" json:"deleted_at"` // 删除时间
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 // TableName Client's table name

View File

@@ -12,12 +12,12 @@ const TableNameLogsLogin = "logs_login"
type LogsLogin struct { type LogsLogin struct {
ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:登录日志ID" json:"id"` // 登录日志ID 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地址 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-密码模式 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-邮箱验证码 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"` // 登录是否成功 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 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 // TableName LogsLogin's table name

View File

@@ -10,17 +10,17 @@ const TableNameLogsRequest = "logs_request"
// LogsRequest mapped from table <logs_request> // LogsRequest mapped from table <logs_request>
type LogsRequest struct { type LogsRequest struct {
ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:访问日志ID" json:"id"` // 访问日志ID 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-内部服务 IP string `gorm:"column:ip;type:character varying(45);not null;comment:IP地址" json:"ip"` // IP地址
Visitor *int32 `gorm:"column:visitor;type:integer;comment:访客ID" json:"visitor"` // 访客ID UA string `gorm:"column:ua;type:character varying(255);not null;comment:用户代理" json:"ua"` // 用户代理
IP string `gorm:"column:ip;type:character varying(45);not null;comment:IP地址" json:"ip"` // IP地址 UserID *int32 `gorm:"column:user_id;type:integer;comment:用户ID" json:"user_id"` // 用户ID
Ua *string `gorm:"column:ua;type:character varying(255);comment:用户代理" json:"ua"` // 用户代理 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"` // 请求方法 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"` // 请求路径 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"` // 响应状态码
Status int32 `gorm:"column:status;type:integer;not null;comment:响应状态码" json:"status"` // 响应状态码 Error *string `gorm:"column:error;type:text;comment:错误信息" json:"error"` // 错误信息
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"` // 请求时间
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 // TableName LogsRequest's table name

View File

@@ -18,12 +18,12 @@ type Proxy struct {
Version int32 `gorm:"column:version;type:integer;not null;comment:代理服务版本" json:"version"` // 代理服务版本 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"` // 代理服务名称 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"` // 代理服务地址 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"` // 代理服务密钥 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"` // 创建时间 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"` // 更新时间 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"` // 删除时间 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"` Edges []Edge `gorm:"foreignKey:ProxyID;references:ID" json:"edges"`
} }

View File

@@ -14,20 +14,23 @@ const TableNameSession = "session"
// Session mapped from table <session> // Session mapped from table <session>
type Session struct { type Session struct {
ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:会话ID" json:"id"` // 会话ID 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 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 AdminID *int32 `gorm:"column:admin_id;type:integer;comment:管理员ID" json:"admin_id"` // 管理员ID
IP *string `gorm:"column:ip;type:character varying(45);comment:IP地址" json:"ip"` // IP地址 ClientID *int32 `gorm:"column:client_id;type:integer;comment:客户端ID" json:"client_id"` // 客户端ID
Ua *string `gorm:"column:ua;type:character varying(255);comment:用户代理" json:"ua"` // 用户代理 IP *string `gorm:"column:ip;type:character varying(45);comment:IP地址" json:"ip"` // IP地址
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-密码模式 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"` // 访问令牌 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"` // 访问令牌过期时间 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"` // 刷新令牌 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"` // 刷新令牌过期时间 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"` // 权限范围 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"` // 创建时间 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"` // 更新时间 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"` // 删除时间 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 // TableName Session's table name

View File

@@ -15,26 +15,26 @@ const TableNameTrade = "trade"
// Trade mapped from table <trade> // Trade mapped from table <trade>
type Trade struct { type Trade struct {
ID int32 `gorm:"column:id;type:integer;primaryKey;autoIncrement:true;comment:订单ID" json:"id"` // 订单ID 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 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"` // 内部订单号 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"` // 外部订单号 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-充值余额 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"` // 订单主题 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"` // 订单备注 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"` // 订单总金额 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"` // 支付金额 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-商福通渠道微信 Method int32 `gorm:"column:method;type:integer;not null;comment:支付方式1-支付宝2-微信3-商福通4-商福通渠道支付宝,5-商福通渠道微信" json:"method"` // 支付方式1-支付宝2-微信3-商福通4-商福通渠道支付宝,5-商福通渠道微信
Status int32 `gorm:"column:status;type:integer;not null;comment:订单状态0-待支付1-已支付2-已取消" json:"status"` // 订单状态0-待支付1-已支付2-已取消 Platform int32 `gorm:"column:platform;type:integer;not null;comment:支付平台1-电脑网站2-手机网站" json:"platform"` // 支付平台1-电脑网站2-手机网站
CreatedAt *orm.LocalDateTime `gorm:"column:created_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 Acquirer *int32 `gorm:"column:acquirer;type:integer;comment:收单机构1-支付宝2-微信3-银联" json:"acquirer"` // 收单机构1-支付宝2-微信3-银联
UpdatedAt *orm.LocalDateTime `gorm:"column:updated_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 Status int32 `gorm:"column:status;type:integer;not null;comment:订单状态0-待支付1-已支付2-已取消" json:"status"` // 订单状态0-待支付1-已支付2-已取消
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp without time zone;comment:删除时间" json:"deleted_at"` // 删除时间 Refunded bool `gorm:"column:refunded;type:boolean;not null" json:"refunded"`
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-手机网站
PaymentURL *string `gorm:"column:payment_url;type:text;comment:支付链接" json:"payment_url"` // 支付链接 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"` // 支付时间 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"` // 取消时间 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 // TableName Trade's table name

View File

@@ -30,10 +30,14 @@ func newChannel(db *gorm.DB, opts ...gen.DOOption) channel {
_channel.ID = field.NewInt32(tableName, "id") _channel.ID = field.NewInt32(tableName, "id")
_channel.UserID = field.NewInt32(tableName, "user_id") _channel.UserID = field.NewInt32(tableName, "user_id")
_channel.ProxyID = field.NewInt32(tableName, "proxy_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.ProxyHost = field.NewString(tableName, "proxy_host")
_channel.ProxyPort = field.NewInt32(tableName, "proxy_port") _channel.ProxyPort = field.NewInt32(tableName, "proxy_port")
_channel.EdgeHost = field.NewString(tableName, "edge_host")
_channel.Protocol = field.NewInt32(tableName, "protocol") _channel.Protocol = field.NewInt32(tableName, "protocol")
_channel.AuthIP = field.NewBool(tableName, "auth_ip") _channel.AuthIP = field.NewBool(tableName, "auth_ip")
_channel.Whitelists = field.NewString(tableName, "whitelists")
_channel.AuthPass = field.NewBool(tableName, "auth_pass") _channel.AuthPass = field.NewBool(tableName, "auth_pass")
_channel.Username = field.NewString(tableName, "username") _channel.Username = field.NewString(tableName, "username")
_channel.Password = field.NewString(tableName, "password") _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.CreatedAt = field.NewField(tableName, "created_at")
_channel.UpdatedAt = field.NewField(tableName, "updated_at") _channel.UpdatedAt = field.NewField(tableName, "updated_at")
_channel.DeletedAt = field.NewField(tableName, "deleted_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() _channel.fillFieldMap()
@@ -58,10 +58,14 @@ type channel struct {
ID field.Int32 // 通道ID ID field.Int32 // 通道ID
UserID field.Int32 // 用户ID UserID field.Int32 // 用户ID
ProxyID field.Int32 // 代理ID ProxyID field.Int32 // 代理ID
EdgeID field.Int32 // 节点ID
ResourceID field.Int32 // 套餐ID
ProxyHost field.String // 代理地址 ProxyHost field.String // 代理地址
ProxyPort field.Int32 // 转发端口 ProxyPort field.Int32 // 转发端口
EdgeHost field.String // 节点地址
Protocol field.Int32 // 协议类型1-http2-https3-socks5 Protocol field.Int32 // 协议类型1-http2-https3-socks5
AuthIP field.Bool // IP认证 AuthIP field.Bool // IP认证
Whitelists field.String // IP白名单逗号分隔
AuthPass field.Bool // 密码认证 AuthPass field.Bool // 密码认证
Username field.String // 用户名 Username field.String // 用户名
Password field.String // 密码 Password field.String // 密码
@@ -69,10 +73,6 @@ type channel struct {
CreatedAt field.Field // 创建时间 CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间 UpdatedAt field.Field // 更新时间
DeletedAt 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 fieldMap map[string]field.Expr
} }
@@ -92,10 +92,14 @@ func (c *channel) updateTableName(table string) *channel {
c.ID = field.NewInt32(table, "id") c.ID = field.NewInt32(table, "id")
c.UserID = field.NewInt32(table, "user_id") c.UserID = field.NewInt32(table, "user_id")
c.ProxyID = field.NewInt32(table, "proxy_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.ProxyHost = field.NewString(table, "proxy_host")
c.ProxyPort = field.NewInt32(table, "proxy_port") c.ProxyPort = field.NewInt32(table, "proxy_port")
c.EdgeHost = field.NewString(table, "edge_host")
c.Protocol = field.NewInt32(table, "protocol") c.Protocol = field.NewInt32(table, "protocol")
c.AuthIP = field.NewBool(table, "auth_ip") c.AuthIP = field.NewBool(table, "auth_ip")
c.Whitelists = field.NewString(table, "whitelists")
c.AuthPass = field.NewBool(table, "auth_pass") c.AuthPass = field.NewBool(table, "auth_pass")
c.Username = field.NewString(table, "username") c.Username = field.NewString(table, "username")
c.Password = field.NewString(table, "password") 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.CreatedAt = field.NewField(table, "created_at")
c.UpdatedAt = field.NewField(table, "updated_at") c.UpdatedAt = field.NewField(table, "updated_at")
c.DeletedAt = field.NewField(table, "deleted_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() c.fillFieldMap()
@@ -127,10 +127,14 @@ func (c *channel) fillFieldMap() {
c.fieldMap["id"] = c.ID c.fieldMap["id"] = c.ID
c.fieldMap["user_id"] = c.UserID c.fieldMap["user_id"] = c.UserID
c.fieldMap["proxy_id"] = c.ProxyID 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_host"] = c.ProxyHost
c.fieldMap["proxy_port"] = c.ProxyPort c.fieldMap["proxy_port"] = c.ProxyPort
c.fieldMap["edge_host"] = c.EdgeHost
c.fieldMap["protocol"] = c.Protocol c.fieldMap["protocol"] = c.Protocol
c.fieldMap["auth_ip"] = c.AuthIP c.fieldMap["auth_ip"] = c.AuthIP
c.fieldMap["whitelists"] = c.Whitelists
c.fieldMap["auth_pass"] = c.AuthPass c.fieldMap["auth_pass"] = c.AuthPass
c.fieldMap["username"] = c.Username c.fieldMap["username"] = c.Username
c.fieldMap["password"] = c.Password c.fieldMap["password"] = c.Password
@@ -138,10 +142,6 @@ func (c *channel) fillFieldMap() {
c.fieldMap["created_at"] = c.CreatedAt c.fieldMap["created_at"] = c.CreatedAt
c.fieldMap["updated_at"] = c.UpdatedAt c.fieldMap["updated_at"] = c.UpdatedAt
c.fieldMap["deleted_at"] = c.DeletedAt 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 { func (c channel) clone(db *gorm.DB) channel {

View File

@@ -31,14 +31,11 @@ func newClient(db *gorm.DB, opts ...gen.DOOption) client {
_client.ClientID = field.NewString(tableName, "client_id") _client.ClientID = field.NewString(tableName, "client_id")
_client.ClientSecret = field.NewString(tableName, "client_secret") _client.ClientSecret = field.NewString(tableName, "client_secret")
_client.RedirectURI = field.NewString(tableName, "redirect_uri") _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.Spec = field.NewInt32(tableName, "spec")
_client.Name = field.NewString(tableName, "name") _client.Name = field.NewString(tableName, "name")
_client.Icon = field.NewString(tableName, "icon") _client.Icon = field.NewString(tableName, "icon")
_client.Status = field.NewInt32(tableName, "status") _client.Status = field.NewInt32(tableName, "status")
_client.Type = field.NewInt32(tableName, "type")
_client.CreatedAt = field.NewField(tableName, "created_at") _client.CreatedAt = field.NewField(tableName, "created_at")
_client.UpdatedAt = field.NewField(tableName, "updated_at") _client.UpdatedAt = field.NewField(tableName, "updated_at")
_client.DeletedAt = field.NewField(tableName, "deleted_at") _client.DeletedAt = field.NewField(tableName, "deleted_at")
@@ -51,22 +48,19 @@ func newClient(db *gorm.DB, opts ...gen.DOOption) client {
type client struct { type client struct {
clientDo clientDo
ALL field.Asterisk ALL field.Asterisk
ID field.Int32 // 客户端ID ID field.Int32 // 客户端ID
ClientID field.String // OAuth2客户端标识符 ClientID field.String // OAuth2客户端标识符
ClientSecret field.String // OAuth2客户端密钥 ClientSecret field.String // OAuth2客户端密钥
RedirectURI field.String // OAuth2 重定向URI RedirectURI field.String // OAuth2 重定向URI
GrantCode field.Bool // 允许授权码授予 Spec field.Int32 // 安全规范1-native2-browser3-web4-api
GrantClient field.Bool // 允许客户端凭证授予 Name field.String // 名称
GrantRefresh field.Bool // 允许刷新令牌授予 Icon field.String // 图标URL
GrantPassword field.Bool // 允许密码授予 Status field.Int32 // 状态0-禁用1-正常
Spec field.Int32 // 安全规范1-native2-browser3-web4-trusted Type field.Int32 // 类型0-普通1-官方
Name field.String // 名称 CreatedAt field.Field // 创建时间
Icon field.String // 图标URL UpdatedAt field.Field // 更新时间
Status field.Int32 // 状态0-禁用1-正常 DeletedAt field.Field // 删除时间
CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
fieldMap map[string]field.Expr fieldMap map[string]field.Expr
} }
@@ -87,14 +81,11 @@ func (c *client) updateTableName(table string) *client {
c.ClientID = field.NewString(table, "client_id") c.ClientID = field.NewString(table, "client_id")
c.ClientSecret = field.NewString(table, "client_secret") c.ClientSecret = field.NewString(table, "client_secret")
c.RedirectURI = field.NewString(table, "redirect_uri") 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.Spec = field.NewInt32(table, "spec")
c.Name = field.NewString(table, "name") c.Name = field.NewString(table, "name")
c.Icon = field.NewString(table, "icon") c.Icon = field.NewString(table, "icon")
c.Status = field.NewInt32(table, "status") c.Status = field.NewInt32(table, "status")
c.Type = field.NewInt32(table, "type")
c.CreatedAt = field.NewField(table, "created_at") c.CreatedAt = field.NewField(table, "created_at")
c.UpdatedAt = field.NewField(table, "updated_at") c.UpdatedAt = field.NewField(table, "updated_at")
c.DeletedAt = field.NewField(table, "deleted_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() { 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["id"] = c.ID
c.fieldMap["client_id"] = c.ClientID c.fieldMap["client_id"] = c.ClientID
c.fieldMap["client_secret"] = c.ClientSecret c.fieldMap["client_secret"] = c.ClientSecret
c.fieldMap["redirect_uri"] = c.RedirectURI 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["spec"] = c.Spec
c.fieldMap["name"] = c.Name c.fieldMap["name"] = c.Name
c.fieldMap["icon"] = c.Icon c.fieldMap["icon"] = c.Icon
c.fieldMap["status"] = c.Status c.fieldMap["status"] = c.Status
c.fieldMap["type"] = c.Type
c.fieldMap["created_at"] = c.CreatedAt c.fieldMap["created_at"] = c.CreatedAt
c.fieldMap["updated_at"] = c.UpdatedAt c.fieldMap["updated_at"] = c.UpdatedAt
c.fieldMap["deleted_at"] = c.DeletedAt c.fieldMap["deleted_at"] = c.DeletedAt

View File

@@ -29,12 +29,12 @@ func newLogsLogin(db *gorm.DB, opts ...gen.DOOption) logsLogin {
_logsLogin.ALL = field.NewAsterisk(tableName) _logsLogin.ALL = field.NewAsterisk(tableName)
_logsLogin.ID = field.NewInt32(tableName, "id") _logsLogin.ID = field.NewInt32(tableName, "id")
_logsLogin.IP = field.NewString(tableName, "ip") _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.GrantType = field.NewString(tableName, "grant_type")
_logsLogin.PasswordGrantType = field.NewString(tableName, "password_grant_type") _logsLogin.PasswordGrantType = field.NewString(tableName, "password_grant_type")
_logsLogin.Success = field.NewBool(tableName, "success") _logsLogin.Success = field.NewBool(tableName, "success")
_logsLogin.Time = field.NewField(tableName, "time")
_logsLogin.UserID = field.NewInt32(tableName, "user_id") _logsLogin.UserID = field.NewInt32(tableName, "user_id")
_logsLogin.Time = field.NewField(tableName, "time")
_logsLogin.fillFieldMap() _logsLogin.fillFieldMap()
@@ -47,12 +47,12 @@ type logsLogin struct {
ALL field.Asterisk ALL field.Asterisk
ID field.Int32 // 登录日志ID ID field.Int32 // 登录日志ID
IP field.String // IP地址 IP field.String // IP地址
Ua field.String // 用户代理 UA field.String // 用户代理
GrantType field.String // 授权类型authorization_code-授权码模式client_credentials-客户端凭证模式refresh_token-刷新令牌模式password-密码模式 GrantType field.String // 授权类型authorization_code-授权码模式client_credentials-客户端凭证模式refresh_token-刷新令牌模式password-密码模式
PasswordGrantType field.String // 密码模式子授权类型password-账号密码phone_code-手机验证码email_code-邮箱验证码 PasswordGrantType field.String // 密码模式子授权类型password-账号密码phone_code-手机验证码email_code-邮箱验证码
Success field.Bool // 登录是否成功 Success field.Bool // 登录是否成功
Time field.Field // 登录时间
UserID field.Int32 // 用户ID UserID field.Int32 // 用户ID
Time field.Field // 登录时间
fieldMap map[string]field.Expr fieldMap map[string]field.Expr
} }
@@ -71,12 +71,12 @@ func (l *logsLogin) updateTableName(table string) *logsLogin {
l.ALL = field.NewAsterisk(table) l.ALL = field.NewAsterisk(table)
l.ID = field.NewInt32(table, "id") l.ID = field.NewInt32(table, "id")
l.IP = field.NewString(table, "ip") 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.GrantType = field.NewString(table, "grant_type")
l.PasswordGrantType = field.NewString(table, "password_grant_type") l.PasswordGrantType = field.NewString(table, "password_grant_type")
l.Success = field.NewBool(table, "success") l.Success = field.NewBool(table, "success")
l.Time = field.NewField(table, "time")
l.UserID = field.NewInt32(table, "user_id") l.UserID = field.NewInt32(table, "user_id")
l.Time = field.NewField(table, "time")
l.fillFieldMap() l.fillFieldMap()
@@ -96,12 +96,12 @@ func (l *logsLogin) fillFieldMap() {
l.fieldMap = make(map[string]field.Expr, 8) l.fieldMap = make(map[string]field.Expr, 8)
l.fieldMap["id"] = l.ID l.fieldMap["id"] = l.ID
l.fieldMap["ip"] = l.IP l.fieldMap["ip"] = l.IP
l.fieldMap["ua"] = l.Ua l.fieldMap["ua"] = l.UA
l.fieldMap["grant_type"] = l.GrantType l.fieldMap["grant_type"] = l.GrantType
l.fieldMap["password_grant_type"] = l.PasswordGrantType l.fieldMap["password_grant_type"] = l.PasswordGrantType
l.fieldMap["success"] = l.Success l.fieldMap["success"] = l.Success
l.fieldMap["time"] = l.Time
l.fieldMap["user_id"] = l.UserID l.fieldMap["user_id"] = l.UserID
l.fieldMap["time"] = l.Time
} }
func (l logsLogin) clone(db *gorm.DB) logsLogin { func (l logsLogin) clone(db *gorm.DB) logsLogin {

View File

@@ -28,16 +28,16 @@ func newLogsRequest(db *gorm.DB, opts ...gen.DOOption) logsRequest {
tableName := _logsRequest.logsRequestDo.TableName() tableName := _logsRequest.logsRequestDo.TableName()
_logsRequest.ALL = field.NewAsterisk(tableName) _logsRequest.ALL = field.NewAsterisk(tableName)
_logsRequest.ID = field.NewInt32(tableName, "id") _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.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.Method = field.NewString(tableName, "method")
_logsRequest.Path = field.NewString(tableName, "path") _logsRequest.Path = field.NewString(tableName, "path")
_logsRequest.Latency = field.NewString(tableName, "latency")
_logsRequest.Status = field.NewInt32(tableName, "status") _logsRequest.Status = field.NewInt32(tableName, "status")
_logsRequest.Error = field.NewString(tableName, "error") _logsRequest.Error = field.NewString(tableName, "error")
_logsRequest.Time = field.NewField(tableName, "time") _logsRequest.Time = field.NewField(tableName, "time")
_logsRequest.Latency = field.NewString(tableName, "latency")
_logsRequest.fillFieldMap() _logsRequest.fillFieldMap()
@@ -49,16 +49,16 @@ type logsRequest struct {
ALL field.Asterisk ALL field.Asterisk
ID field.Int32 // 访问日志ID ID field.Int32 // 访问日志ID
Identity field.Int32 // 访客身份0-游客1-用户2-管理员3-公共服务4-安全服务5-内部服务
Visitor field.Int32 // 访客ID
IP field.String // IP地址 IP field.String // IP地址
Ua field.String // 用户代理 UA field.String // 用户代理
UserID field.Int32 // 用户ID
ClientID field.Int32 // 客户端ID
Method field.String // 请求方法 Method field.String // 请求方法
Path field.String // 请求路径 Path field.String // 请求路径
Latency field.String // 请求延迟
Status field.Int32 // 响应状态码 Status field.Int32 // 响应状态码
Error field.String // 错误信息 Error field.String // 错误信息
Time field.Field // 请求时间 Time field.Field // 请求时间
Latency field.String // 请求延迟
fieldMap map[string]field.Expr fieldMap map[string]field.Expr
} }
@@ -76,16 +76,16 @@ func (l logsRequest) As(alias string) *logsRequest {
func (l *logsRequest) updateTableName(table string) *logsRequest { func (l *logsRequest) updateTableName(table string) *logsRequest {
l.ALL = field.NewAsterisk(table) l.ALL = field.NewAsterisk(table)
l.ID = field.NewInt32(table, "id") 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.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.Method = field.NewString(table, "method")
l.Path = field.NewString(table, "path") l.Path = field.NewString(table, "path")
l.Latency = field.NewString(table, "latency")
l.Status = field.NewInt32(table, "status") l.Status = field.NewInt32(table, "status")
l.Error = field.NewString(table, "error") l.Error = field.NewString(table, "error")
l.Time = field.NewField(table, "time") l.Time = field.NewField(table, "time")
l.Latency = field.NewString(table, "latency")
l.fillFieldMap() l.fillFieldMap()
@@ -104,16 +104,16 @@ func (l *logsRequest) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
func (l *logsRequest) fillFieldMap() { func (l *logsRequest) fillFieldMap() {
l.fieldMap = make(map[string]field.Expr, 11) l.fieldMap = make(map[string]field.Expr, 11)
l.fieldMap["id"] = l.ID l.fieldMap["id"] = l.ID
l.fieldMap["identity"] = l.Identity
l.fieldMap["visitor"] = l.Visitor
l.fieldMap["ip"] = l.IP 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["method"] = l.Method
l.fieldMap["path"] = l.Path l.fieldMap["path"] = l.Path
l.fieldMap["latency"] = l.Latency
l.fieldMap["status"] = l.Status l.fieldMap["status"] = l.Status
l.fieldMap["error"] = l.Error l.fieldMap["error"] = l.Error
l.fieldMap["time"] = l.Time l.fieldMap["time"] = l.Time
l.fieldMap["latency"] = l.Latency
} }
func (l logsRequest) clone(db *gorm.DB) logsRequest { func (l logsRequest) clone(db *gorm.DB) logsRequest {

View File

@@ -31,12 +31,12 @@ func newProxy(db *gorm.DB, opts ...gen.DOOption) proxy {
_proxy.Version = field.NewInt32(tableName, "version") _proxy.Version = field.NewInt32(tableName, "version")
_proxy.Name = field.NewString(tableName, "name") _proxy.Name = field.NewString(tableName, "name")
_proxy.Host = field.NewString(tableName, "host") _proxy.Host = field.NewString(tableName, "host")
_proxy.Type = field.NewInt32(tableName, "type")
_proxy.Secret = field.NewString(tableName, "secret") _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.CreatedAt = field.NewField(tableName, "created_at")
_proxy.UpdatedAt = field.NewField(tableName, "updated_at") _proxy.UpdatedAt = field.NewField(tableName, "updated_at")
_proxy.DeletedAt = field.NewField(tableName, "deleted_at") _proxy.DeletedAt = field.NewField(tableName, "deleted_at")
_proxy.Status = field.NewInt32(tableName, "status")
_proxy.Edges = proxyHasManyEdges{ _proxy.Edges = proxyHasManyEdges{
db: db.Session(&gorm.Session{}), db: db.Session(&gorm.Session{}),
@@ -56,12 +56,12 @@ type proxy struct {
Version field.Int32 // 代理服务版本 Version field.Int32 // 代理服务版本
Name field.String // 代理服务名称 Name field.String // 代理服务名称
Host field.String // 代理服务地址 Host field.String // 代理服务地址
Type field.Int32 // 代理服务类型1-三方2-自有
Secret field.String // 代理服务密钥 Secret field.String // 代理服务密钥
Type field.Int32 // 代理服务类型1-三方2-自有
Status field.Int32 // 代理服务状态0-离线1-在线
CreatedAt field.Field // 创建时间 CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间 UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间 DeletedAt field.Field // 删除时间
Status field.Int32 // 代理服务状态0-离线1-在线
Edges proxyHasManyEdges Edges proxyHasManyEdges
fieldMap map[string]field.Expr fieldMap map[string]field.Expr
@@ -83,12 +83,12 @@ func (p *proxy) updateTableName(table string) *proxy {
p.Version = field.NewInt32(table, "version") p.Version = field.NewInt32(table, "version")
p.Name = field.NewString(table, "name") p.Name = field.NewString(table, "name")
p.Host = field.NewString(table, "host") p.Host = field.NewString(table, "host")
p.Type = field.NewInt32(table, "type")
p.Secret = field.NewString(table, "secret") 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.CreatedAt = field.NewField(table, "created_at")
p.UpdatedAt = field.NewField(table, "updated_at") p.UpdatedAt = field.NewField(table, "updated_at")
p.DeletedAt = field.NewField(table, "deleted_at") p.DeletedAt = field.NewField(table, "deleted_at")
p.Status = field.NewInt32(table, "status")
p.fillFieldMap() p.fillFieldMap()
@@ -110,12 +110,12 @@ func (p *proxy) fillFieldMap() {
p.fieldMap["version"] = p.Version p.fieldMap["version"] = p.Version
p.fieldMap["name"] = p.Name p.fieldMap["name"] = p.Name
p.fieldMap["host"] = p.Host p.fieldMap["host"] = p.Host
p.fieldMap["type"] = p.Type
p.fieldMap["secret"] = p.Secret p.fieldMap["secret"] = p.Secret
p.fieldMap["type"] = p.Type
p.fieldMap["status"] = p.Status
p.fieldMap["created_at"] = p.CreatedAt p.fieldMap["created_at"] = p.CreatedAt
p.fieldMap["updated_at"] = p.UpdatedAt p.fieldMap["updated_at"] = p.UpdatedAt
p.fieldMap["deleted_at"] = p.DeletedAt p.fieldMap["deleted_at"] = p.DeletedAt
p.fieldMap["status"] = p.Status
} }

View File

@@ -29,10 +29,10 @@ func newSession(db *gorm.DB, opts ...gen.DOOption) session {
_session.ALL = field.NewAsterisk(tableName) _session.ALL = field.NewAsterisk(tableName)
_session.ID = field.NewInt32(tableName, "id") _session.ID = field.NewInt32(tableName, "id")
_session.UserID = field.NewInt32(tableName, "user_id") _session.UserID = field.NewInt32(tableName, "user_id")
_session.AdminID = field.NewInt32(tableName, "admin_id")
_session.ClientID = field.NewInt32(tableName, "client_id") _session.ClientID = field.NewInt32(tableName, "client_id")
_session.IP = field.NewString(tableName, "ip") _session.IP = field.NewString(tableName, "ip")
_session.Ua = field.NewString(tableName, "ua") _session.UA = field.NewString(tableName, "ua")
_session.GrantType = field.NewString(tableName, "grant_type")
_session.AccessToken = field.NewString(tableName, "access_token") _session.AccessToken = field.NewString(tableName, "access_token")
_session.AccessTokenExpires = field.NewField(tableName, "access_token_expires") _session.AccessTokenExpires = field.NewField(tableName, "access_token_expires")
_session.RefreshToken = field.NewString(tableName, "refresh_token") _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.CreatedAt = field.NewField(tableName, "created_at")
_session.UpdatedAt = field.NewField(tableName, "updated_at") _session.UpdatedAt = field.NewField(tableName, "updated_at")
_session.DeletedAt = field.NewField(tableName, "deleted_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() _session.fillFieldMap()
@@ -53,10 +70,10 @@ type session struct {
ALL field.Asterisk ALL field.Asterisk
ID field.Int32 // 会话ID ID field.Int32 // 会话ID
UserID field.Int32 // 用户ID UserID field.Int32 // 用户ID
AdminID field.Int32 // 管理员ID
ClientID field.Int32 // 客户端ID ClientID field.Int32 // 客户端ID
IP field.String // IP地址 IP field.String // IP地址
Ua field.String // 用户代理 UA field.String // 用户代理
GrantType field.String // 授权类型authorization_code-授权码模式client_credentials-客户端凭证模式refresh_token-刷新令牌模式password-密码模式
AccessToken field.String // 访问令牌 AccessToken field.String // 访问令牌
AccessTokenExpires field.Field // 访问令牌过期时间 AccessTokenExpires field.Field // 访问令牌过期时间
RefreshToken field.String // 刷新令牌 RefreshToken field.String // 刷新令牌
@@ -65,6 +82,11 @@ type session struct {
CreatedAt field.Field // 创建时间 CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间 UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间 DeletedAt field.Field // 删除时间
User sessionBelongsToUser
Admin sessionBelongsToAdmin
Client sessionBelongsToClient
fieldMap map[string]field.Expr fieldMap map[string]field.Expr
} }
@@ -83,10 +105,10 @@ func (s *session) updateTableName(table string) *session {
s.ALL = field.NewAsterisk(table) s.ALL = field.NewAsterisk(table)
s.ID = field.NewInt32(table, "id") s.ID = field.NewInt32(table, "id")
s.UserID = field.NewInt32(table, "user_id") s.UserID = field.NewInt32(table, "user_id")
s.AdminID = field.NewInt32(table, "admin_id")
s.ClientID = field.NewInt32(table, "client_id") s.ClientID = field.NewInt32(table, "client_id")
s.IP = field.NewString(table, "ip") s.IP = field.NewString(table, "ip")
s.Ua = field.NewString(table, "ua") s.UA = field.NewString(table, "ua")
s.GrantType = field.NewString(table, "grant_type")
s.AccessToken = field.NewString(table, "access_token") s.AccessToken = field.NewString(table, "access_token")
s.AccessTokenExpires = field.NewField(table, "access_token_expires") s.AccessTokenExpires = field.NewField(table, "access_token_expires")
s.RefreshToken = field.NewString(table, "refresh_token") s.RefreshToken = field.NewString(table, "refresh_token")
@@ -111,13 +133,13 @@ func (s *session) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
} }
func (s *session) fillFieldMap() { 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["id"] = s.ID
s.fieldMap["user_id"] = s.UserID s.fieldMap["user_id"] = s.UserID
s.fieldMap["admin_id"] = s.AdminID
s.fieldMap["client_id"] = s.ClientID s.fieldMap["client_id"] = s.ClientID
s.fieldMap["ip"] = s.IP s.fieldMap["ip"] = s.IP
s.fieldMap["ua"] = s.Ua s.fieldMap["ua"] = s.UA
s.fieldMap["grant_type"] = s.GrantType
s.fieldMap["access_token"] = s.AccessToken s.fieldMap["access_token"] = s.AccessToken
s.fieldMap["access_token_expires"] = s.AccessTokenExpires s.fieldMap["access_token_expires"] = s.AccessTokenExpires
s.fieldMap["refresh_token"] = s.RefreshToken s.fieldMap["refresh_token"] = s.RefreshToken
@@ -126,18 +148,271 @@ func (s *session) fillFieldMap() {
s.fieldMap["created_at"] = s.CreatedAt s.fieldMap["created_at"] = s.CreatedAt
s.fieldMap["updated_at"] = s.UpdatedAt s.fieldMap["updated_at"] = s.UpdatedAt
s.fieldMap["deleted_at"] = s.DeletedAt s.fieldMap["deleted_at"] = s.DeletedAt
} }
func (s session) clone(db *gorm.DB) session { func (s session) clone(db *gorm.DB) session {
s.sessionDo.ReplaceConnPool(db.Statement.ConnPool) 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 return s
} }
func (s session) replaceDB(db *gorm.DB) session { func (s session) replaceDB(db *gorm.DB) session {
s.sessionDo.ReplaceDB(db) 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 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 } type sessionDo struct{ gen.DO }
func (s sessionDo) Debug() *sessionDo { func (s sessionDo) Debug() *sessionDo {

View File

@@ -37,16 +37,16 @@ func newTrade(db *gorm.DB, opts ...gen.DOOption) trade {
_trade.Amount = field.NewField(tableName, "amount") _trade.Amount = field.NewField(tableName, "amount")
_trade.Payment = field.NewField(tableName, "payment") _trade.Payment = field.NewField(tableName, "payment")
_trade.Method = field.NewInt32(tableName, "method") _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.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.PaymentURL = field.NewString(tableName, "payment_url")
_trade.CompletedAt = field.NewField(tableName, "completed_at") _trade.CompletedAt = field.NewField(tableName, "completed_at")
_trade.CanceledAt = field.NewField(tableName, "canceled_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() _trade.fillFieldMap()
@@ -65,18 +65,18 @@ type trade struct {
Subject field.String // 订单主题 Subject field.String // 订单主题
Remark field.String // 订单备注 Remark field.String // 订单备注
Amount field.Field // 订单总金额 Amount field.Field // 订单总金额
Payment field.Field // 支付金额 Payment field.Field // 实际支付金额
Method field.Int32 // 支付方式1-支付宝2-微信3-商福通渠道支付宝,4-商福通渠道微信 Method field.Int32 // 支付方式1-支付宝2-微信3-商福通4-商福通渠道支付宝,5-商福通渠道微信
Status field.Int32 // 订单状态0-待支付1-已支付2-已取消
CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
Acquirer field.Int32 // 收单机构1-支付宝2-微信3-银联
Platform field.Int32 // 支付平台1-电脑网站2-手机网站 Platform field.Int32 // 支付平台1-电脑网站2-手机网站
Acquirer field.Int32 // 收单机构1-支付宝2-微信3-银联
Status field.Int32 // 订单状态0-待支付1-已支付2-已取消
Refunded field.Bool
PaymentURL field.String // 支付链接 PaymentURL field.String // 支付链接
CompletedAt field.Field // 支付时间 CompletedAt field.Field // 支付时间
CanceledAt field.Field // 取消时间 CanceledAt field.Field // 取消时间
Refunded field.Bool CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
fieldMap map[string]field.Expr fieldMap map[string]field.Expr
} }
@@ -103,16 +103,16 @@ func (t *trade) updateTableName(table string) *trade {
t.Amount = field.NewField(table, "amount") t.Amount = field.NewField(table, "amount")
t.Payment = field.NewField(table, "payment") t.Payment = field.NewField(table, "payment")
t.Method = field.NewInt32(table, "method") 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.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.PaymentURL = field.NewString(table, "payment_url")
t.CompletedAt = field.NewField(table, "completed_at") t.CompletedAt = field.NewField(table, "completed_at")
t.CanceledAt = field.NewField(table, "canceled_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() t.fillFieldMap()
@@ -140,16 +140,16 @@ func (t *trade) fillFieldMap() {
t.fieldMap["amount"] = t.Amount t.fieldMap["amount"] = t.Amount
t.fieldMap["payment"] = t.Payment t.fieldMap["payment"] = t.Payment
t.fieldMap["method"] = t.Method 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["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["payment_url"] = t.PaymentURL
t.fieldMap["completed_at"] = t.CompletedAt t.fieldMap["completed_at"] = t.CompletedAt
t.fieldMap["canceled_at"] = t.CanceledAt 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 { func (t trade) clone(db *gorm.DB) trade {

View File

@@ -1,7 +1,7 @@
package web package web
import ( import (
"platform/web/core" auth2 "platform/web/auth"
"platform/web/handlers" "platform/web/handlers"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
@@ -12,7 +12,7 @@ func ApplyRouters(app *fiber.App) {
// 认证 // 认证
auth := api.Group("/auth") auth := api.Group("/auth")
auth.Post("/token", handlers.Token) auth.Post("/token", auth2.Token)
auth.Post("/revoke", handlers.Revoke) auth.Post("/revoke", handlers.Revoke)
auth.Post("/introspect", handlers.Introspect) auth.Post("/introspect", handlers.Introspect)
auth.Post("/verify/sms", handlers.SmsCode) auth.Post("/verify/sms", handlers.SmsCode)
@@ -47,7 +47,6 @@ func ApplyRouters(app *fiber.App) {
channel.Post("/list", handlers.ListChannels) channel.Post("/list", handlers.ListChannels)
channel.Post("/create", handlers.CreateChannel) channel.Post("/create", handlers.CreateChannel)
channel.Post("/remove", handlers.RemoveChannels) channel.Post("/remove", handlers.RemoveChannels)
channel.Post("/remove/by-task", handlers.RemoveChannelByTask)
// 交易 // 交易
trade := api.Group("/trade") trade := api.Group("/trade")
@@ -75,12 +74,6 @@ func ApplyRouters(app *fiber.App) {
edge.Post("/all", handlers.AllEdgesAvailable) edge.Post("/all", handlers.AllEdgesAvailable)
// 临时 // 临时
app.Get("/test", func(c *fiber.Ctx) error { debug := app.Group("/debug")
return core.NewBizErr("测试错误") debug.Get("/sms/:phone", handlers.DebugGetSmsCode)
})
// 异步任务客户端
tasks := api.Group("/tasks")
tasks.Post("/channel/remove", handlers.RemoveChannelByTask)
tasks.Post("/trade/cancel", handlers.TradeCancelByTask)
} }

View File

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

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/gofiber/fiber/v2"
"log/slog" "log/slog"
"math" "math"
"math/rand/v2" "math/rand/v2"
@@ -15,15 +14,17 @@ import (
edge2 "platform/web/domains/edge" edge2 "platform/web/domains/edge"
proxy2 "platform/web/domains/proxy" proxy2 "platform/web/domains/proxy"
resource2 "platform/web/domains/resource" resource2 "platform/web/domains/resource"
"platform/web/events"
g "platform/web/globals" g "platform/web/globals"
"platform/web/globals/orm" "platform/web/globals/orm"
m "platform/web/models" m "platform/web/models"
q "platform/web/queries" q "platform/web/queries"
"platform/web/tasks"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/gofiber/fiber/v2"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
"gorm.io/gen/field" "gorm.io/gen/field"
@@ -296,7 +297,7 @@ func (s *channelService) CreateChannel(
ids[i] = channels[i].ID ids[i] = channels[i].ID
} }
_, err = g.Asynq.Enqueue( _, err = g.Asynq.Enqueue(
tasks.NewRemoveChannel(ids), events.NewRemoveChannel(ids),
asynq.ProcessIn(duration), asynq.ProcessIn(duration),
) )
if err != nil { if err != nil {

View File

@@ -12,11 +12,11 @@ import (
"platform/web/core" "platform/web/core"
coupon2 "platform/web/domains/coupon" coupon2 "platform/web/domains/coupon"
trade2 "platform/web/domains/trade" trade2 "platform/web/domains/trade"
"platform/web/events"
g "platform/web/globals" g "platform/web/globals"
"platform/web/globals/orm" "platform/web/globals/orm"
m "platform/web/models" m "platform/web/models"
q "platform/web/queries" q "platform/web/queries"
"platform/web/tasks"
"time" "time"
"github.com/shopspring/decimal" "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, TradeNo: tradeNo,
Method: method, Method: method,
})) }))
@@ -417,7 +417,7 @@ func (s *tradeService) CancelTrade(tradeNo string, method trade2.Method, now tim
MchOrderNo: &tradeNo, MchOrderNo: &tradeNo,
}) })
if err != nil { if err != nil {
slog.Debug(fmt.Sprintf("订单无需关闭%s", err.Error())) slog.Debug(fmt.Sprintf("订单无需关闭: %s", err.Error()))
return nil return nil
} }
@@ -546,11 +546,11 @@ func (s *tradeService) CheckTrade(data *ModifyTradeData) (*CheckTradeResult, err
return nil, core.NewBizErr("订单不存在") return nil, core.NewBizErr("订单不存在")
} }
return nil, core.NewServErr( return nil, core.NewServErr(
fmt.Sprintf("微信上游接口异常code=%vmessage=%v", apiErr.Code, apiErr.Message), fmt.Sprintf("微信上游接口异常: code=%vmessage=%v", apiErr.Code, apiErr.Message),
apiErr, apiErr,
) )
} }
return nil, core.NewServErr(fmt.Sprintf("微信上游支付接口异常%s", err.Error())) return nil, core.NewServErr(fmt.Sprintf("微信上游支付接口异常: %s", err.Error()))
} }
// 填充返回值 // 填充返回值

View File

@@ -19,28 +19,6 @@ import (
var Verifier = &verifierService{} 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 { type verifierService struct {
} }
@@ -148,6 +126,43 @@ func (s *verifierService) VerifySms(ctx context.Context, phone, code string) err
return nil 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 { func smsKey(phone string, purpose VerifierSmsPurpose) string {
return fmt.Sprintf("verify:sms:%d:%s", purpose, phone) 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 "发送频率过快"
}

41
web/tasks/task.go Normal file
View File

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

View File

@@ -1,166 +1,89 @@
package web package web
import ( import (
"github.com/gofiber/fiber/v2" "context"
"github.com/gofiber/fiber/v2/middleware/logger" "fmt"
"github.com/gofiber/fiber/v2/middleware/recover"
"github.com/gofiber/fiber/v2/middleware/requestid"
"github.com/google/uuid"
"github.com/jxskiss/base62"
"log/slog" "log/slog"
"net/http"
_ "net/http/pprof" _ "net/http/pprof"
"platform/web/auth" "platform/web/events"
g "platform/web/globals" base "platform/web/globals"
q "platform/web/queries" "platform/web/tasks"
"runtime"
"strings" "github.com/gofiber/fiber/v2"
"time" "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 err := base.Init(ctx)
} if err != nil {
return fmt.Errorf("初始化依赖失败: %w", err)
type Server struct {
config *Config
fiber *fiber.App
}
func New(config *Config) (*Server, error) {
_config := config
if config == nil {
_config = &Config{}
} }
return &Server{ // 运行服务
config: _config, g.Go(func() error {
}, nil 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 fiber := fiber.New(fiber.Config{
g.Init()
q.SetDefault(g.DB)
// config
s.fiber = fiber.New(fiber.Config{
ProxyHeader: fiber.HeaderXForwardedFor, ProxyHeader: fiber.HeaderXForwardedFor,
ErrorHandler: ErrorHandler, ErrorHandler: ErrorHandler,
}) })
// middlewares ApplyMiddlewares(fiber)
s.fiber.Use(newRecover()) ApplyRouters(fiber)
s.fiber.Use(newRequestId())
s.fiber.Use(newLogger())
// routes // 停止服务
ApplyRouters(s.fiber)
// pprof
go func() { go func() {
runtime.SetBlockProfileRate(1) <-ctx.Done()
err := http.ListenAndServe(":6060", nil) err := fiber.Shutdown()
if err != nil { if err != nil {
slog.Error("pprof 服务错误", slog.Any("err", err)) slog.Error("服务停止失败", "error", err)
} }
}() }()
// listen // 启动服务
slog.Info("服务开始监听 :8080") slog.Info("web 服务开始监听 :8080")
err := s.fiber.Listen("0.0.0.0:8080") err := fiber.Listen("0.0.0.0:8080")
if err != nil { 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 return nil
} }
func (s *Server) Stop() { func RunTask(ctx context.Context) error {
err := g.ExitRedis()
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 { if err != nil {
slog.Error("Failed to close Redis connection", slog.Any("err", err)) return fmt.Errorf("任务服务运行失败: %w", err)
} }
err = g.ExitOrm() return nil
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))
}
} }
// 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