实现 gost 网关
This commit is contained in:
@@ -4,24 +4,28 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math/rand/v2"
|
||||
"net/netip"
|
||||
"platform/pkg/env"
|
||||
"platform/pkg/u"
|
||||
"platform/web/core"
|
||||
e "platform/web/events"
|
||||
g "platform/web/globals"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gen/field"
|
||||
)
|
||||
|
||||
// 通道服务
|
||||
var Channel = &channelServer{
|
||||
provider: &channelBaiyinProvider{},
|
||||
provider: &channelGostProvider{},
|
||||
}
|
||||
|
||||
type ChannelServiceProvider interface {
|
||||
@@ -46,13 +50,77 @@ func (s *channelServer) ClearExpiredChannels(proxyId int32) (int, error) {
|
||||
return s.provider.ClearExpiredChannels(proxyId)
|
||||
}
|
||||
|
||||
func lockChannelCreateKey(resourceNo string) string {
|
||||
return fmt.Sprintf("platform:channel:create:%s", resourceNo)
|
||||
}
|
||||
|
||||
func lockChannelRemoveKey(bid string) string {
|
||||
return fmt.Sprintf("platform:batch:remove_expired:%s", bid)
|
||||
}
|
||||
|
||||
func selectPorts(proxyId int32, batchNo string, count int, expire time.Time) ([]netip.AddrPort, error) {
|
||||
chans, err := lockChans(proxyId, batchNo, count)
|
||||
if err != nil {
|
||||
return nil, core.NewBizErr("无可用通道,请稍后再试", err)
|
||||
}
|
||||
|
||||
_, err = g.Asynq.Enqueue(
|
||||
e.NewRemoveChannel(batchNo),
|
||||
asynq.ProcessAt(expire),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, core.NewServErr("注册异步关闭通道任务失败", err)
|
||||
}
|
||||
|
||||
return chans, nil
|
||||
}
|
||||
|
||||
func selectProxyByType(proxyType m.ProxyType, count int) (*m.Proxy, error) {
|
||||
proxies, err := q.Proxy.Where(
|
||||
q.Proxy.Type.Eq(int(proxyType)),
|
||||
q.Proxy.Status.Eq(int(m.ProxyStatusOnline)),
|
||||
).Find()
|
||||
if err != nil {
|
||||
return nil, core.NewBizErr("获取可用代理失败", err)
|
||||
}
|
||||
if len(proxies) == 0 {
|
||||
return nil, core.NewBizErr("无可用代理")
|
||||
}
|
||||
|
||||
proxyIDs := make([]int32, 0, len(proxies))
|
||||
proxyMap := make(map[int32]*m.Proxy, len(proxies))
|
||||
for _, item := range proxies {
|
||||
proxyIDs = append(proxyIDs, item.ID)
|
||||
proxyMap[item.ID] = item
|
||||
}
|
||||
|
||||
maxID := int32(0)
|
||||
maxCount := -1
|
||||
for _, id := range proxyIDs {
|
||||
idCount, err := g.Redis.SCard(context.Background(), freeChansKey(id)).Result()
|
||||
if err != nil {
|
||||
return nil, core.NewServErr("查询可用通道数量失败", err)
|
||||
}
|
||||
if idCount > int64(maxCount) {
|
||||
maxCount = int(idCount)
|
||||
maxID = id
|
||||
}
|
||||
}
|
||||
if maxCount < count {
|
||||
return nil, core.NewBizErr("无可用代理")
|
||||
}
|
||||
|
||||
return proxyMap[maxID], nil
|
||||
}
|
||||
|
||||
func (s *channelServer) RefreshEdges() error {
|
||||
if env.RunMode != env.RunModeProd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 找到所有网关
|
||||
// 仅白银网关支持边缘节点刷新,GOST 不参与此流程。
|
||||
proxies, err := q.Proxy.Where(
|
||||
q.Proxy.Type.Eq(int(m.ProxyTypeBaiYin)),
|
||||
q.Proxy.Status.Eq(int(m.ProxyStatusOnline)),
|
||||
).Find()
|
||||
if err != nil {
|
||||
@@ -282,6 +350,83 @@ func usedChansKey(proxy int32, batch string) string {
|
||||
return "channel:used:" + strconv.Itoa(int(proxy)) + ":" + batch
|
||||
}
|
||||
|
||||
type usedChanBatch struct {
|
||||
ProxyID int32
|
||||
Chans []netip.AddrPort
|
||||
}
|
||||
|
||||
func findUsedChanBatch(batch string) (*usedChanBatch, error) {
|
||||
keys, err := g.Redis.Keys(context.Background(), "channel:used:*:"+batch).Result()
|
||||
if err != nil {
|
||||
return nil, core.NewServErr("查询使用中通道失败", err)
|
||||
}
|
||||
|
||||
key, ok, err := selectUsedChanBatchKey(batch, keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
chans, err := g.Redis.LRange(context.Background(), key, 0, -1).Result()
|
||||
if err != nil {
|
||||
return nil, core.NewServErr("查询使用中通道失败", err)
|
||||
}
|
||||
|
||||
return parseUsedChanBatch(key, chans)
|
||||
}
|
||||
|
||||
func selectUsedChanBatchKey(batch string, keys []string) (string, bool, error) {
|
||||
switch len(keys) {
|
||||
case 0:
|
||||
return "", false, nil
|
||||
case 1:
|
||||
return keys[0], true, nil
|
||||
default:
|
||||
slog.Error("batchNo 全局唯一约束被破坏", "batch", batch, "keys", keys)
|
||||
return "", false, core.NewServErr(
|
||||
fmt.Sprintf("检测到重复 usedChans 键,batchNo 全局唯一被破坏: %s", batch),
|
||||
fmt.Errorf("keys=%s", strings.Join(keys, ",")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func parseUsedChanBatch(key string, chans []string) (*usedChanBatch, error) {
|
||||
proxyID, err := parseUsedChansKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addrs := make([]netip.AddrPort, len(chans))
|
||||
for i, ch := range chans {
|
||||
addr, err := netip.ParseAddrPort(ch)
|
||||
if err != nil {
|
||||
return nil, core.NewServErr(fmt.Sprintf("解析通道数据失败: %s", ch), err)
|
||||
}
|
||||
addrs[i] = addr
|
||||
}
|
||||
|
||||
return &usedChanBatch{
|
||||
ProxyID: proxyID,
|
||||
Chans: addrs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseUsedChansKey(key string) (int32, error) {
|
||||
parts := strings.Split(key, ":")
|
||||
if len(parts) != 4 {
|
||||
return 0, core.NewServErr(fmt.Sprintf("使用中通道键格式错误: %s", key), nil)
|
||||
}
|
||||
|
||||
proxyID, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return 0, core.NewServErr(fmt.Sprintf("使用中通道键格式错误: %s", key), err)
|
||||
}
|
||||
|
||||
return int32(proxyID), nil
|
||||
}
|
||||
|
||||
// 扩容通道
|
||||
func regChans(proxy int32, chans []netip.AddrPort) error {
|
||||
strs := make([]any, len(chans))
|
||||
|
||||
Reference in New Issue
Block a user