重构白银节点分配方式,使用手动接口精确配置节点

This commit is contained in:
2025-12-08 14:22:30 +08:00
parent 9e237be21e
commit 983dbb4564
25 changed files with 651 additions and 630 deletions

View File

@@ -2,25 +2,42 @@ package services
import (
"context"
"fmt"
"math/rand/v2"
"net/netip"
"platform/web/core"
g "platform/web/globals"
m "platform/web/models"
q "platform/web/queries"
"strconv"
"time"
"github.com/redis/go-redis/v9"
"gorm.io/gen/field"
)
var Channel ChannelService = &channelBaiyinService{}
// 通道服务
type ChannelService interface {
var Channel = &channelServer{
provider: &channelBaiyinService{},
}
type ChanProviderAdapter interface {
CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter ...EdgeFilter) ([]*m.Channel, error)
RemoveChannels(batch string) error
}
type channelServer struct {
provider ChanProviderAdapter
}
func (s *channelServer) CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter ...EdgeFilter) ([]*m.Channel, error) {
return s.provider.CreateChannels(source, resourceId, authWhitelist, authPassword, count, edgeFilter...)
}
func (s *channelServer) RemoveChannels(batch string) error {
return s.provider.RemoveChannels(batch)
}
// 授权方式
type ChannelAuthType int
@@ -60,12 +77,14 @@ func findResource(resourceId int32) (*ResourceView, error) {
if err != nil {
return nil, ErrResourceNotExist
}
if resource.User == nil {
return nil, ErrResourceNotExist
}
var info = &ResourceView{
Id: resource.ID,
Active: resource.Active,
Type: resource.Type,
User: resource.User,
User: *resource.User,
}
switch resource.Type {
@@ -135,34 +154,127 @@ type ResourceView struct {
User m.User
}
// 检查用户是否可提取
func ensure(now time.Time, source netip.Addr, resourceId int32, count int) (*ResourceView, []string, error) {
if count > 400 {
return nil, nil, core.NewBizErr("单次最多提取 400 个")
}
// 获取用户套餐
resource, err := findResource(resourceId)
if err != nil {
return nil, nil, err
}
// 检查用户
user := resource.User
if user.IDToken == nil || *user.IDToken == "" {
return nil, nil, core.NewBizErr("账号未实名")
}
// 获取用户白名单并检查用户 ip 地址
whitelists, err := q.Whitelist.Where(
q.Whitelist.UserID.Eq(user.ID),
).Find()
if err != nil {
return nil, nil, err
}
ips := make([]string, len(whitelists))
pass := false
for i, item := range whitelists {
ips[i] = item.IP.String()
if item.IP.Addr == source {
pass = true
}
}
if !pass {
return nil, nil, core.NewBizErr(fmt.Sprintf("IP 地址 %s 不在白名单内", source.String()))
}
// 检查套餐使用情况
switch resource.Mode {
default:
return nil, nil, core.NewBizErr("不支持的套餐模式")
// 包时
case m.ResourceModeTime:
// 检查过期时间
if resource.Expire.Before(now) {
return nil, 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, nil, ErrResourceDailyLimit
}
// 包量
case m.ResourceModeQuota:
// 检查可用配额
if int(resource.Quota)-int(resource.Used) < count {
return nil, nil, ErrResourceExhausted
}
}
return resource, ips, nil
}
var (
allChansKey = "channel:all"
freeChansKey = "channel:free"
usedChansKey = "channel:used"
)
// 扩容通道
func regChans(proxy int32, chans []netip.AddrPort) error {
strs := make([]any, len(chans))
for i, ch := range chans {
strs[i] = ch.String()
}
key := freeChansKey + ":" + strconv.Itoa(int(proxy))
err := g.Redis.SAdd(context.Background(), key, strs...).Err()
if err != nil {
return fmt.Errorf("扩容通道失败: %w", err)
}
return nil
}
// 缩容通道
func remChans(proxy int32) error {
key := freeChansKey + ":" + strconv.Itoa(int(proxy))
err := g.Redis.SRem(context.Background(), key).Err()
if err != nil {
return fmt.Errorf("缩容通道失败: %w", err)
}
return nil
}
// 取用通道
func lockChans(batch string, count int, expire time.Time) ([]netip.AddrPort, error) {
chans, err := g.Redis.Eval(
func lockChans(proxy int32, batch string, count int) ([]netip.AddrPort, error) {
pid := strconv.Itoa(int(proxy))
chans, err := RedisScriptLockChans.Run(
context.Background(),
RedisScriptLockChans,
g.Redis,
[]string{
freeChansKey,
usedChansKey,
usedChansKey + ":" + batch,
freeChansKey + ":" + pid,
usedChansKey + ":" + pid + ":" + batch,
},
count,
expire.Unix(),
).StringSlice()
if err != nil {
return nil, core.NewBizErr("获取通道失败", err)
return nil, fmt.Errorf("获取通道失败: %w", err)
}
addrs := make([]netip.AddrPort, len(chans))
for i, ch := range chans {
addr, err := netip.ParseAddrPort(ch)
if err != nil {
return nil, core.NewServErr("解析通道数据失败", err)
return nil, fmt.Errorf("解析通道数据失败: %w", err)
}
addrs[i] = addr
}
@@ -170,41 +282,31 @@ func lockChans(batch string, count int, expire time.Time) ([]netip.AddrPort, err
return addrs, nil
}
var RedisScriptLockChans = `
var RedisScriptLockChans = redis.NewScript(`
local free_key = KEYS[1]
local used_key = KEYS[2]
local batch_key = KEYS[3]
local batch_key = KEYS[2]
local count = tonumber(ARGV[1])
local expire = tonumber(ARGV[2])
if redis.call("SCARD", free_key) < count then
return nil
end
local ports = redis.call("SPOP", free_key, count)
redis.call("ZADD", used_key, expire, batch_key)
redis.call("RPUSH", batch_key, unpack(ports))
return ports
`
`)
// 归还通道
func freeChans(batch string, chans []string) error {
values := make([]any, len(chans))
for i, ch := range chans {
values[i] = ch
}
err := g.Redis.Eval(
func freeChans(proxy int32, batch string) error {
pid := strconv.Itoa(int(proxy))
err := RedisScriptFreeChans.Run(
context.Background(),
RedisScriptFreeChans,
g.Redis,
[]string{
freeChansKey,
usedChansKey,
usedChansKey + ":" + batch,
allChansKey,
freeChansKey + ":" + pid,
usedChansKey + ":" + pid + ":" + batch,
},
values...,
).Err()
if err != nil {
return core.NewBizErr("释放通道失败", err)
@@ -213,92 +315,19 @@ func freeChans(batch string, chans []string) error {
return nil
}
var RedisScriptFreeChans = `
var RedisScriptFreeChans = redis.NewScript(`
local free_key = KEYS[1]
local used_key = KEYS[2]
local batch_key = KEYS[3]
local all_key = KEYS[4]
local chans = ARGV
local batch_key = KEYS[2]
local count = 0
for i, chan in ipairs(chans) do
if redis.call("SISMEMBER", all_key, chan) == 1 then
redis.call("SADD", free_key, chan)
count = count + 1
end
end
redis.call("ZREM", used_key, batch_key)
local chans = redis.call("LRANGE", batch_key, 0, -1)
redis.call("DEL", batch_key)
return count
`
// 扩容通道
func addChans(chans []netip.AddrPort) error {
strs := make([]string, len(chans))
for i, ch := range chans {
strs[i] = ch.String()
}
err := g.Redis.Eval(
context.Background(),
RedisScriptAddChans,
[]string{
freeChansKey,
allChansKey,
},
strs,
).Err()
if err != nil {
return core.NewBizErr("扩容通道失败", err)
}
return nil
}
var RedisScriptAddChans = `
local free_key = KEYS[1]
local all_key = KEYS[2]
local chans = ARGV
local batch_size = 5000
for i = 1, #chans, batch_size do
local end_index = math.min(i + batch_size - 1, #chans)
redis.call("SADD", free_key, unpack(chans, i, end_index))
redis.call("SADD", all_key, unpack(chans, i, end_index))
if redis.call("EXISTS", free_key) == 1 then
redis.call("SADD", free_key, unpack(chans))
end
return 1
`
// 缩容通道
func removeChans(chans []string) error {
err := g.Redis.Eval(
context.Background(),
RedisScriptRemoveChans,
[]string{
freeChansKey,
allChansKey,
},
chans,
).Err()
if err != nil {
return core.NewBizErr("缩容通道失败", err)
}
return nil
}
var RedisScriptRemoveChans = `
local free_key = KEYS[1]
local all_key = KEYS[2]
local chans = ARGV
redis.call("SREM", free_key, unpack(chans))
redis.call("SREM", all_key, unpack(chans))
return 1
`
`)
// 错误信息
var (