实现 gost 网关

This commit is contained in:
2026-06-08 17:24:55 +08:00
parent b00782b3f6
commit c5453557ae
13 changed files with 972 additions and 123 deletions

View File

@@ -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))