通道的增删接口实现,数据表和目录结构调整

This commit is contained in:
2025-03-26 14:57:44 +08:00
parent 727297f4ee
commit 1ac87f79c6
36 changed files with 6753 additions and 153 deletions

View File

@@ -19,6 +19,12 @@
- [ ] Limiter - [ ] Limiter
- [ ] Compress - [ ] Compress
有些地方在用手动事务,有时间改成自动事务
remote 用环境变量保存账号密码!
重新手动实现 model 层
环境变量配置默认会话配置 环境变量配置默认会话配置
oauth token 验证授权范围 oauth token 验证授权范围
@@ -50,3 +56,8 @@ captcha_id 关联用户本机信息,实现验证码设备绑定(或者其他
| proxy/shared-static | pss | 动态代理 | | proxy/shared-static | pss | 动态代理 |
| proxy/shared-rotate | psr | 隧道代理 | | proxy/shared-rotate | psr | 隧道代理 |
| proxy/private-static | pps | 独享代理 | | proxy/private-static | pps | 独享代理 |
### 外部服务
服务器ip 110.40.82.248
api账密api:123456

View File

@@ -4,10 +4,10 @@ import (
"log/slog" "log/slog"
"os" "os"
"os/signal" "os/signal"
"platform/init/env" "platform/pkg/env"
"platform/init/logs" "platform/pkg/logs"
"platform/init/orm" "platform/pkg/orm"
"platform/init/rds" "platform/pkg/rds"
"platform/web" "platform/web"
"syscall" "syscall"
) )

7
cmd/playground/main.go Normal file
View File

@@ -0,0 +1,7 @@
package main
import "encoding/base64"
func main() {
println(base64.URLEncoding.EncodeToString([]byte("app:123456")))
}

68
cmd/tasks/main.go Normal file
View File

@@ -0,0 +1,68 @@
package main
import (
"context"
"errors"
"log/slog"
"platform/pkg/env"
"platform/pkg/logs"
"platform/pkg/orm"
"platform/pkg/rds"
"time"
"github.com/redis/go-redis/v9"
)
func main() {
Start()
}
func Start() {
ctx := context.Background()
env.Init()
logs.Init()
rds.Init()
orm.Init()
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for curr := range ticker.C {
err := process(ctx, curr)
if err != nil {
panic(err)
}
}
}
func process(ctx context.Context, curr time.Time) error {
// 获取并删除
script := redis.NewScript(`
local result = redis.call('ZRANGEBYSCORE', KEYS[1], 0, ARGV[1])
if #result > 0 then
redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[1])
end
return result
`)
// 计算时间范围
// 执行脚本
result, err := script.Run(ctx, rds.Client, []string{"tasks:session"}, curr.Unix()).Result()
if err != nil {
return err
}
// 处理结果
list, ok := result.([]string)
if !ok {
return errors.New("failed to convert result to []string")
}
for _, item := range list {
// 从数据库删除授权信息
slog.Debug(item)
}
return nil
}

85
cmd/wrapper/main.go Normal file
View File

@@ -0,0 +1,85 @@
package main
import (
"context"
"log/slog"
"os"
"os/signal"
"platform/pkg/env"
"platform/pkg/logs"
"time"
)
func main() {
// 初始化环境
env.Init()
logs.Init()
// 上下文
ctx, cancel := context.WithCancel(context.Background())
// 监听退出
exit := make(chan os.Signal, 1)
defer close(exit)
signal.Notify(exit, os.Interrupt, os.Kill)
defer signal.Stop(exit)
// 启动管理子线程
errCh := make(chan error, 1)
go func() {
defer close(errCh)
err := start(ctx)
if err != nil {
errCh <- err
}
}()
select {
case <-exit:
slog.Debug("exit by signal")
cancel()
case err := <-errCh:
slog.Error("exit by error", "error", err)
}
}
// 连接池,硬编码提供 10000 的容量
var idle = 100
var maximum = 10000
var pool = make(map[string]*Node, 10000)
var tick = 1 * time.Minute
type Node struct {
Ip string
}
var last time.Time
func start(ctx context.Context) error {
ticker := time.NewTicker(tick)
go func() {
<-ctx.Done()
ticker.Stop()
}()
for curr := range ticker.C {
last = curr
go func() {
process(ctx, curr)
}()
}
return nil
}
func process(ctx context.Context, curr time.Time) {
// 查询节点状态
// 筛选在线节点添加到节点池
//
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1407
docs/系统结构.excalidraw Normal file

File diff suppressed because it is too large Load Diff

7
go.mod
View File

@@ -7,9 +7,9 @@ require (
github.com/gofiber/fiber/v2 v2.52.6 github.com/gofiber/fiber/v2 v2.52.6
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
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.3.0 github.com/redis/go-redis/v9 v9.3.0
github.com/stretchr/testify v1.8.1
golang.org/x/crypto v0.17.0 golang.org/x/crypto v0.17.0
gorm.io/driver/postgres v1.5.11 gorm.io/driver/postgres v1.5.11
gorm.io/gen v0.3.26 gorm.io/gen v0.3.26
@@ -21,7 +21,6 @@ require (
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // 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/go-sql-driver/mysql v1.7.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
@@ -31,13 +30,10 @@ 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/klauspost/compress v1.17.9 // indirect github.com/klauspost/compress v1.17.9 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // 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.16 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect
@@ -47,7 +43,6 @@ require (
golang.org/x/sys v0.28.0 // indirect golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.23.0 // indirect golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.26.0 // indirect golang.org/x/tools v0.26.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c // indirect gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c // indirect
gorm.io/driver/mysql v1.5.7 // indirect gorm.io/driver/mysql v1.5.7 // indirect
gorm.io/hints v1.1.0 // indirect gorm.io/hints v1.1.0 // indirect

15
go.sum
View File

@@ -10,7 +10,6 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -41,12 +40,10 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y=
github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@@ -67,15 +64,9 @@ github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0
github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/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.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@@ -132,8 +123,6 @@ golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

View File

@@ -3,7 +3,7 @@ package logs
import ( import (
"log/slog" "log/slog"
"os" "os"
"platform/init/env" "platform/pkg/env"
"time" "time"
"github.com/lmittmann/tint" "github.com/lmittmann/tint"

View File

@@ -3,7 +3,7 @@ package orm
import ( import (
"fmt" "fmt"
"log/slog" "log/slog"
"platform/init/env" "platform/pkg/env"
"platform/web/queries" "platform/web/queries"
"gorm.io/gorm" "gorm.io/gorm"

View File

@@ -2,7 +2,7 @@ package rds
import ( import (
"net" "net"
"platform/init/env" "platform/pkg/env"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
) )

126
pkg/remote/remote.go Normal file
View File

@@ -0,0 +1,126 @@
package remote
import (
"encoding/json"
"errors"
"io"
"net/http"
"strings"
)
type client struct {
gatewayUrl string
username string
password string
cloudUrl string
token string
}
var Client client
func Init() error {
// todo 从环境变量中获取参数
Client = client{
gatewayUrl: "http://110.40.82.248:9990",
username: "api",
password: "123456",
cloudUrl: "http://103.139.212.110",
}
return nil
}
type PortConfig struct {
Port string `json:"port"`
Edge []string `json:"edge"`
Type string `json:"type"`
Time int `json:"time"`
Status bool `json:"status"`
Rate int `json:"rate"`
Whitelist []string `json:"whitelist"`
Userpass string `json:"userpass"`
AutoEdgeConfig AutoEdgeConfig `json:"auto_edge_config"`
}
type AutoEdgeConfig struct {
Province string `json:"province"`
City string `json:"city"`
Isp string `json:"isp"`
Count int `json:"count"`
PacketLoss int `json:"packet_loss"`
}
func (c *client) PortConfigs(params ...PortConfig) error {
resp, err := c.requestCloud("/port/configs", params)
if err != nil {
return err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(resp.Body)
if resp.StatusCode != http.StatusOK {
return errors.New("failed to configure port")
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
var result map[string]any
err = json.Unmarshal(body, &result)
if err != nil {
return err
}
if result["code"] != 0 {
return errors.New("failed to configure port")
}
return nil
}
func (c *client) requestGateway(url string, data any) (*http.Response, error) {
jsonData, err := json.Marshal(data)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", c.gatewayUrl+url, strings.NewReader(string(jsonData)))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth(c.username, c.password)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}
func (c *client) requestCloud(url string, data any) (*http.Response, error) {
jsonData, err := json.Marshal(data)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", c.cloudUrl+url, strings.NewReader(string(jsonData)))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("token", c.token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}

View File

@@ -516,16 +516,19 @@ create table resource (
user_id int not null references "user" (id) user_id int not null references "user" (id)
on update cascade on update cascade
on delete cascade, on delete cascade,
active bool default true,
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);
create index resource_active_index on resource (active);
-- resource表字段注释 -- resource表字段注释
comment on table resource is '套餐表'; comment on table resource is '套餐表';
comment on column resource.id is '套餐ID'; comment on column resource.id is '套餐ID';
comment on column resource.user_id is '用户ID'; comment on column resource.user_id is '用户ID';
comment on column resource.active is '套餐状态';
comment on column resource.created_at is '创建时间'; comment on column resource.created_at is '创建时间';
comment on column resource.updated_at is '更新时间'; comment on column resource.updated_at is '更新时间';
comment on column resource.deleted_at is '删除时间'; comment on column resource.deleted_at is '删除时间';
@@ -534,19 +537,19 @@ comment on column resource.deleted_at is '删除时间';
drop table if exists resource_pss cascade; drop table if exists resource_pss cascade;
create table resource_pss ( create table resource_pss (
id serial primary key, id serial primary key,
resource_id int not null references resource (id) resource_id int not null references resource (id)
on update cascade on update cascade
on delete cascade, on delete cascade,
active bool not null default false,
type int, type int,
live int, live int,
quota int, quota int,
used int, used int,
expire timestamp, expire timestamp,
limit_day int, daily_limit int,
last_used timestamp, daily_used int,
created_at timestamp default current_timestamp, daily_last timestamp,
updated_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp,
deleted_at timestamp deleted_at timestamp
); );
create index resource_pss_resource_id_index on resource_pss (resource_id); create index resource_pss_resource_id_index on resource_pss (resource_id);
@@ -555,14 +558,14 @@ create index resource_pss_resource_id_index on resource_pss (resource_id);
comment on table resource_pss is '动态代理套餐表'; comment on table resource_pss is '动态代理套餐表';
comment on column resource_pss.id is 'ID'; comment on column resource_pss.id is 'ID';
comment on column resource_pss.resource_id is '套餐ID'; comment on column resource_pss.resource_id is '套餐ID';
comment on column resource_pss.active is '是否启用';
comment on column resource_pss.type is '套餐类型1-包时2-包量'; comment on column resource_pss.type is '套餐类型1-包时2-包量';
comment on column resource_pss.live is '可用时长(秒)'; comment on column resource_pss.live is '可用时长(秒)';
comment on column resource_pss.quota is '配额数量'; comment on column resource_pss.quota is '配额数量';
comment on column resource_pss.used is '已用数量'; comment on column resource_pss.used is '已用数量';
comment on column resource_pss.expire is '过期时间'; comment on column resource_pss.expire is '过期时间';
comment on column resource_pss.limit_day is '每日限'; comment on column resource_pss.daily_limit is '每日限';
comment on column resource_pss.last_used is '最后提取时间'; comment on column resource_pss.daily_used is '今日已用数量';
comment on column resource_pss.daily_last is '今日最后使用时间';
comment on column resource_pss.created_at is '创建时间'; comment on column resource_pss.created_at is '创建时间';
comment on column resource_pss.updated_at is '更新时间'; comment on column resource_pss.updated_at is '更新时间';
comment on column resource_pss.deleted_at is '删除时间'; comment on column resource_pss.deleted_at is '删除时间';
@@ -571,16 +574,15 @@ comment on column resource_pss.deleted_at is '删除时间';
drop table if exists resource_psr cascade; drop table if exists resource_psr cascade;
create table resource_psr ( create table resource_psr (
id serial primary key, id serial primary key,
resource_id int not null references resource (id) resource_id int not null references resource (id)
on update cascade on update cascade
on delete cascade, on delete cascade,
active bool not null default false,
live int, live int,
conn int, conn int,
expire timestamp, expire timestamp,
used bool, used bool,
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_psr_resource_id_index on resource_psr (resource_id); create index resource_psr_resource_id_index on resource_psr (resource_id);
@@ -589,7 +591,6 @@ create index resource_psr_resource_id_index on resource_psr (resource_id);
comment on table resource_psr is '隧道代理套餐表'; comment on table resource_psr is '隧道代理套餐表';
comment on column resource_psr.id is 'ID'; comment on column resource_psr.id is 'ID';
comment on column resource_psr.resource_id is '套餐ID'; comment on column resource_psr.resource_id is '套餐ID';
comment on column resource_psr.active is '是否启用';
comment on column resource_psr.live is '轮换周期(秒)'; comment on column resource_psr.live is '轮换周期(秒)';
comment on column resource_psr.conn is '最大连接数'; comment on column resource_psr.conn is '最大连接数';
comment on column resource_psr.expire is '过期时间'; comment on column resource_psr.expire is '过期时间';
@@ -602,12 +603,11 @@ comment on column resource_psr.deleted_at is '删除时间';
drop table if exists resource_pps cascade; drop table if exists resource_pps cascade;
create table resource_pps ( create table resource_pps (
id serial primary key, id serial primary key,
resource_id int not null references resource (id) resource_id int not null references resource (id)
on update cascade on update cascade
on delete cascade, on delete cascade,
active bool not null default false, 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_pps_resource_id_index on resource_pps (resource_id); create index resource_pps_resource_id_index on resource_pps (resource_id);
@@ -616,7 +616,6 @@ create index resource_pps_resource_id_index on resource_pps (resource_id);
comment on table resource_pps is '独享代理套餐表'; comment on table resource_pps is '独享代理套餐表';
comment on column resource_pps.id is 'ID'; comment on column resource_pps.id is 'ID';
comment on column resource_pps.resource_id is '套餐ID'; comment on column resource_pps.resource_id is '套餐ID';
comment on column resource_pps.active is '是否启用';
comment on column resource_pps.created_at is '创建时间'; comment on column resource_pps.created_at is '创建时间';
comment on column resource_pps.updated_at is '更新时间'; comment on column resource_pps.updated_at is '更新时间';
comment on column resource_pps.deleted_at is '删除时间'; comment on column resource_pps.deleted_at is '删除时间';

View File

@@ -9,8 +9,8 @@ import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
// Protect 创建针对单个路由的鉴权中间件 // PermitUser 创建针对单个路由的鉴权中间件
func Protect(permissions ...string) fiber.Handler { func PermitUser(permissions ...string) fiber.Handler {
return func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error {
// 获取令牌 // 获取令牌
var header = c.Get("Authorization") var header = c.Get("Authorization")
@@ -32,7 +32,17 @@ func Protect(permissions ...string) fiber.Handler {
} }
// 检查权限 // 检查权限
if len(permissions) > 0 && !auth.AnyPermission(permissions...) { switch auth.Payload.Type {
case services.PayloadAdmin:
// 管理员不需要权限检查
case services.PayloadUser:
if len(permissions) > 0 && !auth.AnyPermission(permissions...) {
return c.Status(fiber.StatusForbidden).JSON(common.ErrResp{
Error: true,
Message: "拒绝访问",
})
}
default:
return c.Status(fiber.StatusForbidden).JSON(common.ErrResp{ return c.Status(fiber.StatusForbidden).JSON(common.ErrResp{
Error: true, Error: true,
Message: "拒绝访问", Message: "拒绝访问",

13
web/common/errors.go Normal file
View File

@@ -0,0 +1,13 @@
package common
type AuthUnAuthorizedErr string
func (e AuthUnAuthorizedErr) Error() string {
return string(e)
}
type AuthForbiddenErr string
func (e AuthForbiddenErr) Error() string {
return string(e)
}

View File

@@ -14,7 +14,7 @@ import (
type CreateChannelReq struct { type CreateChannelReq struct {
Region string `json:"region" validate:"required"` Region string `json:"region" validate:"required"`
Provider string `json:"provider" validate:"required"` Provider string `json:"provider" validate:"required"`
Protocol services.Protocol `json:"protocol" validate:"required,oneof=socks5 http https"` Protocol services.ChannelProtocol `json:"protocol" validate:"required,oneof=socks5 http https"`
ResourceId int `json:"resource_id" validate:"required"` ResourceId int `json:"resource_id" validate:"required"`
Count int `json:"count" validate:"required"` Count int `json:"count" validate:"required"`
ResultType CreateChannelResultType `json:"return_type" validate:"required,oneof=json text"` ResultType CreateChannelResultType `json:"return_type" validate:"required,oneof=json text"`

View File

@@ -16,6 +16,7 @@ const TableNameResource = "resource"
type Resource struct { type Resource struct {
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:套餐ID" json:"id"` // 套餐ID ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:套餐ID" json:"id"` // 套餐ID
UserID int32 `gorm:"column:user_id;not null;comment:用户ID" json:"user_id"` // 用户ID UserID int32 `gorm:"column:user_id;not null;comment:用户ID" json:"user_id"` // 用户ID
Active bool `gorm:"column:active;default:true;comment:套餐状态" json:"active"` // 套餐状态
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间 DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间

View File

@@ -16,7 +16,6 @@ const TableNameResourcePps = "resource_pps"
type ResourcePps struct { type ResourcePps struct {
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:ID" json:"id"` // ID ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:ID" json:"id"` // ID
ResourceID int32 `gorm:"column:resource_id;not null;comment:套餐ID" json:"resource_id"` // 套餐ID ResourceID int32 `gorm:"column:resource_id;not null;comment:套餐ID" json:"resource_id"` // 套餐ID
Active bool `gorm:"column:active;not null;comment:是否启用" json:"active"` // 是否启用
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间 DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间

View File

@@ -16,7 +16,6 @@ const TableNameResourcePsr = "resource_psr"
type ResourcePsr struct { type ResourcePsr struct {
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:ID" json:"id"` // ID ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:ID" json:"id"` // ID
ResourceID int32 `gorm:"column:resource_id;not null;comment:套餐ID" json:"resource_id"` // 套餐ID ResourceID int32 `gorm:"column:resource_id;not null;comment:套餐ID" json:"resource_id"` // 套餐ID
Active bool `gorm:"column:active;not null;comment:是否启用" json:"active"` // 是否启用
Live int32 `gorm:"column:live;comment:轮换周期(秒)" json:"live"` // 轮换周期(秒) Live int32 `gorm:"column:live;comment:轮换周期(秒)" json:"live"` // 轮换周期(秒)
Conn int32 `gorm:"column:conn;comment:最大连接数" json:"conn"` // 最大连接数 Conn int32 `gorm:"column:conn;comment:最大连接数" json:"conn"` // 最大连接数
Expire time.Time `gorm:"column:expire;comment:过期时间" json:"expire"` // 过期时间 Expire time.Time `gorm:"column:expire;comment:过期时间" json:"expire"` // 过期时间

View File

@@ -16,14 +16,14 @@ const TableNameResourcePss = "resource_pss"
type ResourcePss struct { type ResourcePss struct {
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:ID" json:"id"` // ID ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:ID" json:"id"` // ID
ResourceID int32 `gorm:"column:resource_id;not null;comment:套餐ID" json:"resource_id"` // 套餐ID ResourceID int32 `gorm:"column:resource_id;not null;comment:套餐ID" json:"resource_id"` // 套餐ID
Active bool `gorm:"column:active;not null;comment:是否启用" json:"active"` // 是否启用
Type int32 `gorm:"column:type;comment:套餐类型1-包时2-包量" json:"type"` // 套餐类型1-包时2-包量 Type int32 `gorm:"column:type;comment:套餐类型1-包时2-包量" json:"type"` // 套餐类型1-包时2-包量
Live int32 `gorm:"column:live;comment:可用时长(秒)" json:"live"` // 可用时长(秒) Live int32 `gorm:"column:live;comment:可用时长(秒)" json:"live"` // 可用时长(秒)
Quota int32 `gorm:"column:quota;comment:配额数量" json:"quota"` // 配额数量 Quota int32 `gorm:"column:quota;comment:配额数量" json:"quota"` // 配额数量
Used int32 `gorm:"column:used;comment:已用数量" json:"used"` // 已用数量 Used int32 `gorm:"column:used;comment:已用数量" json:"used"` // 已用数量
Expire time.Time `gorm:"column:expire;comment:过期时间" json:"expire"` // 过期时间 Expire time.Time `gorm:"column:expire;comment:过期时间" json:"expire"` // 过期时间
LimitDay int32 `gorm:"column:limit_day;comment:每日限" json:"limit_day"` // 每日限 DailyLimit int32 `gorm:"column:daily_limit;comment:每日限" json:"daily_limit"` // 每日限
LastUsed time.Time `gorm:"column:last_used;comment:最后提取时间" json:"last_used"` // 最后提取时间 DailyUsed int32 `gorm:"column:daily_used;comment:今日已用数量" json:"daily_used"` // 今日已用数量
DailyLast time.Time `gorm:"column:daily_last;comment:今日最后使用时间" json:"daily_last"` // 今日最后使用时间
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间 DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间

View File

@@ -29,6 +29,7 @@ func newResource(db *gorm.DB, opts ...gen.DOOption) resource {
_resource.ALL = field.NewAsterisk(tableName) _resource.ALL = field.NewAsterisk(tableName)
_resource.ID = field.NewInt32(tableName, "id") _resource.ID = field.NewInt32(tableName, "id")
_resource.UserID = field.NewInt32(tableName, "user_id") _resource.UserID = field.NewInt32(tableName, "user_id")
_resource.Active = field.NewBool(tableName, "active")
_resource.CreatedAt = field.NewTime(tableName, "created_at") _resource.CreatedAt = field.NewTime(tableName, "created_at")
_resource.UpdatedAt = field.NewTime(tableName, "updated_at") _resource.UpdatedAt = field.NewTime(tableName, "updated_at")
_resource.DeletedAt = field.NewField(tableName, "deleted_at") _resource.DeletedAt = field.NewField(tableName, "deleted_at")
@@ -44,6 +45,7 @@ type resource struct {
ALL field.Asterisk ALL field.Asterisk
ID field.Int32 // 套餐ID ID field.Int32 // 套餐ID
UserID field.Int32 // 用户ID UserID field.Int32 // 用户ID
Active field.Bool // 套餐状态
CreatedAt field.Time // 创建时间 CreatedAt field.Time // 创建时间
UpdatedAt field.Time // 更新时间 UpdatedAt field.Time // 更新时间
DeletedAt field.Field // 删除时间 DeletedAt field.Field // 删除时间
@@ -65,6 +67,7 @@ func (r *resource) updateTableName(table string) *resource {
r.ALL = field.NewAsterisk(table) r.ALL = field.NewAsterisk(table)
r.ID = field.NewInt32(table, "id") r.ID = field.NewInt32(table, "id")
r.UserID = field.NewInt32(table, "user_id") r.UserID = field.NewInt32(table, "user_id")
r.Active = field.NewBool(table, "active")
r.CreatedAt = field.NewTime(table, "created_at") r.CreatedAt = field.NewTime(table, "created_at")
r.UpdatedAt = field.NewTime(table, "updated_at") r.UpdatedAt = field.NewTime(table, "updated_at")
r.DeletedAt = field.NewField(table, "deleted_at") r.DeletedAt = field.NewField(table, "deleted_at")
@@ -84,9 +87,10 @@ func (r *resource) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
} }
func (r *resource) fillFieldMap() { func (r *resource) fillFieldMap() {
r.fieldMap = make(map[string]field.Expr, 5) r.fieldMap = make(map[string]field.Expr, 6)
r.fieldMap["id"] = r.ID r.fieldMap["id"] = r.ID
r.fieldMap["user_id"] = r.UserID r.fieldMap["user_id"] = r.UserID
r.fieldMap["active"] = r.Active
r.fieldMap["created_at"] = r.CreatedAt r.fieldMap["created_at"] = r.CreatedAt
r.fieldMap["updated_at"] = r.UpdatedAt r.fieldMap["updated_at"] = r.UpdatedAt
r.fieldMap["deleted_at"] = r.DeletedAt r.fieldMap["deleted_at"] = r.DeletedAt

View File

@@ -29,7 +29,6 @@ func newResourcePps(db *gorm.DB, opts ...gen.DOOption) resourcePps {
_resourcePps.ALL = field.NewAsterisk(tableName) _resourcePps.ALL = field.NewAsterisk(tableName)
_resourcePps.ID = field.NewInt32(tableName, "id") _resourcePps.ID = field.NewInt32(tableName, "id")
_resourcePps.ResourceID = field.NewInt32(tableName, "resource_id") _resourcePps.ResourceID = field.NewInt32(tableName, "resource_id")
_resourcePps.Active = field.NewBool(tableName, "active")
_resourcePps.CreatedAt = field.NewTime(tableName, "created_at") _resourcePps.CreatedAt = field.NewTime(tableName, "created_at")
_resourcePps.UpdatedAt = field.NewTime(tableName, "updated_at") _resourcePps.UpdatedAt = field.NewTime(tableName, "updated_at")
_resourcePps.DeletedAt = field.NewField(tableName, "deleted_at") _resourcePps.DeletedAt = field.NewField(tableName, "deleted_at")
@@ -45,7 +44,6 @@ type resourcePps struct {
ALL field.Asterisk ALL field.Asterisk
ID field.Int32 // ID ID field.Int32 // ID
ResourceID field.Int32 // 套餐ID ResourceID field.Int32 // 套餐ID
Active field.Bool // 是否启用
CreatedAt field.Time // 创建时间 CreatedAt field.Time // 创建时间
UpdatedAt field.Time // 更新时间 UpdatedAt field.Time // 更新时间
DeletedAt field.Field // 删除时间 DeletedAt field.Field // 删除时间
@@ -67,7 +65,6 @@ func (r *resourcePps) updateTableName(table string) *resourcePps {
r.ALL = field.NewAsterisk(table) r.ALL = field.NewAsterisk(table)
r.ID = field.NewInt32(table, "id") r.ID = field.NewInt32(table, "id")
r.ResourceID = field.NewInt32(table, "resource_id") r.ResourceID = field.NewInt32(table, "resource_id")
r.Active = field.NewBool(table, "active")
r.CreatedAt = field.NewTime(table, "created_at") r.CreatedAt = field.NewTime(table, "created_at")
r.UpdatedAt = field.NewTime(table, "updated_at") r.UpdatedAt = field.NewTime(table, "updated_at")
r.DeletedAt = field.NewField(table, "deleted_at") r.DeletedAt = field.NewField(table, "deleted_at")
@@ -87,10 +84,9 @@ func (r *resourcePps) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
} }
func (r *resourcePps) fillFieldMap() { func (r *resourcePps) fillFieldMap() {
r.fieldMap = make(map[string]field.Expr, 6) r.fieldMap = make(map[string]field.Expr, 5)
r.fieldMap["id"] = r.ID r.fieldMap["id"] = r.ID
r.fieldMap["resource_id"] = r.ResourceID r.fieldMap["resource_id"] = r.ResourceID
r.fieldMap["active"] = r.Active
r.fieldMap["created_at"] = r.CreatedAt r.fieldMap["created_at"] = r.CreatedAt
r.fieldMap["updated_at"] = r.UpdatedAt r.fieldMap["updated_at"] = r.UpdatedAt
r.fieldMap["deleted_at"] = r.DeletedAt r.fieldMap["deleted_at"] = r.DeletedAt

View File

@@ -29,7 +29,6 @@ func newResourcePsr(db *gorm.DB, opts ...gen.DOOption) resourcePsr {
_resourcePsr.ALL = field.NewAsterisk(tableName) _resourcePsr.ALL = field.NewAsterisk(tableName)
_resourcePsr.ID = field.NewInt32(tableName, "id") _resourcePsr.ID = field.NewInt32(tableName, "id")
_resourcePsr.ResourceID = field.NewInt32(tableName, "resource_id") _resourcePsr.ResourceID = field.NewInt32(tableName, "resource_id")
_resourcePsr.Active = field.NewBool(tableName, "active")
_resourcePsr.Live = field.NewInt32(tableName, "live") _resourcePsr.Live = field.NewInt32(tableName, "live")
_resourcePsr.Conn = field.NewInt32(tableName, "conn") _resourcePsr.Conn = field.NewInt32(tableName, "conn")
_resourcePsr.Expire = field.NewTime(tableName, "expire") _resourcePsr.Expire = field.NewTime(tableName, "expire")
@@ -49,7 +48,6 @@ type resourcePsr struct {
ALL field.Asterisk ALL field.Asterisk
ID field.Int32 // ID ID field.Int32 // ID
ResourceID field.Int32 // 套餐ID ResourceID field.Int32 // 套餐ID
Active field.Bool // 是否启用
Live field.Int32 // 轮换周期(秒) Live field.Int32 // 轮换周期(秒)
Conn field.Int32 // 最大连接数 Conn field.Int32 // 最大连接数
Expire field.Time // 过期时间 Expire field.Time // 过期时间
@@ -75,7 +73,6 @@ func (r *resourcePsr) updateTableName(table string) *resourcePsr {
r.ALL = field.NewAsterisk(table) r.ALL = field.NewAsterisk(table)
r.ID = field.NewInt32(table, "id") r.ID = field.NewInt32(table, "id")
r.ResourceID = field.NewInt32(table, "resource_id") r.ResourceID = field.NewInt32(table, "resource_id")
r.Active = field.NewBool(table, "active")
r.Live = field.NewInt32(table, "live") r.Live = field.NewInt32(table, "live")
r.Conn = field.NewInt32(table, "conn") r.Conn = field.NewInt32(table, "conn")
r.Expire = field.NewTime(table, "expire") r.Expire = field.NewTime(table, "expire")
@@ -99,10 +96,9 @@ func (r *resourcePsr) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
} }
func (r *resourcePsr) fillFieldMap() { func (r *resourcePsr) fillFieldMap() {
r.fieldMap = make(map[string]field.Expr, 10) r.fieldMap = make(map[string]field.Expr, 9)
r.fieldMap["id"] = r.ID r.fieldMap["id"] = r.ID
r.fieldMap["resource_id"] = r.ResourceID r.fieldMap["resource_id"] = r.ResourceID
r.fieldMap["active"] = r.Active
r.fieldMap["live"] = r.Live r.fieldMap["live"] = r.Live
r.fieldMap["conn"] = r.Conn r.fieldMap["conn"] = r.Conn
r.fieldMap["expire"] = r.Expire r.fieldMap["expire"] = r.Expire

View File

@@ -29,14 +29,14 @@ func newResourcePss(db *gorm.DB, opts ...gen.DOOption) resourcePss {
_resourcePss.ALL = field.NewAsterisk(tableName) _resourcePss.ALL = field.NewAsterisk(tableName)
_resourcePss.ID = field.NewInt32(tableName, "id") _resourcePss.ID = field.NewInt32(tableName, "id")
_resourcePss.ResourceID = field.NewInt32(tableName, "resource_id") _resourcePss.ResourceID = field.NewInt32(tableName, "resource_id")
_resourcePss.Active = field.NewBool(tableName, "active")
_resourcePss.Type = field.NewInt32(tableName, "type") _resourcePss.Type = field.NewInt32(tableName, "type")
_resourcePss.Live = field.NewInt32(tableName, "live") _resourcePss.Live = field.NewInt32(tableName, "live")
_resourcePss.Quota = field.NewInt32(tableName, "quota") _resourcePss.Quota = field.NewInt32(tableName, "quota")
_resourcePss.Used = field.NewInt32(tableName, "used") _resourcePss.Used = field.NewInt32(tableName, "used")
_resourcePss.Expire = field.NewTime(tableName, "expire") _resourcePss.Expire = field.NewTime(tableName, "expire")
_resourcePss.LimitDay = field.NewInt32(tableName, "limit_day") _resourcePss.DailyLimit = field.NewInt32(tableName, "daily_limit")
_resourcePss.LastUsed = field.NewTime(tableName, "last_used") _resourcePss.DailyUsed = field.NewInt32(tableName, "daily_used")
_resourcePss.DailyLast = field.NewTime(tableName, "daily_last")
_resourcePss.CreatedAt = field.NewTime(tableName, "created_at") _resourcePss.CreatedAt = field.NewTime(tableName, "created_at")
_resourcePss.UpdatedAt = field.NewTime(tableName, "updated_at") _resourcePss.UpdatedAt = field.NewTime(tableName, "updated_at")
_resourcePss.DeletedAt = field.NewField(tableName, "deleted_at") _resourcePss.DeletedAt = field.NewField(tableName, "deleted_at")
@@ -52,14 +52,14 @@ type resourcePss struct {
ALL field.Asterisk ALL field.Asterisk
ID field.Int32 // ID ID field.Int32 // ID
ResourceID field.Int32 // 套餐ID ResourceID field.Int32 // 套餐ID
Active field.Bool // 是否启用
Type field.Int32 // 套餐类型1-包时2-包量 Type field.Int32 // 套餐类型1-包时2-包量
Live field.Int32 // 可用时长(秒) Live field.Int32 // 可用时长(秒)
Quota field.Int32 // 配额数量 Quota field.Int32 // 配额数量
Used field.Int32 // 已用数量 Used field.Int32 // 已用数量
Expire field.Time // 过期时间 Expire field.Time // 过期时间
LimitDay field.Int32 // 每日限 DailyLimit field.Int32 // 每日限
LastUsed field.Time // 最后提取时间 DailyUsed field.Int32 // 今日已用数量
DailyLast field.Time // 今日最后使用时间
CreatedAt field.Time // 创建时间 CreatedAt field.Time // 创建时间
UpdatedAt field.Time // 更新时间 UpdatedAt field.Time // 更新时间
DeletedAt field.Field // 删除时间 DeletedAt field.Field // 删除时间
@@ -81,14 +81,14 @@ func (r *resourcePss) updateTableName(table string) *resourcePss {
r.ALL = field.NewAsterisk(table) r.ALL = field.NewAsterisk(table)
r.ID = field.NewInt32(table, "id") r.ID = field.NewInt32(table, "id")
r.ResourceID = field.NewInt32(table, "resource_id") r.ResourceID = field.NewInt32(table, "resource_id")
r.Active = field.NewBool(table, "active")
r.Type = field.NewInt32(table, "type") r.Type = field.NewInt32(table, "type")
r.Live = field.NewInt32(table, "live") r.Live = field.NewInt32(table, "live")
r.Quota = field.NewInt32(table, "quota") r.Quota = field.NewInt32(table, "quota")
r.Used = field.NewInt32(table, "used") r.Used = field.NewInt32(table, "used")
r.Expire = field.NewTime(table, "expire") r.Expire = field.NewTime(table, "expire")
r.LimitDay = field.NewInt32(table, "limit_day") r.DailyLimit = field.NewInt32(table, "daily_limit")
r.LastUsed = field.NewTime(table, "last_used") r.DailyUsed = field.NewInt32(table, "daily_used")
r.DailyLast = field.NewTime(table, "daily_last")
r.CreatedAt = field.NewTime(table, "created_at") r.CreatedAt = field.NewTime(table, "created_at")
r.UpdatedAt = field.NewTime(table, "updated_at") r.UpdatedAt = field.NewTime(table, "updated_at")
r.DeletedAt = field.NewField(table, "deleted_at") r.DeletedAt = field.NewField(table, "deleted_at")
@@ -111,14 +111,14 @@ func (r *resourcePss) fillFieldMap() {
r.fieldMap = make(map[string]field.Expr, 13) r.fieldMap = make(map[string]field.Expr, 13)
r.fieldMap["id"] = r.ID r.fieldMap["id"] = r.ID
r.fieldMap["resource_id"] = r.ResourceID r.fieldMap["resource_id"] = r.ResourceID
r.fieldMap["active"] = r.Active
r.fieldMap["type"] = r.Type r.fieldMap["type"] = r.Type
r.fieldMap["live"] = r.Live r.fieldMap["live"] = r.Live
r.fieldMap["quota"] = r.Quota r.fieldMap["quota"] = r.Quota
r.fieldMap["used"] = r.Used r.fieldMap["used"] = r.Used
r.fieldMap["expire"] = r.Expire r.fieldMap["expire"] = r.Expire
r.fieldMap["limit_day"] = r.LimitDay r.fieldMap["daily_limit"] = r.DailyLimit
r.fieldMap["last_used"] = r.LastUsed r.fieldMap["daily_used"] = r.DailyUsed
r.fieldMap["daily_last"] = r.DailyLast
r.fieldMap["created_at"] = r.CreatedAt r.fieldMap["created_at"] = r.CreatedAt
r.fieldMap["updated_at"] = r.UpdatedAt r.fieldMap["updated_at"] = r.UpdatedAt
r.fieldMap["deleted_at"] = r.DeletedAt r.fieldMap["deleted_at"] = r.DeletedAt

View File

@@ -9,13 +9,17 @@ import (
func ApplyRouters(app *fiber.App) { func ApplyRouters(app *fiber.App) {
api := app.Group("/api") api := app.Group("/api")
// 认证路由 // 认证
auth := api.Group("/auth") auth := api.Group("/auth")
auth.Post("/verify/sms", Protect(), handlers.SmsCode) auth.Post("/verify/sms", PermitUser(), handlers.SmsCode)
auth.Post("/login/sms", Protect(), handlers.Login) auth.Post("/login/sms", PermitUser(), handlers.Login)
auth.Post("/token", handlers.Token) auth.Post("/token", handlers.Token)
// 客户端路由 // 客户端
client := api.Group("/client") client := api.Group("/client")
client.Get("/test/create", handlers.CreateClient) client.Get("/test/create", handlers.CreateClient)
// 通道
channel := api.Group("/channel", PermitUser())
channel.Post("/create", handlers.CreateChannel)
} }

View File

@@ -1,10 +1,20 @@
package services package services
import ( import (
"context"
"errors" "errors"
"log/slog" "fmt"
"math"
"platform/pkg/rds"
"platform/web/common"
"platform/web/models" "platform/web/models"
q "platform/web/queries" q "platform/web/queries"
"time"
"github.com/google/uuid"
"github.com/jxskiss/base62"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
) )
var Channel = &channelService{} var Channel = &channelService{}
@@ -12,47 +22,229 @@ var Channel = &channelService{}
type channelService struct { type channelService struct {
} }
// CreateChannel 创建连接通道,并返回连接信息,如果配额不足则返回错误
func (s *channelService) CreateChannel( func (s *channelService) CreateChannel(
userID int32, ctx context.Context,
region string, auth *AuthContext,
provider string, resourceId int32,
protocol Protocol, protocol ChannelProtocol,
resourceId int, authType ChannelAuthType,
count int, count int,
nodeFilter ...NodeFilterConfig,
) ([]*models.Channel, error) { ) ([]*models.Channel, error) {
// 检查并扣减套餐余额 // 创建通道
var resourceInfo = struct { var channels []*models.Channel
models.Resource err := q.Q.Transaction(func(tx *q.Query) error {
models.ResourcePss // 查找套餐
}{} var resource = struct {
err := q.Resource. data models.Resource
Where(q.Resource.UserID.Eq(userID)). pss models.ResourcePss
LeftJoin(q.ResourcePss, q.ResourcePss.ResourceID.EqCol(q.Resource.ID)). }{}
Scan(&resourceInfo) err := q.Resource.As("data").
LeftJoin(q.ResourcePss.As("pss"), q.ResourcePss.ResourceID.EqCol(q.Resource.ID)).
Where(q.Resource.ID.Eq(resourceId)).
Scan(&resource)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ChannelServiceErr("套餐不存在")
}
return err
}
// 检查使用人
if auth.Payload.Type == PayloadUser && auth.Payload.Id != resource.data.UserID {
return common.AuthForbiddenErr("无权限访问")
}
// 检查套餐状态
if !resource.data.Active {
return ChannelServiceErr("套餐已失效")
}
// 检查每日限额
today := time.Now().Format("2006-01-02") == resource.pss.DailyLast.Format("2006-01-02")
dailyRemain := int(math.Max(float64(resource.pss.DailyLimit-resource.pss.DailyUsed), 0))
if today && dailyRemain < count {
return ChannelServiceErr("套餐每日配额不足")
}
// 检查时间或配额
if resource.pss.Type == 1 { // 包时
if resource.pss.Expire.Before(time.Now()) {
return ChannelServiceErr("套餐已过期")
}
} else { // 包量
remain := int(math.Max(float64(resource.pss.Quota-resource.pss.Used), 0))
if remain < count {
return ChannelServiceErr("套餐配额不足")
}
}
// 筛选可用节点
nodes, err := Node.Filter(auth.Payload.Id, protocol, count, nodeFilter...)
if err != nil {
return err
}
// 获取用户配置白名单
whitelist, err := q.Whitelist.Where(
q.Whitelist.UserID.Eq(auth.Payload.Id),
).Find()
if err != nil {
return err
}
// 创建连接通道
channels = make([]*models.Channel, 0, len(nodes)*len(whitelist))
for _, node := range nodes {
for _, allowed := range whitelist {
username, password := genPassPair()
channels = append(channels, &models.Channel{
UserID: auth.Payload.Id,
NodeID: node.ID,
NodePort: node.FwdPort,
Protocol: string(protocol),
AuthIP: authType == ChannelAuthTypeIp,
UserHost: allowed.Host,
AuthPass: authType == ChannelAuthTypePass,
Username: username,
Password: password,
Expiration: time.Now().Add(time.Duration(resource.pss.Live) * time.Second),
})
}
}
// 保存到数据库
err = tx.Channel.Create(channels...)
if err != nil {
return err
}
// 更新套餐使用记录
if today {
resource.pss.DailyUsed += int32(count)
resource.pss.Used += int32(count)
} else {
resource.pss.DailyLast = time.Now()
resource.pss.DailyUsed = int32(count)
resource.pss.Used += int32(count)
}
err = tx.ResourcePss.
Where(q.ResourcePss.ID.Eq(resource.pss.ID)).
Omit(q.ResourcePss.ResourceID).
Save(&resource.pss)
if err != nil {
return err
}
return nil
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
slog.Debug("查询资源", slog.Any("info", resourceInfo)) // 缓存通道信息与异步删除任务
pipe := rds.Client.TxPipeline()
// 创建连接通道 zList := make([]redis.Z, 0, len(channels))
for _, channel := range channels {
// 保存到数据库与缓存,以及计时关闭 pipe.Set(ctx, chKey(channel), channel, channel.Expiration.Sub(time.Now()))
zList = append(zList, redis.Z{
// 组织请求数据 Score: float64(channel.Expiration.Unix()),
Member: channel.ID,
// 发送请求到远端配置服务 })
}
pipe.ZAdd(ctx, "tasks:channel", zList...)
_, err = pipe.Exec(ctx)
if err != nil {
return nil, err
}
// 返回连接通道列表 // 返回连接通道列表
return channels, errors.New("not implemented")
return nil, errors.New("not implemented")
} }
type Protocol string type ChannelAuthType int
const ( const (
ProtocolSocks5 = Protocol("socks5") ChannelAuthTypeIp = iota
ProtocolHTTP = Protocol("http") ChannelAuthTypePass
ProtocolHttps = Protocol("https")
) )
type ChannelProtocol string
const (
ProtocolSocks5 = ChannelProtocol("socks5")
ProtocolHTTP = ChannelProtocol("http")
ProtocolHttps = ChannelProtocol("https")
)
func genPassPair() (string, string) {
usernameBytes, err := uuid.New().MarshalBinary()
if err != nil {
panic(err)
}
passwordBytes, err := uuid.New().MarshalBinary()
if err != nil {
panic(err)
}
username := base62.EncodeToString(usernameBytes)
password := base62.EncodeToString(passwordBytes)
return username, password
}
func (s *channelService) RemoveChannels(auth *AuthContext, id ...int32) error {
var channels []*models.Channel
// 删除通道
err := q.Q.Transaction(func(tx *q.Query) error {
// 查找通道
channels, err := tx.Channel.Where(
q.Channel.ID.In(id...),
).Find()
if err != nil {
return err
}
// 检查权限,只有用户自己和管理员能删除
for _, channel := range channels {
if auth.Payload.Type == PayloadUser && auth.Payload.Id != channel.UserID {
return common.AuthForbiddenErr("无权限访问")
}
}
// 删除指定的通道
result, err := tx.Channel.Delete(channels...)
if err != nil {
return err
}
if result.RowsAffected != int64(len(channels)) {
return ChannelServiceErr("删除通道失败")
}
return nil
})
if err != nil {
return err
}
// 删除缓存,异步任务直接在消费端处理删除
pipe := rds.Client.TxPipeline()
for _, channel := range channels {
pipe.Del(context.Background(), chKey(channel))
}
return nil
}
func chKey(channel *models.Channel) string {
return fmt.Sprintf("channel:%s:%d", channel.UserHost, channel.NodePort)
}
type ChannelServiceErr string
func (c ChannelServiceErr) Error() string {
return string(c)
}

View File

@@ -1,37 +0,0 @@
package services
import (
"testing"
)
func Test_channelService_CreateChannel(t *testing.T) {
// type args struct {
// userID int32
// region string
// provider string
// protocol Protocol
// resourceId int
// count int
// }
// tests := []struct {
// name string
// args args
// want []*models.Channel
// wantErr bool
// }{
// // TODO: Add test cases.
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// s := &channelService{}
// got, err := s.CreateChannel(tt.args.userID, tt.args.region, tt.args.provider, tt.args.protocol, tt.args.resourceId, tt.args.count)
// if (err != nil) != tt.wantErr {
// t.Errorf("CreateChannel() error = %v, wantErr %v", err, tt.wantErr)
// return
// }
// if !reflect.DeepEqual(got, tt.want) {
// t.Errorf("CreateChannel() got = %v, want %v", got, tt.want)
// }
// })
// }
}

18
web/services/node.go Normal file
View File

@@ -0,0 +1,18 @@
package services
import "platform/web/models"
var Node = &nodeService{}
type nodeService struct{}
func (s *nodeService) Filter(userId int32, proto ChannelProtocol, count int, config ...NodeFilterConfig) ([]*models.Node, error) {
return make([]*models.Node, 0), nil
}
type NodeFilterConfig struct {
province string
city string
provider string
}

View File

@@ -5,7 +5,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"platform/init/rds" "platform/pkg/rds"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"

View File

@@ -3,7 +3,7 @@ package services
import ( import (
"context" "context"
"errors" "errors"
"platform/init/rds" "platform/pkg/rds"
"reflect" "reflect"
"testing" "testing"
"time" "time"

View File

@@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"math/rand" "math/rand"
"platform/init/rds" "platform/pkg/rds"
"strconv" "strconv"
"time" "time"

View File

@@ -2,7 +2,7 @@ package services
import ( import (
"context" "context"
"platform/init/rds" "platform/pkg/rds"
"strconv" "strconv"
"testing" "testing"
"time" "time"

View File

@@ -1,7 +1,7 @@
package web package web
import ( import (
"platform/init/env" "platform/pkg/env"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/logger"