修复通道提取接口权限与数据映射问题
This commit is contained in:
@@ -17,6 +17,8 @@ trade/create 性能问题,缩短事务时间,考虑其他方式实现可靠
|
|||||||
|
|
||||||
网关缩扩容太慢
|
网关缩扩容太慢
|
||||||
|
|
||||||
|
redis channel lease 加一个 zset,定时处理没有成功释放的端口
|
||||||
|
|
||||||
### 长期
|
### 长期
|
||||||
|
|
||||||
分离 task 的客户端,支持多进程(prefork 必要!)
|
分离 task 的客户端,支持多进程(prefork 必要!)
|
||||||
|
|||||||
@@ -604,7 +604,7 @@ create table channel (
|
|||||||
filter_prov text,
|
filter_prov text,
|
||||||
filter_city text,
|
filter_city text,
|
||||||
ip inet,
|
ip inet,
|
||||||
whitelists text[],
|
whitelists text,
|
||||||
username text,
|
username text,
|
||||||
password text,
|
password text,
|
||||||
expired_at timestamptz not null,
|
expired_at timestamptz not null,
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"database/sql/driver"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LocalDateTime time.Time
|
|
||||||
|
|
||||||
var formats = []string{
|
|
||||||
"2006-01-02 15:04:05.999999999-07:00",
|
|
||||||
"2006-01-02T15:04:05.999999999-07:00",
|
|
||||||
"2006-01-02 15:04:05.999999999",
|
|
||||||
"2006-01-02T15:04:05.999999999",
|
|
||||||
"2006-01-02 15:04:05",
|
|
||||||
"2006-01-02T15:04:05",
|
|
||||||
"2006-01-02 15:04",
|
|
||||||
"2006-01-02T15:04",
|
|
||||||
"2006-01-02",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ldt *LocalDateTime) Scan(value any) (err error) {
|
|
||||||
var t time.Time
|
|
||||||
if strValue, ok := value.(string); ok {
|
|
||||||
var timeValue time.Time
|
|
||||||
for _, format := range formats {
|
|
||||||
timeValue, err = time.Parse(format, strValue)
|
|
||||||
if err == nil {
|
|
||||||
t = timeValue
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t = timeValue
|
|
||||||
} else {
|
|
||||||
nullTime := &sql.NullTime{}
|
|
||||||
err = nullTime.Scan(value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if nullTime == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t = nullTime.Time
|
|
||||||
}
|
|
||||||
*ldt = LocalDateTime(time.Date(
|
|
||||||
t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.Local,
|
|
||||||
))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ldt LocalDateTime) Value() (driver.Value, error) {
|
|
||||||
return time.Time(ldt).Local(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ldt LocalDateTime) GormDataType() string {
|
|
||||||
return "ldt"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ldt LocalDateTime) GobEncode() ([]byte, error) {
|
|
||||||
return time.Time(ldt).GobEncode()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ldt *LocalDateTime) GobDecode(b []byte) error {
|
|
||||||
return (*time.Time)(ldt).GobDecode(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ldt LocalDateTime) MarshalJSON() ([]byte, error) {
|
|
||||||
return time.Time(ldt).MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ldt *LocalDateTime) UnmarshalJSON(b []byte) error {
|
|
||||||
return (*time.Time)(ldt).UnmarshalJSON(b)
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package orm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql/driver"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Slice[T any] struct {
|
|
||||||
Arr []T
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Slice[T]) Value() (driver.Value, error) {
|
|
||||||
return json.Marshal(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Slice[T]) Scan(value any) error {
|
|
||||||
switch value := value.(type) {
|
|
||||||
case []byte:
|
|
||||||
return json.Unmarshal(value, s)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("不支持的类型: %T", value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -56,7 +56,8 @@ func ListChannels(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 查询数据
|
// 查询数据
|
||||||
channels, err := q.Channel.Debug().
|
channels, err := q.Channel.
|
||||||
|
Preload(q.Channel.Proxy).
|
||||||
Where(cond).
|
Where(cond).
|
||||||
Order(q.Channel.CreatedAt.Desc()).
|
Order(q.Channel.CreatedAt.Desc()).
|
||||||
Offset(req.GetOffset()).
|
Offset(req.GetOffset()).
|
||||||
@@ -110,17 +111,6 @@ type CreateChannelRespItem struct {
|
|||||||
|
|
||||||
func CreateChannel(c *fiber.Ctx) error {
|
func CreateChannel(c *fiber.Ctx) error {
|
||||||
|
|
||||||
// 检查权限
|
|
||||||
authCtx, err := auth.GetAuthCtx(c).PermitUser()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
user := authCtx.User
|
|
||||||
if user.IDToken == nil || *user.IDToken == "" {
|
|
||||||
return fiber.NewError(fiber.StatusForbidden, "账号未实名")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析参数
|
// 解析参数
|
||||||
req := new(CreateChannelReq)
|
req := new(CreateChannelReq)
|
||||||
if err := g.Validator.Validate(c, req); err != nil {
|
if err := g.Validator.Validate(c, req); err != nil {
|
||||||
@@ -135,7 +125,6 @@ func CreateChannel(c *fiber.Ctx) error {
|
|||||||
// 创建通道
|
// 创建通道
|
||||||
result, err := s.Channel.CreateChannels(
|
result, err := s.Channel.CreateChannels(
|
||||||
ip,
|
ip,
|
||||||
user.ID,
|
|
||||||
req.ResourceId,
|
req.ResourceId,
|
||||||
req.AuthType == s.ChannelAuthTypeIp,
|
req.AuthType == s.ChannelAuthTypeIp,
|
||||||
req.AuthType == s.ChannelAuthTypePass,
|
req.AuthType == s.ChannelAuthTypePass,
|
||||||
|
|||||||
@@ -9,20 +9,20 @@ import (
|
|||||||
// Channel 通道表
|
// Channel 通道表
|
||||||
type Channel struct {
|
type Channel struct {
|
||||||
core.Model
|
core.Model
|
||||||
UserID int32 `json:"user_id" gorm:"column:user_id"` // 用户ID
|
UserID int32 `json:"user_id" gorm:"column:user_id"` // 用户ID
|
||||||
ResourceID int32 `json:"resource_id" gorm:"column:resource_id"` // 套餐ID
|
ResourceID int32 `json:"resource_id" gorm:"column:resource_id"` // 套餐ID
|
||||||
ProxyID int32 `json:"proxy_id" gorm:"column:proxy_id"` // 代理ID
|
ProxyID int32 `json:"proxy_id" gorm:"column:proxy_id"` // 代理ID
|
||||||
BatchNo string `json:"batch_no" gorm:"column:batch_no"` // 批次编号
|
BatchNo string `json:"batch_no" gorm:"column:batch_no"` // 批次编号
|
||||||
Port uint16 `json:"port" gorm:"column:port"` // 代理端口
|
Port uint16 `json:"port" gorm:"column:port"` // 代理端口
|
||||||
EdgeID *int32 `json:"edge_id" gorm:"column:edge_id"` // 节点ID(手动配置)
|
EdgeID *int32 `json:"edge_id" gorm:"column:edge_id"` // 节点ID(手动配置)
|
||||||
FilterISP *EdgeISP `json:"filter_isp" gorm:"column:filter_isp"` // 运营商过滤(自动配置):参考 edge.isp
|
FilterISP *EdgeISP `json:"filter_isp" gorm:"column:filter_isp"` // 运营商过滤(自动配置):参考 edge.isp
|
||||||
FilterProv *string `json:"filter_prov" gorm:"column:filter_prov"` // 省份过滤(自动配置)
|
FilterProv *string `json:"filter_prov" gorm:"column:filter_prov"` // 省份过滤(自动配置)
|
||||||
FilterCity *string `json:"filter_city" gorm:"column:filter_city"` // 城市过滤(自动配置)
|
FilterCity *string `json:"filter_city" gorm:"column:filter_city"` // 城市过滤(自动配置)
|
||||||
IP *orm.Inet `json:"ip" gorm:"column:ip"` // 节点地址
|
IP *orm.Inet `json:"ip" gorm:"column:ip"` // 节点地址
|
||||||
Whitelists *orm.Slice[string] `json:"whitelists" gorm:"column:whitelists"` // IP白名单,逗号分隔
|
Whitelists *string `json:"whitelists" gorm:"column:whitelists"` // IP白名单,逗号分隔
|
||||||
Username *string `json:"username" gorm:"column:username"` // 用户名
|
Username *string `json:"username" gorm:"column:username"` // 用户名
|
||||||
Password *string `json:"password" gorm:"column:password"` // 密码
|
Password *string `json:"password" gorm:"column:password"` // 密码
|
||||||
ExpiredAt time.Time `json:"expired_at" gorm:"column:expired_at"` // 过期时间
|
ExpiredAt time.Time `json:"expired_at" gorm:"column:expired_at"` // 过期时间
|
||||||
|
|
||||||
User User `json:"user" gorm:"foreignKey:UserID"`
|
User User `json:"user" gorm:"foreignKey:UserID"`
|
||||||
Resource Resource `json:"resource" gorm:"foreignKey:ResourceID"`
|
Resource Resource `json:"resource" gorm:"foreignKey:ResourceID"`
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func newChannel(db *gorm.DB, opts ...gen.DOOption) channel {
|
|||||||
_channel.FilterProv = field.NewString(tableName, "filter_prov")
|
_channel.FilterProv = field.NewString(tableName, "filter_prov")
|
||||||
_channel.FilterCity = field.NewString(tableName, "filter_city")
|
_channel.FilterCity = field.NewString(tableName, "filter_city")
|
||||||
_channel.IP = field.NewField(tableName, "ip")
|
_channel.IP = field.NewField(tableName, "ip")
|
||||||
_channel.Whitelists = field.NewField(tableName, "whitelists")
|
_channel.Whitelists = field.NewString(tableName, "whitelists")
|
||||||
_channel.Username = field.NewString(tableName, "username")
|
_channel.Username = field.NewString(tableName, "username")
|
||||||
_channel.Password = field.NewString(tableName, "password")
|
_channel.Password = field.NewString(tableName, "password")
|
||||||
_channel.ExpiredAt = field.NewTime(tableName, "expired_at")
|
_channel.ExpiredAt = field.NewTime(tableName, "expired_at")
|
||||||
@@ -149,7 +149,7 @@ type channel struct {
|
|||||||
FilterProv field.String
|
FilterProv field.String
|
||||||
FilterCity field.String
|
FilterCity field.String
|
||||||
IP field.Field
|
IP field.Field
|
||||||
Whitelists field.Field
|
Whitelists field.String
|
||||||
Username field.String
|
Username field.String
|
||||||
Password field.String
|
Password field.String
|
||||||
ExpiredAt field.Time
|
ExpiredAt field.Time
|
||||||
@@ -190,7 +190,7 @@ func (c *channel) updateTableName(table string) *channel {
|
|||||||
c.FilterProv = field.NewString(table, "filter_prov")
|
c.FilterProv = field.NewString(table, "filter_prov")
|
||||||
c.FilterCity = field.NewString(table, "filter_city")
|
c.FilterCity = field.NewString(table, "filter_city")
|
||||||
c.IP = field.NewField(table, "ip")
|
c.IP = field.NewField(table, "ip")
|
||||||
c.Whitelists = field.NewField(table, "whitelists")
|
c.Whitelists = field.NewString(table, "whitelists")
|
||||||
c.Username = field.NewString(table, "username")
|
c.Username = field.NewString(table, "username")
|
||||||
c.Password = field.NewString(table, "password")
|
c.Password = field.NewString(table, "password")
|
||||||
c.ExpiredAt = field.NewTime(table, "expired_at")
|
c.ExpiredAt = field.NewTime(table, "expired_at")
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ func newProxy(db *gorm.DB, opts ...gen.DOOption) proxy {
|
|||||||
_proxy.Secret = field.NewString(tableName, "secret")
|
_proxy.Secret = field.NewString(tableName, "secret")
|
||||||
_proxy.Type = field.NewInt(tableName, "type")
|
_proxy.Type = field.NewInt(tableName, "type")
|
||||||
_proxy.Status = field.NewInt(tableName, "status")
|
_proxy.Status = field.NewInt(tableName, "status")
|
||||||
|
_proxy.Meta = field.NewField(tableName, "meta")
|
||||||
_proxy.Channels = proxyHasManyChannels{
|
_proxy.Channels = proxyHasManyChannels{
|
||||||
db: db.Session(&gorm.Session{}),
|
db: db.Session(&gorm.Session{}),
|
||||||
|
|
||||||
@@ -122,6 +123,7 @@ type proxy struct {
|
|||||||
Secret field.String
|
Secret field.String
|
||||||
Type field.Int
|
Type field.Int
|
||||||
Status field.Int
|
Status field.Int
|
||||||
|
Meta field.Field
|
||||||
Channels proxyHasManyChannels
|
Channels proxyHasManyChannels
|
||||||
|
|
||||||
fieldMap map[string]field.Expr
|
fieldMap map[string]field.Expr
|
||||||
@@ -149,6 +151,7 @@ func (p *proxy) updateTableName(table string) *proxy {
|
|||||||
p.Secret = field.NewString(table, "secret")
|
p.Secret = field.NewString(table, "secret")
|
||||||
p.Type = field.NewInt(table, "type")
|
p.Type = field.NewInt(table, "type")
|
||||||
p.Status = field.NewInt(table, "status")
|
p.Status = field.NewInt(table, "status")
|
||||||
|
p.Meta = field.NewField(table, "meta")
|
||||||
|
|
||||||
p.fillFieldMap()
|
p.fillFieldMap()
|
||||||
|
|
||||||
@@ -165,7 +168,7 @@ func (p *proxy) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *proxy) fillFieldMap() {
|
func (p *proxy) fillFieldMap() {
|
||||||
p.fieldMap = make(map[string]field.Expr, 11)
|
p.fieldMap = make(map[string]field.Expr, 12)
|
||||||
p.fieldMap["id"] = p.ID
|
p.fieldMap["id"] = p.ID
|
||||||
p.fieldMap["created_at"] = p.CreatedAt
|
p.fieldMap["created_at"] = p.CreatedAt
|
||||||
p.fieldMap["updated_at"] = p.UpdatedAt
|
p.fieldMap["updated_at"] = p.UpdatedAt
|
||||||
@@ -176,6 +179,7 @@ func (p *proxy) fillFieldMap() {
|
|||||||
p.fieldMap["secret"] = p.Secret
|
p.fieldMap["secret"] = p.Secret
|
||||||
p.fieldMap["type"] = p.Type
|
p.fieldMap["type"] = p.Type
|
||||||
p.fieldMap["status"] = p.Status
|
p.fieldMap["status"] = p.Status
|
||||||
|
p.fieldMap["meta"] = p.Meta
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ var Channel ChannelService = &channelBaiyinService{}
|
|||||||
|
|
||||||
// 通道服务
|
// 通道服务
|
||||||
type ChannelService interface {
|
type ChannelService interface {
|
||||||
CreateChannels(source netip.Addr, userId int32, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter ...EdgeFilter) ([]*m.Channel, error)
|
CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter ...EdgeFilter) ([]*m.Channel, error)
|
||||||
RemoveChannels(batch string, ids []int32) error
|
RemoveChannels(batch string, ids []int32) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,12 +47,11 @@ func genPassPair() (string, string) {
|
|||||||
return string(username), string(password)
|
return string(username), string(password)
|
||||||
}
|
}
|
||||||
|
|
||||||
func findResource(q *q.Query, resourceId int32, userId int32, count int, now time.Time) (*ResourceView, error) {
|
func findResource(q *q.Query, resourceId int32, count int, now time.Time) (*ResourceView, error) {
|
||||||
resource, err := q.Resource.
|
resource, err := q.Resource.
|
||||||
Preload(field.Associations).
|
Preload(field.Associations).
|
||||||
Where(
|
Where(
|
||||||
q.Resource.ID.Eq(resourceId),
|
q.Resource.ID.Eq(resourceId),
|
||||||
q.Resource.UserID.Eq(userId),
|
|
||||||
q.Resource.Active.Is(true),
|
q.Resource.Active.Is(true),
|
||||||
).
|
).
|
||||||
Take()
|
Take()
|
||||||
@@ -64,6 +63,7 @@ func findResource(q *q.Query, resourceId int32, userId int32, count int, now tim
|
|||||||
Id: resource.ID,
|
Id: resource.ID,
|
||||||
Active: resource.Active,
|
Active: resource.Active,
|
||||||
Type: resource.Type,
|
Type: resource.Type,
|
||||||
|
User: resource.User,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch resource.Type {
|
switch resource.Type {
|
||||||
@@ -114,35 +114,6 @@ func findResource(q *q.Query, resourceId int32, userId int32, count int, now tim
|
|||||||
info.Used = sub.Used
|
info.Used = sub.Used
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查套餐使用情况
|
|
||||||
switch info.Mode {
|
|
||||||
default:
|
|
||||||
return nil, core.NewBizErr("不支持的套餐模式")
|
|
||||||
|
|
||||||
// 包时
|
|
||||||
case m.ResourceModeTime:
|
|
||||||
// 检查过期时间
|
|
||||||
if info.Expire.Before(now) {
|
|
||||||
return nil, ErrResourceExpired
|
|
||||||
}
|
|
||||||
// 检查每日限额
|
|
||||||
used := 0
|
|
||||||
if now.Format("2006-01-02") == info.DailyLast.Format("2006-01-02") {
|
|
||||||
used = int(info.DailyUsed)
|
|
||||||
}
|
|
||||||
excess := used+count > int(info.DailyLimit)
|
|
||||||
if excess {
|
|
||||||
return nil, ErrResourceDailyLimit
|
|
||||||
}
|
|
||||||
|
|
||||||
// 包量
|
|
||||||
case m.ResourceModeQuota:
|
|
||||||
// 检查可用配额
|
|
||||||
if int(info.Quota)-int(info.Used) < count {
|
|
||||||
return nil, ErrResourceExhausted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,14 +130,17 @@ type ResourceView struct {
|
|||||||
Quota int32
|
Quota int32
|
||||||
Used int32
|
Used int32
|
||||||
Expire time.Time
|
Expire time.Time
|
||||||
|
User m.User
|
||||||
}
|
}
|
||||||
|
|
||||||
func lockChans(batch string, count int, expire time.Time) ([]netip.AddrPort, error) {
|
func lockChans(batch string, count int, expire time.Time) ([]netip.AddrPort, error) {
|
||||||
chans, err := g.Redis.Eval(
|
chans, err := g.Redis.Eval(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
RedisScriptLockChans,
|
RedisScriptLockChans,
|
||||||
[]string{"channel"},
|
[]string{
|
||||||
batch,
|
"channel:chans",
|
||||||
|
"channel:lease:" + batch,
|
||||||
|
},
|
||||||
count,
|
count,
|
||||||
expire.Unix(),
|
expire.Unix(),
|
||||||
).StringSlice()
|
).StringSlice()
|
||||||
@@ -187,13 +161,10 @@ func lockChans(batch string, count int, expire time.Time) ([]netip.AddrPort, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
var RedisScriptLockChans = `
|
var RedisScriptLockChans = `
|
||||||
local key = KEYS[1]
|
local chans_key = KEYS[1]
|
||||||
local batch = ARGV[1]
|
local lease_key = KEYS[2]
|
||||||
local count = tonumber(ARGV[2])
|
local count = tonumber(ARGV[1])
|
||||||
local expire = tonumber(ARGV[3])
|
local expire = tonumber(ARGV[2])
|
||||||
|
|
||||||
local chans_key = key .. ":chans"
|
|
||||||
local lease_key = key .. ":lease:" .. batch
|
|
||||||
|
|
||||||
if redis.call("SCARD", chans_key) < count then
|
if redis.call("SCARD", chans_key) < count then
|
||||||
return nil
|
return nil
|
||||||
@@ -210,12 +181,19 @@ return ports
|
|||||||
`
|
`
|
||||||
|
|
||||||
func freeChans(batch string, chans []string) error {
|
func freeChans(batch string, chans []string) error {
|
||||||
|
values := make([]any, len(chans))
|
||||||
|
for i, ch := range chans {
|
||||||
|
values[i] = ch
|
||||||
|
}
|
||||||
|
|
||||||
err := g.Redis.Eval(
|
err := g.Redis.Eval(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
RedisScriptFreeChans,
|
RedisScriptFreeChans,
|
||||||
[]string{"channel"},
|
[]string{
|
||||||
batch,
|
"channel:chans",
|
||||||
chans,
|
"channel:lease:" + batch,
|
||||||
|
},
|
||||||
|
values...,
|
||||||
).Err()
|
).Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return core.NewBizErr("释放通道失败", err)
|
return core.NewBizErr("释放通道失败", err)
|
||||||
@@ -225,15 +203,11 @@ func freeChans(batch string, chans []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var RedisScriptFreeChans = `
|
var RedisScriptFreeChans = `
|
||||||
local key = KEYS[1]
|
local chans_key = KEYS[1]
|
||||||
local batch = ARGV[1]
|
local lease_key = KEYS[2]
|
||||||
local chans = ARGV[2]
|
local chans = ARGV
|
||||||
|
|
||||||
local chans_key = key .. ":chans"
|
|
||||||
local lease_key = key .. ":lease:" .. batch
|
|
||||||
|
|
||||||
redis.call("SADD", chans_key, unpack(chans))
|
redis.call("SADD", chans_key, unpack(chans))
|
||||||
|
|
||||||
redis.call("DEL", lease_key)
|
redis.call("DEL", lease_key)
|
||||||
|
|
||||||
return chans
|
return chans
|
||||||
|
|||||||
@@ -18,11 +18,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
|
"gorm.io/gen/field"
|
||||||
)
|
)
|
||||||
|
|
||||||
type channelBaiyinService struct{}
|
type channelBaiyinService struct{}
|
||||||
|
|
||||||
func (s *channelBaiyinService) CreateChannels(source netip.Addr, userId int32, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter ...EdgeFilter) ([]*m.Channel, error) {
|
func (s *channelBaiyinService) CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter ...EdgeFilter) ([]*m.Channel, error) {
|
||||||
if count > 400 {
|
if count > 400 {
|
||||||
return nil, core.NewBizErr("单次最多提取 400 个")
|
return nil, core.NewBizErr("单次最多提取 400 个")
|
||||||
}
|
}
|
||||||
@@ -35,9 +36,21 @@ func (s *channelBaiyinService) CreateChannels(source netip.Addr, userId int32, r
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
batch := ID.GenReadable("bat")
|
batch := ID.GenReadable("bat")
|
||||||
|
|
||||||
|
// 获取用户套餐
|
||||||
|
resource, err := findResource(q.Q, resourceId, count, now)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户
|
||||||
|
user := resource.User
|
||||||
|
if user.IDToken == nil || *user.IDToken == "" {
|
||||||
|
return nil, core.NewBizErr("账号未实名")
|
||||||
|
}
|
||||||
|
|
||||||
// 获取用户白名单并检查用户 ip 地址
|
// 获取用户白名单并检查用户 ip 地址
|
||||||
whitelists, err := q.Whitelist.Where(
|
whitelists, err := q.Whitelist.Where(
|
||||||
q.Whitelist.UserID.Eq(userId),
|
q.Whitelist.UserID.Eq(user.ID),
|
||||||
).Find()
|
).Find()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -55,11 +68,35 @@ func (s *channelBaiyinService) CreateChannels(source netip.Addr, userId int32, r
|
|||||||
return nil, core.NewBizErr(fmt.Sprintf("IP 地址 %s 不在白名单内", source.String()))
|
return nil, core.NewBizErr(fmt.Sprintf("IP 地址 %s 不在白名单内", source.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户套餐并检查
|
// 检查套餐使用情况
|
||||||
resource, err := findResource(q.Q, resourceId, userId, count, now)
|
switch resource.Mode {
|
||||||
if err != nil {
|
default:
|
||||||
return nil, err
|
return nil, core.NewBizErr("不支持的套餐模式")
|
||||||
|
|
||||||
|
// 包时
|
||||||
|
case m.ResourceModeTime:
|
||||||
|
// 检查过期时间
|
||||||
|
if resource.Expire.Before(now) {
|
||||||
|
return nil, ErrResourceExpired
|
||||||
|
}
|
||||||
|
// 检查每日限额
|
||||||
|
used := 0
|
||||||
|
if now.Format("2006-01-02") == resource.DailyLast.Format("2006-01-02") {
|
||||||
|
used = int(resource.DailyUsed)
|
||||||
|
}
|
||||||
|
excess := used+count > int(resource.DailyLimit)
|
||||||
|
if excess {
|
||||||
|
return nil, ErrResourceDailyLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
// 包量
|
||||||
|
case m.ResourceModeQuota:
|
||||||
|
// 检查可用配额
|
||||||
|
if int(resource.Quota)-int(resource.Used) < count {
|
||||||
|
return nil, ErrResourceExhausted
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expire := now.Add(resource.Live)
|
expire := now.Add(resource.Live)
|
||||||
|
|
||||||
// 获取可用通道
|
// 获取可用通道
|
||||||
@@ -104,18 +141,19 @@ func (s *channelBaiyinService) CreateChannels(source netip.Addr, userId int32, r
|
|||||||
|
|
||||||
// 使用记录
|
// 使用记录
|
||||||
actions[i] = &m.LogsUserUsage{
|
actions[i] = &m.LogsUserUsage{
|
||||||
UserID: userId,
|
UserID: user.ID,
|
||||||
ResourceID: resourceId,
|
ResourceID: resourceId,
|
||||||
Count: int32(count),
|
Count: int32(count),
|
||||||
ISP: u.P(filter.Isp.String()),
|
ISP: u.P(filter.Isp.String()),
|
||||||
Prov: filter.Prov,
|
Prov: filter.Prov,
|
||||||
City: filter.City,
|
City: filter.City,
|
||||||
|
IP: orm.Inet{Addr: source},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通道数据
|
// 通道数据
|
||||||
inet := orm.Inet{Addr: ch.Addr()}
|
inet := orm.Inet{Addr: ch.Addr()}
|
||||||
channels[i] = &m.Channel{
|
channels[i] = &m.Channel{
|
||||||
UserID: userId,
|
UserID: user.ID,
|
||||||
ResourceID: resourceId,
|
ResourceID: resourceId,
|
||||||
BatchNo: batch,
|
BatchNo: batch,
|
||||||
ProxyID: findProxy[inet].ID,
|
ProxyID: findProxy[inet].ID,
|
||||||
@@ -124,9 +162,10 @@ func (s *channelBaiyinService) CreateChannels(source netip.Addr, userId int32, r
|
|||||||
FilterProv: filter.Prov,
|
FilterProv: filter.Prov,
|
||||||
FilterCity: filter.City,
|
FilterCity: filter.City,
|
||||||
ExpiredAt: expire,
|
ExpiredAt: expire,
|
||||||
|
Proxy: *findProxy[inet],
|
||||||
}
|
}
|
||||||
if authWhitelist {
|
if authWhitelist {
|
||||||
channels[i].Whitelists = &orm.Slice[string]{Arr: whitelistIPs}
|
channels[i].Whitelists = u.P(strings.Join(whitelistIPs, ","))
|
||||||
}
|
}
|
||||||
if authPassword {
|
if authPassword {
|
||||||
username, password := genPassPair()
|
username, password := genPassPair()
|
||||||
@@ -181,7 +220,9 @@ func (s *channelBaiyinService) CreateChannels(source netip.Addr, userId int32, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 保存通道和分配记录
|
// 保存通道和分配记录
|
||||||
err = q.Channel.Create(channels...)
|
err = q.Channel.
|
||||||
|
Omit(field.AssociationFields).
|
||||||
|
Create(channels...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return core.NewServErr("保存通道失败", err)
|
return core.NewServErr("保存通道失败", err)
|
||||||
}
|
}
|
||||||
@@ -226,7 +267,7 @@ func (s *channelBaiyinService) CreateChannels(source netip.Addr, userId int32, r
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
if authWhitelist {
|
if authWhitelist {
|
||||||
configs[i].Whitelist = &channel.Whitelists.Arr
|
configs[i].Whitelist = &whitelistIPs
|
||||||
}
|
}
|
||||||
if authPassword {
|
if authPassword {
|
||||||
configs[i].Userpass = u.P(fmt.Sprintf("%s:%s", *channel.Username, *channel.Password))
|
configs[i].Userpass = u.P(fmt.Sprintf("%s:%s", *channel.Username, *channel.Password))
|
||||||
@@ -248,9 +289,10 @@ func (s *channelBaiyinService) CreateChannels(source netip.Addr, userId int32, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *channelBaiyinService) RemoveChannels(batch string, ids []int32) error {
|
func (s *channelBaiyinService) RemoveChannels(batch string, ids []int32) error {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
// 获取连接数据
|
// 获取连接数据
|
||||||
channels, err := q.Channel.Debug().
|
channels, err := q.Channel.
|
||||||
Preload(q.Channel.Proxy).
|
Preload(q.Channel.Proxy).
|
||||||
Where(q.Channel.ID.In(ids...)).
|
Where(q.Channel.ID.In(ids...)).
|
||||||
Find()
|
Find()
|
||||||
@@ -279,7 +321,7 @@ func (s *channelBaiyinService) RemoveChannels(batch string, ids []int32) error {
|
|||||||
// 释放端口
|
// 释放端口
|
||||||
err = freeChans(batch, chans)
|
err = freeChans(batch, chans)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return core.NewServErr("释放端口失败", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空配置
|
// 清空配置
|
||||||
@@ -304,9 +346,10 @@ func (s *channelBaiyinService) RemoveChannels(batch string, ids []int32) error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bytes, _ := json.Marshal(configs)
|
bytes, _ := json.Marshal(configs)
|
||||||
slog.Debug("清除代理端口配置", "config", bytes)
|
slog.Debug("清除代理端口配置", "config", string(bytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
slog.Debug("清除代理端口配置", "time", time.Since(start).String())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ import (
|
|||||||
e "platform/web/events"
|
e "platform/web/events"
|
||||||
g "platform/web/globals"
|
g "platform/web/globals"
|
||||||
m "platform/web/models"
|
m "platform/web/models"
|
||||||
|
q "platform/web/queries"
|
||||||
s "platform/web/services"
|
s "platform/web/services"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
|
"gorm.io/datatypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HandleCompleteTrade(_ context.Context, task *asynq.Task) (err error) {
|
func HandleCompleteTrade(_ context.Context, task *asynq.Task) (err error) {
|
||||||
@@ -58,7 +60,7 @@ func HandleRemoveChannel(_ context.Context, task *asynq.Task) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func HandleFlushGateway(_ context.Context, task *asynq.Task) (err error) {
|
func HandleFlushGateway(_ context.Context, task *asynq.Task) (err error) {
|
||||||
now := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
// 获取所有网关:配置组
|
// 获取所有网关:配置组
|
||||||
proxies, err := s.Proxy.AllProxies(m.ProxyTypeBaiYin, true)
|
proxies, err := s.Proxy.AllProxies(m.ProxyTypeBaiYin, true)
|
||||||
@@ -120,16 +122,26 @@ func HandleFlushGateway(_ context.Context, task *asynq.Task) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if env.DebugExternalChange {
|
if env.DebugExternalChange {
|
||||||
g.Cloud.CloudConnect(g.CloudConnectReq{
|
err := g.Cloud.CloudConnect(g.CloudConnectReq{
|
||||||
Uuid: proxy.Mac,
|
Uuid: proxy.Mac,
|
||||||
AutoConfig: configs,
|
AutoConfig: configs,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("提交代理后备配置失败", "error", err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
bytes, _ := json.Marshal(configs)
|
bytes, _ := json.Marshal(configs)
|
||||||
slog.Debug("更新代理后备配置", "config", string(bytes))
|
slog.Debug("更新代理后备配置", "config", string(bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err := q.Proxy.
|
||||||
|
Where(q.Proxy.ID.Eq(proxy.ID)).
|
||||||
|
UpdateSimple(q.Proxy.Meta.Value(datatypes.NewJSONType(configs)))
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("更新代理后备配置失败", "error", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("更新代理后备配置", "time", time.Since(now).String())
|
slog.Debug("更新代理后备配置", "time", time.Since(start).String())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user