通道的增删接口实现,数据表和目录结构调整
This commit is contained in:
16
web/auth.go
16
web/auth.go
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// Protect 创建针对单个路由的鉴权中间件
|
||||
func Protect(permissions ...string) fiber.Handler {
|
||||
// PermitUser 创建针对单个路由的鉴权中间件
|
||||
func PermitUser(permissions ...string) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
// 获取令牌
|
||||
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{
|
||||
Error: true,
|
||||
Message: "拒绝访问",
|
||||
|
||||
13
web/common/errors.go
Normal file
13
web/common/errors.go
Normal 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)
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
type CreateChannelReq struct {
|
||||
Region string `json:"region" 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"`
|
||||
Count int `json:"count" validate:"required"`
|
||||
ResultType CreateChannelResultType `json:"return_type" validate:"required,oneof=json text"`
|
||||
|
||||
@@ -16,6 +16,7 @@ const TableNameResource = "resource"
|
||||
type Resource struct {
|
||||
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
|
||||
Active bool `gorm:"column:active;default:true;comment:套餐状态" json:"active"` // 套餐状态
|
||||
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
|
||||
|
||||
@@ -16,7 +16,6 @@ const TableNameResourcePps = "resource_pps"
|
||||
type ResourcePps struct {
|
||||
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
|
||||
Active bool `gorm:"column:active;not null;comment:是否启用" json:"active"` // 是否启用
|
||||
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
|
||||
|
||||
@@ -16,7 +16,6 @@ const TableNameResourcePsr = "resource_psr"
|
||||
type ResourcePsr struct {
|
||||
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
|
||||
Active bool `gorm:"column:active;not null;comment:是否启用" json:"active"` // 是否启用
|
||||
Live int32 `gorm:"column:live;comment:轮换周期(秒)" json:"live"` // 轮换周期(秒)
|
||||
Conn int32 `gorm:"column:conn;comment:最大连接数" json:"conn"` // 最大连接数
|
||||
Expire time.Time `gorm:"column:expire;comment:过期时间" json:"expire"` // 过期时间
|
||||
|
||||
@@ -16,14 +16,14 @@ const TableNameResourcePss = "resource_pss"
|
||||
type ResourcePss struct {
|
||||
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
|
||||
Active bool `gorm:"column:active;not null;comment:是否启用" json:"active"` // 是否启用
|
||||
Type int32 `gorm:"column:type;comment:套餐类型:1-包时,2-包量" json:"type"` // 套餐类型:1-包时,2-包量
|
||||
Live int32 `gorm:"column:live;comment:可用时长(秒)" json:"live"` // 可用时长(秒)
|
||||
Quota int32 `gorm:"column:quota;comment:配额数量" json:"quota"` // 配额数量
|
||||
Used int32 `gorm:"column:used;comment:已用数量" json:"used"` // 已用数量
|
||||
Expire time.Time `gorm:"column:expire;comment:过期时间" json:"expire"` // 过期时间
|
||||
LimitDay int32 `gorm:"column:limit_day;comment:每日限额" json:"limit_day"` // 每日限额
|
||||
LastUsed time.Time `gorm:"column:last_used;comment:最后提取时间" json:"last_used"` // 最后提取时间
|
||||
DailyLimit int32 `gorm:"column:daily_limit;comment:每日限制" json:"daily_limit"` // 每日限制
|
||||
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"` // 创建时间
|
||||
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"` // 删除时间
|
||||
|
||||
@@ -29,6 +29,7 @@ func newResource(db *gorm.DB, opts ...gen.DOOption) resource {
|
||||
_resource.ALL = field.NewAsterisk(tableName)
|
||||
_resource.ID = field.NewInt32(tableName, "id")
|
||||
_resource.UserID = field.NewInt32(tableName, "user_id")
|
||||
_resource.Active = field.NewBool(tableName, "active")
|
||||
_resource.CreatedAt = field.NewTime(tableName, "created_at")
|
||||
_resource.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||
_resource.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||
@@ -44,6 +45,7 @@ type resource struct {
|
||||
ALL field.Asterisk
|
||||
ID field.Int32 // 套餐ID
|
||||
UserID field.Int32 // 用户ID
|
||||
Active field.Bool // 套餐状态
|
||||
CreatedAt field.Time // 创建时间
|
||||
UpdatedAt field.Time // 更新时间
|
||||
DeletedAt field.Field // 删除时间
|
||||
@@ -65,6 +67,7 @@ func (r *resource) updateTableName(table string) *resource {
|
||||
r.ALL = field.NewAsterisk(table)
|
||||
r.ID = field.NewInt32(table, "id")
|
||||
r.UserID = field.NewInt32(table, "user_id")
|
||||
r.Active = field.NewBool(table, "active")
|
||||
r.CreatedAt = field.NewTime(table, "created_at")
|
||||
r.UpdatedAt = field.NewTime(table, "updated_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() {
|
||||
r.fieldMap = make(map[string]field.Expr, 5)
|
||||
r.fieldMap = make(map[string]field.Expr, 6)
|
||||
r.fieldMap["id"] = r.ID
|
||||
r.fieldMap["user_id"] = r.UserID
|
||||
r.fieldMap["active"] = r.Active
|
||||
r.fieldMap["created_at"] = r.CreatedAt
|
||||
r.fieldMap["updated_at"] = r.UpdatedAt
|
||||
r.fieldMap["deleted_at"] = r.DeletedAt
|
||||
|
||||
@@ -29,7 +29,6 @@ func newResourcePps(db *gorm.DB, opts ...gen.DOOption) resourcePps {
|
||||
_resourcePps.ALL = field.NewAsterisk(tableName)
|
||||
_resourcePps.ID = field.NewInt32(tableName, "id")
|
||||
_resourcePps.ResourceID = field.NewInt32(tableName, "resource_id")
|
||||
_resourcePps.Active = field.NewBool(tableName, "active")
|
||||
_resourcePps.CreatedAt = field.NewTime(tableName, "created_at")
|
||||
_resourcePps.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||
_resourcePps.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||
@@ -45,7 +44,6 @@ type resourcePps struct {
|
||||
ALL field.Asterisk
|
||||
ID field.Int32 // ID
|
||||
ResourceID field.Int32 // 套餐ID
|
||||
Active field.Bool // 是否启用
|
||||
CreatedAt field.Time // 创建时间
|
||||
UpdatedAt field.Time // 更新时间
|
||||
DeletedAt field.Field // 删除时间
|
||||
@@ -67,7 +65,6 @@ func (r *resourcePps) updateTableName(table string) *resourcePps {
|
||||
r.ALL = field.NewAsterisk(table)
|
||||
r.ID = field.NewInt32(table, "id")
|
||||
r.ResourceID = field.NewInt32(table, "resource_id")
|
||||
r.Active = field.NewBool(table, "active")
|
||||
r.CreatedAt = field.NewTime(table, "created_at")
|
||||
r.UpdatedAt = field.NewTime(table, "updated_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() {
|
||||
r.fieldMap = make(map[string]field.Expr, 6)
|
||||
r.fieldMap = make(map[string]field.Expr, 5)
|
||||
r.fieldMap["id"] = r.ID
|
||||
r.fieldMap["resource_id"] = r.ResourceID
|
||||
r.fieldMap["active"] = r.Active
|
||||
r.fieldMap["created_at"] = r.CreatedAt
|
||||
r.fieldMap["updated_at"] = r.UpdatedAt
|
||||
r.fieldMap["deleted_at"] = r.DeletedAt
|
||||
|
||||
@@ -29,7 +29,6 @@ func newResourcePsr(db *gorm.DB, opts ...gen.DOOption) resourcePsr {
|
||||
_resourcePsr.ALL = field.NewAsterisk(tableName)
|
||||
_resourcePsr.ID = field.NewInt32(tableName, "id")
|
||||
_resourcePsr.ResourceID = field.NewInt32(tableName, "resource_id")
|
||||
_resourcePsr.Active = field.NewBool(tableName, "active")
|
||||
_resourcePsr.Live = field.NewInt32(tableName, "live")
|
||||
_resourcePsr.Conn = field.NewInt32(tableName, "conn")
|
||||
_resourcePsr.Expire = field.NewTime(tableName, "expire")
|
||||
@@ -49,7 +48,6 @@ type resourcePsr struct {
|
||||
ALL field.Asterisk
|
||||
ID field.Int32 // ID
|
||||
ResourceID field.Int32 // 套餐ID
|
||||
Active field.Bool // 是否启用
|
||||
Live field.Int32 // 轮换周期(秒)
|
||||
Conn field.Int32 // 最大连接数
|
||||
Expire field.Time // 过期时间
|
||||
@@ -75,7 +73,6 @@ func (r *resourcePsr) updateTableName(table string) *resourcePsr {
|
||||
r.ALL = field.NewAsterisk(table)
|
||||
r.ID = field.NewInt32(table, "id")
|
||||
r.ResourceID = field.NewInt32(table, "resource_id")
|
||||
r.Active = field.NewBool(table, "active")
|
||||
r.Live = field.NewInt32(table, "live")
|
||||
r.Conn = field.NewInt32(table, "conn")
|
||||
r.Expire = field.NewTime(table, "expire")
|
||||
@@ -99,10 +96,9 @@ func (r *resourcePsr) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||
}
|
||||
|
||||
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["resource_id"] = r.ResourceID
|
||||
r.fieldMap["active"] = r.Active
|
||||
r.fieldMap["live"] = r.Live
|
||||
r.fieldMap["conn"] = r.Conn
|
||||
r.fieldMap["expire"] = r.Expire
|
||||
|
||||
@@ -29,14 +29,14 @@ func newResourcePss(db *gorm.DB, opts ...gen.DOOption) resourcePss {
|
||||
_resourcePss.ALL = field.NewAsterisk(tableName)
|
||||
_resourcePss.ID = field.NewInt32(tableName, "id")
|
||||
_resourcePss.ResourceID = field.NewInt32(tableName, "resource_id")
|
||||
_resourcePss.Active = field.NewBool(tableName, "active")
|
||||
_resourcePss.Type = field.NewInt32(tableName, "type")
|
||||
_resourcePss.Live = field.NewInt32(tableName, "live")
|
||||
_resourcePss.Quota = field.NewInt32(tableName, "quota")
|
||||
_resourcePss.Used = field.NewInt32(tableName, "used")
|
||||
_resourcePss.Expire = field.NewTime(tableName, "expire")
|
||||
_resourcePss.LimitDay = field.NewInt32(tableName, "limit_day")
|
||||
_resourcePss.LastUsed = field.NewTime(tableName, "last_used")
|
||||
_resourcePss.DailyLimit = field.NewInt32(tableName, "daily_limit")
|
||||
_resourcePss.DailyUsed = field.NewInt32(tableName, "daily_used")
|
||||
_resourcePss.DailyLast = field.NewTime(tableName, "daily_last")
|
||||
_resourcePss.CreatedAt = field.NewTime(tableName, "created_at")
|
||||
_resourcePss.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||
_resourcePss.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||
@@ -52,14 +52,14 @@ type resourcePss struct {
|
||||
ALL field.Asterisk
|
||||
ID field.Int32 // ID
|
||||
ResourceID field.Int32 // 套餐ID
|
||||
Active field.Bool // 是否启用
|
||||
Type field.Int32 // 套餐类型:1-包时,2-包量
|
||||
Live field.Int32 // 可用时长(秒)
|
||||
Quota field.Int32 // 配额数量
|
||||
Used field.Int32 // 已用数量
|
||||
Expire field.Time // 过期时间
|
||||
LimitDay field.Int32 // 每日限额
|
||||
LastUsed field.Time // 最后提取时间
|
||||
DailyLimit field.Int32 // 每日限制
|
||||
DailyUsed field.Int32 // 今日已用数量
|
||||
DailyLast field.Time // 今日最后使用时间
|
||||
CreatedAt field.Time // 创建时间
|
||||
UpdatedAt field.Time // 更新时间
|
||||
DeletedAt field.Field // 删除时间
|
||||
@@ -81,14 +81,14 @@ func (r *resourcePss) updateTableName(table string) *resourcePss {
|
||||
r.ALL = field.NewAsterisk(table)
|
||||
r.ID = field.NewInt32(table, "id")
|
||||
r.ResourceID = field.NewInt32(table, "resource_id")
|
||||
r.Active = field.NewBool(table, "active")
|
||||
r.Type = field.NewInt32(table, "type")
|
||||
r.Live = field.NewInt32(table, "live")
|
||||
r.Quota = field.NewInt32(table, "quota")
|
||||
r.Used = field.NewInt32(table, "used")
|
||||
r.Expire = field.NewTime(table, "expire")
|
||||
r.LimitDay = field.NewInt32(table, "limit_day")
|
||||
r.LastUsed = field.NewTime(table, "last_used")
|
||||
r.DailyLimit = field.NewInt32(table, "daily_limit")
|
||||
r.DailyUsed = field.NewInt32(table, "daily_used")
|
||||
r.DailyLast = field.NewTime(table, "daily_last")
|
||||
r.CreatedAt = field.NewTime(table, "created_at")
|
||||
r.UpdatedAt = field.NewTime(table, "updated_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["id"] = r.ID
|
||||
r.fieldMap["resource_id"] = r.ResourceID
|
||||
r.fieldMap["active"] = r.Active
|
||||
r.fieldMap["type"] = r.Type
|
||||
r.fieldMap["live"] = r.Live
|
||||
r.fieldMap["quota"] = r.Quota
|
||||
r.fieldMap["used"] = r.Used
|
||||
r.fieldMap["expire"] = r.Expire
|
||||
r.fieldMap["limit_day"] = r.LimitDay
|
||||
r.fieldMap["last_used"] = r.LastUsed
|
||||
r.fieldMap["daily_limit"] = r.DailyLimit
|
||||
r.fieldMap["daily_used"] = r.DailyUsed
|
||||
r.fieldMap["daily_last"] = r.DailyLast
|
||||
r.fieldMap["created_at"] = r.CreatedAt
|
||||
r.fieldMap["updated_at"] = r.UpdatedAt
|
||||
r.fieldMap["deleted_at"] = r.DeletedAt
|
||||
|
||||
@@ -9,13 +9,17 @@ import (
|
||||
func ApplyRouters(app *fiber.App) {
|
||||
api := app.Group("/api")
|
||||
|
||||
// 认证路由
|
||||
// 认证
|
||||
auth := api.Group("/auth")
|
||||
auth.Post("/verify/sms", Protect(), handlers.SmsCode)
|
||||
auth.Post("/login/sms", Protect(), handlers.Login)
|
||||
auth.Post("/verify/sms", PermitUser(), handlers.SmsCode)
|
||||
auth.Post("/login/sms", PermitUser(), handlers.Login)
|
||||
auth.Post("/token", handlers.Token)
|
||||
|
||||
// 客户端路由
|
||||
// 客户端
|
||||
client := api.Group("/client")
|
||||
client.Get("/test/create", handlers.CreateClient)
|
||||
|
||||
// 通道
|
||||
channel := api.Group("/channel", PermitUser())
|
||||
channel.Post("/create", handlers.CreateChannel)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"fmt"
|
||||
"math"
|
||||
"platform/pkg/rds"
|
||||
"platform/web/common"
|
||||
"platform/web/models"
|
||||
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{}
|
||||
@@ -12,47 +22,229 @@ var Channel = &channelService{}
|
||||
type channelService struct {
|
||||
}
|
||||
|
||||
// CreateChannel 创建连接通道,并返回连接信息,如果配额不足则返回错误
|
||||
func (s *channelService) CreateChannel(
|
||||
userID int32,
|
||||
region string,
|
||||
provider string,
|
||||
protocol Protocol,
|
||||
resourceId int,
|
||||
ctx context.Context,
|
||||
auth *AuthContext,
|
||||
resourceId int32,
|
||||
protocol ChannelProtocol,
|
||||
authType ChannelAuthType,
|
||||
count int,
|
||||
nodeFilter ...NodeFilterConfig,
|
||||
) ([]*models.Channel, error) {
|
||||
|
||||
// 检查并扣减套餐余额
|
||||
var resourceInfo = struct {
|
||||
models.Resource
|
||||
models.ResourcePss
|
||||
}{}
|
||||
err := q.Resource.
|
||||
Where(q.Resource.UserID.Eq(userID)).
|
||||
LeftJoin(q.ResourcePss, q.ResourcePss.ResourceID.EqCol(q.Resource.ID)).
|
||||
Scan(&resourceInfo)
|
||||
// 创建通道
|
||||
var channels []*models.Channel
|
||||
err := q.Q.Transaction(func(tx *q.Query) error {
|
||||
// 查找套餐
|
||||
var resource = struct {
|
||||
data models.Resource
|
||||
pss models.ResourcePss
|
||||
}{}
|
||||
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 {
|
||||
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 nil, errors.New("not implemented")
|
||||
return channels, errors.New("not implemented")
|
||||
}
|
||||
|
||||
type Protocol string
|
||||
type ChannelAuthType int
|
||||
|
||||
const (
|
||||
ProtocolSocks5 = Protocol("socks5")
|
||||
ProtocolHTTP = Protocol("http")
|
||||
ProtocolHttps = Protocol("https")
|
||||
ChannelAuthTypeIp = iota
|
||||
ChannelAuthTypePass
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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
18
web/services/node.go
Normal 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
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"platform/init/rds"
|
||||
"platform/pkg/rds"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
@@ -3,7 +3,7 @@ package services
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"platform/init/rds"
|
||||
"platform/pkg/rds"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math/rand"
|
||||
"platform/init/rds"
|
||||
"platform/pkg/rds"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"platform/init/rds"
|
||||
"platform/pkg/rds"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"platform/init/env"
|
||||
"platform/pkg/env"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
|
||||
Reference in New Issue
Block a user