2025-03-25 09:49:56 +08:00
|
|
|
|
package services
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2025-03-26 14:57:44 +08:00
|
|
|
|
"context"
|
2025-03-28 18:15:03 +08:00
|
|
|
|
"encoding/json"
|
2025-03-25 09:49:56 +08:00
|
|
|
|
"errors"
|
2025-03-26 14:57:44 +08:00
|
|
|
|
"fmt"
|
2025-03-28 10:03:29 +08:00
|
|
|
|
"log/slog"
|
2025-03-26 14:57:44 +08:00
|
|
|
|
"math"
|
2025-03-28 10:03:29 +08:00
|
|
|
|
"platform/pkg/orm"
|
2025-03-26 14:57:44 +08:00
|
|
|
|
"platform/pkg/rds"
|
2025-03-28 10:03:29 +08:00
|
|
|
|
"platform/pkg/remote"
|
2025-03-26 14:57:44 +08:00
|
|
|
|
"platform/web/common"
|
2025-03-25 09:49:56 +08:00
|
|
|
|
"platform/web/models"
|
|
|
|
|
|
q "platform/web/queries"
|
2025-03-26 14:57:44 +08:00
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
|
|
"github.com/jxskiss/base62"
|
|
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
|
|
|
|
"gorm.io/gorm"
|
2025-03-25 09:49:56 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
var Channel = &channelService{}
|
|
|
|
|
|
|
|
|
|
|
|
type channelService struct {
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-26 14:57:44 +08:00
|
|
|
|
// CreateChannel 创建连接通道,并返回连接信息,如果配额不足则返回错误
|
2025-03-25 09:49:56 +08:00
|
|
|
|
func (s *channelService) CreateChannel(
|
2025-03-26 14:57:44 +08:00
|
|
|
|
ctx context.Context,
|
|
|
|
|
|
auth *AuthContext,
|
|
|
|
|
|
resourceId int32,
|
|
|
|
|
|
protocol ChannelProtocol,
|
|
|
|
|
|
authType ChannelAuthType,
|
2025-03-25 09:49:56 +08:00
|
|
|
|
count int,
|
2025-03-26 14:57:44 +08:00
|
|
|
|
nodeFilter ...NodeFilterConfig,
|
2025-03-25 09:49:56 +08:00
|
|
|
|
) ([]*models.Channel, error) {
|
|
|
|
|
|
|
2025-03-26 14:57:44 +08:00
|
|
|
|
// 创建通道
|
|
|
|
|
|
var channels []*models.Channel
|
|
|
|
|
|
err := q.Q.Transaction(func(tx *q.Query) error {
|
|
|
|
|
|
// 查找套餐
|
2025-03-28 18:15:03 +08:00
|
|
|
|
var resource = ResourceInfo{}
|
2025-03-26 14:57:44 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查使用人
|
2025-03-28 18:15:03 +08:00
|
|
|
|
if auth.Payload.Type == PayloadUser && auth.Payload.Id != resource.UserId {
|
2025-03-26 14:57:44 +08:00
|
|
|
|
return common.AuthForbiddenErr("无权限访问")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查套餐状态
|
2025-03-28 18:15:03 +08:00
|
|
|
|
if !resource.Active {
|
2025-03-26 14:57:44 +08:00
|
|
|
|
return ChannelServiceErr("套餐已失效")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查每日限额
|
2025-03-28 18:15:03 +08:00
|
|
|
|
today := time.Now().Format("2006-01-02") == resource.DailyLast.Format("2006-01-02")
|
|
|
|
|
|
dailyRemain := int(math.Max(float64(resource.DailyLimit-resource.DailyUsed), 0))
|
2025-03-26 14:57:44 +08:00
|
|
|
|
if today && dailyRemain < count {
|
|
|
|
|
|
return ChannelServiceErr("套餐每日配额不足")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查时间或配额
|
2025-03-28 18:15:03 +08:00
|
|
|
|
if resource.Type == 1 { // 包时
|
|
|
|
|
|
if resource.Expire.Before(time.Now()) {
|
2025-03-26 14:57:44 +08:00
|
|
|
|
return ChannelServiceErr("套餐已过期")
|
|
|
|
|
|
}
|
|
|
|
|
|
} else { // 包量
|
2025-03-28 18:15:03 +08:00
|
|
|
|
remain := int(math.Max(float64(resource.Quota-resource.Used), 0))
|
2025-03-26 14:57:44 +08:00
|
|
|
|
if remain < count {
|
|
|
|
|
|
return ChannelServiceErr("套餐配额不足")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 筛选可用节点
|
2025-03-28 10:03:29 +08:00
|
|
|
|
nodes, err := Node.Filter(ctx, auth.Payload.Id, count, nodeFilter...)
|
2025-03-26 14:57:44 +08:00
|
|
|
|
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,
|
2025-03-28 10:03:29 +08:00
|
|
|
|
UserHost: allowed.Host,
|
|
|
|
|
|
NodeHost: node.Host,
|
|
|
|
|
|
ProxyPort: node.ProxyPort,
|
2025-03-26 14:57:44 +08:00
|
|
|
|
Protocol: string(protocol),
|
|
|
|
|
|
AuthIP: authType == ChannelAuthTypeIp,
|
|
|
|
|
|
AuthPass: authType == ChannelAuthTypePass,
|
|
|
|
|
|
Username: username,
|
|
|
|
|
|
Password: password,
|
2025-03-28 18:15:03 +08:00
|
|
|
|
Expiration: time.Now().Add(time.Duration(resource.Live) * time.Second),
|
2025-03-26 14:57:44 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存到数据库
|
|
|
|
|
|
err = tx.Channel.Create(channels...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新套餐使用记录
|
|
|
|
|
|
if today {
|
2025-03-28 18:15:03 +08:00
|
|
|
|
resource.DailyUsed += int32(count)
|
|
|
|
|
|
resource.Used += int32(count)
|
2025-03-26 14:57:44 +08:00
|
|
|
|
} else {
|
2025-03-28 18:15:03 +08:00
|
|
|
|
resource.DailyLast = time.Now()
|
|
|
|
|
|
resource.DailyUsed = int32(count)
|
|
|
|
|
|
resource.Used += int32(count)
|
2025-03-26 14:57:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
err = tx.ResourcePss.
|
2025-03-28 18:15:03 +08:00
|
|
|
|
Where(q.ResourcePss.ID.Eq(resource.Id)).
|
|
|
|
|
|
Select(
|
|
|
|
|
|
q.ResourcePss.Used,
|
|
|
|
|
|
q.ResourcePss.DailyUsed,
|
|
|
|
|
|
q.ResourcePss.DailyLast).
|
|
|
|
|
|
Save(&models.ResourcePss{
|
|
|
|
|
|
Used: resource.Used,
|
|
|
|
|
|
DailyUsed: resource.DailyUsed,
|
|
|
|
|
|
DailyLast: resource.DailyLast})
|
2025-03-26 14:57:44 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
2025-03-25 09:49:56 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-26 14:57:44 +08:00
|
|
|
|
// 缓存通道信息与异步删除任务
|
2025-03-28 18:15:03 +08:00
|
|
|
|
// err = cache(ctx, channels)
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return nil, err
|
|
|
|
|
|
// }
|
2025-03-25 09:49:56 +08:00
|
|
|
|
|
2025-03-26 14:57:44 +08:00
|
|
|
|
// 返回连接通道列表
|
|
|
|
|
|
return channels, errors.New("not implemented")
|
|
|
|
|
|
}
|
2025-03-25 09:49:56 +08:00
|
|
|
|
|
2025-03-26 14:57:44 +08:00
|
|
|
|
type ChannelAuthType int
|
2025-03-25 09:49:56 +08:00
|
|
|
|
|
2025-03-26 14:57:44 +08:00
|
|
|
|
const (
|
|
|
|
|
|
ChannelAuthTypeIp = iota
|
|
|
|
|
|
ChannelAuthTypePass
|
|
|
|
|
|
)
|
2025-03-25 09:49:56 +08:00
|
|
|
|
|
2025-03-26 14:57:44 +08:00
|
|
|
|
type ChannelProtocol string
|
2025-03-25 09:49:56 +08:00
|
|
|
|
|
2025-03-26 14:57:44 +08:00
|
|
|
|
const (
|
|
|
|
|
|
ProtocolSocks5 = ChannelProtocol("socks5")
|
|
|
|
|
|
ProtocolHTTP = ChannelProtocol("http")
|
|
|
|
|
|
ProtocolHttps = ChannelProtocol("https")
|
|
|
|
|
|
)
|
2025-03-25 09:49:56 +08:00
|
|
|
|
|
2025-03-26 14:57:44 +08:00
|
|
|
|
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
|
2025-03-25 09:49:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-28 10:03:29 +08:00
|
|
|
|
func (s *channelService) RemoveChannels(ctx context.Context, auth *AuthContext, id ...int32) error {
|
2025-03-25 09:49:56 +08:00
|
|
|
|
|
2025-03-26 14:57:44 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除缓存,异步任务直接在消费端处理删除
|
2025-03-28 10:03:29 +08:00
|
|
|
|
err = deleteCache(ctx, channels)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
2025-03-26 14:57:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type ChannelServiceErr string
|
|
|
|
|
|
|
|
|
|
|
|
func (c ChannelServiceErr) Error() string {
|
|
|
|
|
|
return string(c)
|
|
|
|
|
|
}
|
2025-03-28 10:03:29 +08:00
|
|
|
|
|
|
|
|
|
|
// region channel by remote
|
|
|
|
|
|
|
|
|
|
|
|
func (s *channelService) RemoteCreateChannel(
|
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
|
auth *AuthContext,
|
|
|
|
|
|
resourceId int32,
|
|
|
|
|
|
protocol ChannelProtocol,
|
|
|
|
|
|
authType ChannelAuthType,
|
|
|
|
|
|
count int,
|
|
|
|
|
|
nodeFilter ...NodeFilterConfig,
|
2025-03-28 18:15:03 +08:00
|
|
|
|
) ([]AssignPortResult, error) {
|
2025-03-28 10:03:29 +08:00
|
|
|
|
|
|
|
|
|
|
filter := NodeFilterConfig{}
|
|
|
|
|
|
if len(nodeFilter) > 0 {
|
|
|
|
|
|
filter = nodeFilter[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找套餐
|
|
|
|
|
|
var resource = new(ResourceInfo)
|
|
|
|
|
|
data := q.Resource.As("data")
|
|
|
|
|
|
pss := q.ResourcePss.As("pss")
|
2025-03-28 18:15:03 +08:00
|
|
|
|
err := data.Debug().Scopes(orm.Alias(data)).
|
|
|
|
|
|
Select(
|
|
|
|
|
|
data.ID, data.UserID, data.Active,
|
|
|
|
|
|
pss.Type, pss.Live, pss.DailyUsed, pss.DailyLimit, pss.DailyLast, pss.Quota, pss.Used, pss.Expire,
|
|
|
|
|
|
).
|
2025-03-28 10:03:29 +08:00
|
|
|
|
LeftJoin(q.ResourcePss.As("pss"), pss.ResourceID.EqCol(data.ID)).
|
|
|
|
|
|
Where(data.ID.Eq(resourceId)).
|
|
|
|
|
|
Scan(&resource)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
|
return nil, ChannelServiceErr("套餐不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查用户权限
|
|
|
|
|
|
err = checkUser(auth, resource, count)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-03-28 18:15:03 +08:00
|
|
|
|
slog.Debug("检查用户权限完成")
|
2025-03-28 10:03:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 申请节点
|
2025-03-28 18:15:03 +08:00
|
|
|
|
edgeAssigns, err := assignEdge(count, filter)
|
2025-03-28 10:03:29 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-03-28 18:15:03 +08:00
|
|
|
|
debugAssigned := fmt.Sprintf("%+v", edgeAssigns)
|
|
|
|
|
|
slog.Debug("申请节点完成", "edgeAssigns", debugAssigned)
|
2025-03-28 10:03:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 分配端口
|
2025-03-28 18:15:03 +08:00
|
|
|
|
expiration := time.Now().Add(time.Duration(resource.Live) * time.Second)
|
|
|
|
|
|
portAssigns, err := assignPort(edgeAssigns, auth.Payload.Id, protocol, authType, expiration, filter)
|
2025-03-28 10:03:29 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-03-28 18:15:03 +08:00
|
|
|
|
debugChannels := fmt.Sprintf("%+v", portAssigns)
|
|
|
|
|
|
slog.Debug("分配端口完成", "portAssigns", debugChannels)
|
2025-03-28 10:03:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 缓存并关闭代理
|
2025-03-28 18:15:03 +08:00
|
|
|
|
err = cache(ctx, portAssigns)
|
2025-03-28 10:03:29 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-28 18:15:03 +08:00
|
|
|
|
return portAssigns, nil
|
2025-03-28 10:03:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
|
|
|
|
|
|
func checkUser(auth *AuthContext, resource *ResourceInfo, count int) error {
|
|
|
|
|
|
|
|
|
|
|
|
// 检查使用人
|
2025-03-28 18:15:03 +08:00
|
|
|
|
if auth.Payload.Type == PayloadUser && auth.Payload.Id != resource.UserId {
|
2025-03-28 10:03:29 +08:00
|
|
|
|
return common.AuthForbiddenErr("无权限访问")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查套餐状态
|
2025-03-28 18:15:03 +08:00
|
|
|
|
if !resource.Active {
|
2025-03-28 10:03:29 +08:00
|
|
|
|
return ChannelServiceErr("套餐已失效")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查每日限额
|
2025-03-28 18:15:03 +08:00
|
|
|
|
today := time.Now().Format("2006-01-02") == resource.DailyLast.Format("2006-01-02")
|
|
|
|
|
|
dailyRemain := int(math.Max(float64(resource.DailyLimit-resource.DailyUsed), 0))
|
2025-03-28 10:03:29 +08:00
|
|
|
|
if today && dailyRemain < count {
|
|
|
|
|
|
return ChannelServiceErr("套餐每日配额不足")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查时间或配额
|
2025-03-28 18:15:03 +08:00
|
|
|
|
if resource.Type == 1 { // 包时
|
|
|
|
|
|
if resource.Expire.Before(time.Now()) {
|
2025-03-28 10:03:29 +08:00
|
|
|
|
return ChannelServiceErr("套餐已过期")
|
|
|
|
|
|
}
|
|
|
|
|
|
} else { // 包量
|
2025-03-28 18:15:03 +08:00
|
|
|
|
remain := int(math.Max(float64(resource.Quota-resource.Used), 0))
|
2025-03-28 10:03:29 +08:00
|
|
|
|
if remain < count {
|
|
|
|
|
|
return ChannelServiceErr("套餐配额不足")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// assignEdge 分配边缘节点数量
|
2025-03-28 18:15:03 +08:00
|
|
|
|
func assignEdge(count int, filter NodeFilterConfig) ([]*AssignEdgeResult, error) {
|
2025-03-28 10:03:29 +08:00
|
|
|
|
// 查询现有节点连接情况
|
|
|
|
|
|
edgeConfigs, err := remote.Client.CloudAutoQuery()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
proxies, err := q.Proxy.
|
|
|
|
|
|
Where(q.Proxy.Type.Eq(1)).
|
|
|
|
|
|
Find()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-28 18:15:03 +08:00
|
|
|
|
// 过滤需要变动的连接配置
|
|
|
|
|
|
type ConfigInfo struct {
|
|
|
|
|
|
proxy *models.Proxy
|
|
|
|
|
|
config *remote.AutoConfig
|
2025-03-28 10:03:29 +08:00
|
|
|
|
}
|
2025-03-28 18:15:03 +08:00
|
|
|
|
var total = count
|
|
|
|
|
|
var assigns = make([]*AssignEdgeResult, len(proxies), len(proxies))
|
|
|
|
|
|
for i, proxy := range proxies {
|
|
|
|
|
|
remoteConfigs := edgeConfigs[proxy.Name]
|
|
|
|
|
|
for _, config := range remoteConfigs {
|
|
|
|
|
|
if config.Isp == filter.Isp && config.City == filter.City && config.Province == filter.Prov {
|
|
|
|
|
|
total += config.Count
|
|
|
|
|
|
assigns[i] = &AssignEdgeResult{
|
|
|
|
|
|
proxy: proxy,
|
|
|
|
|
|
count: config.Count,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if assigns[i] == nil {
|
|
|
|
|
|
assigns[i] = &AssignEdgeResult{
|
2025-03-28 10:03:29 +08:00
|
|
|
|
proxy: proxy,
|
2025-03-28 18:15:03 +08:00
|
|
|
|
count: 0,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
avg := int(math.Ceil(float64(total) / float64(len(proxies))))
|
|
|
|
|
|
|
|
|
|
|
|
for i, assign := range assigns {
|
|
|
|
|
|
var prev = assign.count
|
|
|
|
|
|
var next = assign.count
|
|
|
|
|
|
if prev < avg && prev < total {
|
|
|
|
|
|
next = int(math.Min(float64(avg), float64(total)))
|
|
|
|
|
|
assigns[i].count = next - prev
|
|
|
|
|
|
total -= next
|
2025-03-28 10:03:29 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
2025-03-28 18:15:03 +08:00
|
|
|
|
// err := remote.Client.CloudConnect(remote.CloudConnectReq{
|
|
|
|
|
|
// Uuid: assign.Proxy.Name,
|
|
|
|
|
|
// Edge: nil,
|
|
|
|
|
|
// AutoConfig: []remote.AutoConfig{{
|
|
|
|
|
|
// Province: filter.Prov,
|
|
|
|
|
|
// City: filter.City,
|
|
|
|
|
|
// Isp: filter.Isp,
|
|
|
|
|
|
// Count: next,
|
|
|
|
|
|
// }},
|
|
|
|
|
|
// })
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return nil, err
|
|
|
|
|
|
// }
|
2025-03-28 10:03:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-28 18:15:03 +08:00
|
|
|
|
return assigns, nil
|
2025-03-28 10:03:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type AssignEdgeResult struct {
|
|
|
|
|
|
proxy *models.Proxy
|
|
|
|
|
|
count int
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// assignPort 分配指定数量的端口
|
|
|
|
|
|
func assignPort(
|
2025-03-28 18:15:03 +08:00
|
|
|
|
assigns []*AssignEdgeResult,
|
2025-03-28 10:03:29 +08:00
|
|
|
|
userId int32,
|
|
|
|
|
|
protocol ChannelProtocol,
|
|
|
|
|
|
authType ChannelAuthType,
|
|
|
|
|
|
expiration time.Time,
|
|
|
|
|
|
filter NodeFilterConfig,
|
2025-03-28 18:15:03 +08:00
|
|
|
|
) ([]AssignPortResult, error) {
|
2025-03-28 10:03:29 +08:00
|
|
|
|
// 查询代理已配置端口
|
|
|
|
|
|
var proxyIds = make([]int32, 0, len(assigns))
|
|
|
|
|
|
for _, assigned := range assigns {
|
|
|
|
|
|
proxyIds = append(proxyIds, assigned.proxy.ID)
|
|
|
|
|
|
}
|
|
|
|
|
|
channels, err := q.Channel.
|
|
|
|
|
|
Select(
|
|
|
|
|
|
q.Channel.ProxyID,
|
|
|
|
|
|
q.Channel.ProxyPort).
|
|
|
|
|
|
Where(
|
|
|
|
|
|
q.Channel.ProxyID.In(proxyIds...),
|
|
|
|
|
|
q.Channel.Expiration.Gt(time.Now())).
|
|
|
|
|
|
Group(
|
2025-03-28 18:15:03 +08:00
|
|
|
|
q.Channel.ProxyPort,
|
|
|
|
|
|
q.Channel.ProxyID).
|
2025-03-28 10:03:29 +08:00
|
|
|
|
Find()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 端口查找表
|
|
|
|
|
|
var proxyPorts = make(map[uint64]struct{})
|
|
|
|
|
|
for _, channel := range channels {
|
|
|
|
|
|
key := uint64(channel.ProxyID)<<32 | uint64(channel.ProxyPort)
|
|
|
|
|
|
proxyPorts[key] = struct{}{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 配置启用代理
|
2025-03-28 18:15:03 +08:00
|
|
|
|
var result = make([]AssignPortResult, len(assigns))
|
|
|
|
|
|
for i, assign := range assigns {
|
|
|
|
|
|
var proxy = assign.proxy
|
|
|
|
|
|
var count = assign.count
|
|
|
|
|
|
|
|
|
|
|
|
result[i] = AssignPortResult{
|
|
|
|
|
|
Proxy: proxy,
|
|
|
|
|
|
}
|
2025-03-28 10:03:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 筛选可用端口
|
2025-03-28 18:15:03 +08:00
|
|
|
|
var channels = result[i].Channels
|
|
|
|
|
|
var configs = make([]remote.PortConfigsReq, 0, count)
|
|
|
|
|
|
for port := 10000; port < 20000 && len(configs) < count; port++ {
|
2025-03-28 10:03:29 +08:00
|
|
|
|
// 跳过存在的端口
|
|
|
|
|
|
key := uint64(proxy.ID)<<32 | uint64(port)
|
|
|
|
|
|
_, ok := proxyPorts[key]
|
|
|
|
|
|
if ok {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 配置新端口
|
2025-03-28 18:15:03 +08:00
|
|
|
|
var i = len(configs)
|
|
|
|
|
|
configs = append(configs, remote.PortConfigsReq{
|
|
|
|
|
|
Port: port,
|
2025-03-28 10:03:29 +08:00
|
|
|
|
Edge: nil,
|
|
|
|
|
|
Status: true,
|
|
|
|
|
|
AutoEdgeConfig: remote.AutoEdgeConfig{
|
|
|
|
|
|
Province: filter.Prov,
|
|
|
|
|
|
City: filter.City,
|
|
|
|
|
|
Isp: filter.Isp,
|
|
|
|
|
|
Count: 1,
|
|
|
|
|
|
},
|
2025-03-28 18:15:03 +08:00
|
|
|
|
})
|
2025-03-28 10:03:29 +08:00
|
|
|
|
switch authType {
|
|
|
|
|
|
case ChannelAuthTypeIp:
|
|
|
|
|
|
var whitelist []string
|
|
|
|
|
|
err := q.Whitelist.
|
|
|
|
|
|
Where(q.Whitelist.UserID.Eq(userId)).
|
|
|
|
|
|
Select(q.Whitelist.Host).
|
|
|
|
|
|
Scan(&whitelist)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-03-28 18:15:03 +08:00
|
|
|
|
configs[i].Whitelist = whitelist
|
2025-03-28 10:03:29 +08:00
|
|
|
|
for _, item := range whitelist {
|
2025-03-28 18:15:03 +08:00
|
|
|
|
channels = append(channels, &models.Channel{
|
2025-03-28 10:03:29 +08:00
|
|
|
|
UserID: userId,
|
|
|
|
|
|
ProxyID: proxy.ID,
|
|
|
|
|
|
UserHost: item,
|
|
|
|
|
|
ProxyPort: int32(port),
|
|
|
|
|
|
AuthIP: true,
|
|
|
|
|
|
AuthPass: false,
|
|
|
|
|
|
Protocol: string(protocol),
|
|
|
|
|
|
Expiration: expiration,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
case ChannelAuthTypePass:
|
|
|
|
|
|
username, password := genPassPair()
|
2025-03-28 18:15:03 +08:00
|
|
|
|
configs[i].Userpass = fmt.Sprintf("%s:%s", username, password)
|
|
|
|
|
|
channels = append(channels, &models.Channel{
|
2025-03-28 10:03:29 +08:00
|
|
|
|
UserID: userId,
|
|
|
|
|
|
ProxyID: proxy.ID,
|
|
|
|
|
|
ProxyPort: int32(port),
|
|
|
|
|
|
AuthIP: false,
|
|
|
|
|
|
AuthPass: true,
|
|
|
|
|
|
Username: username,
|
|
|
|
|
|
Password: password,
|
|
|
|
|
|
Protocol: string(protocol),
|
|
|
|
|
|
Expiration: expiration,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-28 18:15:03 +08:00
|
|
|
|
result[i].Channels = channels
|
|
|
|
|
|
|
|
|
|
|
|
if len(configs) < count {
|
|
|
|
|
|
return nil, ChannelServiceErr("网关端口数量到达上限,无法分配")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-28 10:03:29 +08:00
|
|
|
|
// 提交端口配置
|
2025-03-28 18:15:03 +08:00
|
|
|
|
// gateway := remote.InitGateway(
|
|
|
|
|
|
// proxy.Host,
|
|
|
|
|
|
// "api",
|
|
|
|
|
|
// "123456",
|
|
|
|
|
|
// )
|
|
|
|
|
|
// err = gateway.GatewayPortConfigs(configs)
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return nil, err
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// 保存到数据库
|
|
|
|
|
|
err = q.Channel.
|
|
|
|
|
|
Omit(q.Channel.NodeID).
|
|
|
|
|
|
Save(channels...)
|
2025-03-28 10:03:29 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-28 18:15:03 +08:00
|
|
|
|
type AssignPortResult struct {
|
|
|
|
|
|
Proxy *models.Proxy
|
|
|
|
|
|
Channels []*models.Channel
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func chKey(channel *models.Channel) string {
|
|
|
|
|
|
return fmt.Sprintf("channel:%s:%s", channel.UserHost, channel.NodeHost)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func cache(ctx context.Context, assigns []AssignPortResult) error {
|
2025-03-28 10:03:29 +08:00
|
|
|
|
pipe := rds.Client.TxPipeline()
|
|
|
|
|
|
|
2025-03-28 18:15:03 +08:00
|
|
|
|
zList := make([]redis.Z, 0, len(assigns))
|
|
|
|
|
|
for _, assign := range assigns {
|
|
|
|
|
|
var channels = assign.Channels
|
|
|
|
|
|
for _, channel := range channels {
|
|
|
|
|
|
marshal, err := json.Marshal(assign)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pipe.Set(ctx, chKey(channel), string(marshal), channel.Expiration.Sub(time.Now()))
|
|
|
|
|
|
zList = append(zList, redis.Z{
|
|
|
|
|
|
Score: float64(channel.Expiration.Unix()),
|
|
|
|
|
|
Member: channel.ID,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-03-28 10:03:29 +08:00
|
|
|
|
}
|
2025-03-28 18:15:03 +08:00
|
|
|
|
pipe.ZAdd(ctx, "tasks:assign", zList...)
|
2025-03-28 10:03:29 +08:00
|
|
|
|
|
|
|
|
|
|
_, err := pipe.Exec(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func deleteCache(ctx context.Context, channels []*models.Channel) error {
|
|
|
|
|
|
pipe := rds.Client.TxPipeline()
|
2025-03-28 18:15:03 +08:00
|
|
|
|
keys := make([]string, 0, len(channels))
|
2025-03-28 10:03:29 +08:00
|
|
|
|
for i := range keys {
|
|
|
|
|
|
keys[i] = chKey(channels[i])
|
|
|
|
|
|
}
|
|
|
|
|
|
pipe.Del(ctx, keys...)
|
|
|
|
|
|
// 忽略异步任务,zrem 效率较低,在使用时再删除
|
|
|
|
|
|
_, err := pipe.Exec(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type ResourceInfo struct {
|
2025-03-28 18:15:03 +08:00
|
|
|
|
Id int32
|
|
|
|
|
|
UserId int32
|
|
|
|
|
|
Active bool
|
|
|
|
|
|
Type int32
|
|
|
|
|
|
Live int32
|
|
|
|
|
|
DailyLimit int32
|
|
|
|
|
|
DailyUsed int32
|
|
|
|
|
|
DailyLast time.Time
|
|
|
|
|
|
Quota int32
|
|
|
|
|
|
Used int32
|
|
|
|
|
|
Expire time.Time
|
2025-03-28 10:03:29 +08:00
|
|
|
|
}
|