Files
platform/web/services/channel_baiyin.go

339 lines
8.5 KiB
Go
Raw Normal View History

package services
import (
"encoding/json"
"fmt"
"log/slog"
"net/netip"
"platform/pkg/env"
"platform/pkg/u"
"platform/web/core"
e "platform/web/events"
g "platform/web/globals"
"platform/web/globals/orm"
m "platform/web/models"
q "platform/web/queries"
"strings"
"time"
"github.com/hibiken/asynq"
"gorm.io/gen"
"gorm.io/gen/field"
)
2025-12-18 14:22:56 +08:00
type channelBaiyinProvider struct{}
2026-04-17 16:27:29 +08:00
func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, filter *EdgeFilter) ([]*m.Channel, error) {
if filter == nil {
return nil, core.NewBizErr("缺少节点过滤条件")
}
now := time.Now()
batch := ID.GenReadable("bat")
// 检查并获取套餐与白名单
resource, whitelists, err := ensure(now, source, resourceId, count)
if err != nil {
return nil, err
}
user := resource.User
expire := now.Add(resource.Live)
// 选择代理
proxyResult := struct {
m.Proxy
Count int
}{}
err = q.Proxy.
LeftJoin(q.Channel, q.Channel.ProxyID.EqCol(q.Proxy.ID), q.Channel.ExpiredAt.Gt(now)).
Select(q.Proxy.ALL, field.NewUnsafeFieldRaw("10000 - count(*)").As("count")).
Where(
q.Proxy.Type.Eq(int(m.ProxyTypeBaiYin)),
q.Proxy.Status.Eq(int(m.ProxyStatusOnline)),
).
Group(q.Proxy.ID).
Order(field.NewField("", "count")).
Limit(1).Scan(&proxyResult)
if err != nil {
return nil, core.NewBizErr("获取可用代理失败", err)
}
if proxyResult.Count < count {
return nil, core.NewBizErr("无可用主机,请稍后再试")
}
proxy := proxyResult.Proxy
2026-04-18 11:15:29 +08:00
// 锁内确认状态并锁定端口,避免与状态切换并发穿透
var chans []netip.AddrPort
err = g.Redsync.WithLock(proxyStatusLockKey(proxy.ID), func() error {
lockedProxy, err := q.Proxy.Where(q.Proxy.ID.Eq(proxy.ID)).Take()
if err != nil {
return err
}
if lockedProxy.Status != m.ProxyStatusOnline {
return core.NewBizErr("无可用主机,请稍后再试")
}
chans, err = lockChans(proxy.ID, batch, count)
if err != nil {
return core.NewBizErr("无可用通道,请稍后再试", err)
}
proxy = *lockedProxy
return nil
})
if err != nil {
2026-04-18 11:15:29 +08:00
return nil, err
}
// 获取可用节点
edgesResp, err := g.Cloud.CloudEdges(&g.CloudEdgesReq{
Province: filter.Prov,
City: filter.City,
Isp: u.X(filter.Isp.String()),
Limit: &count,
NoRepeat: u.P(true),
NoDayRepeat: u.P(true),
ActiveTime: u.P(3600),
IpUnchangedTime: u.P(3600),
Sort: u.P("ip_unchanged_time_asc"),
})
if err != nil {
return nil, core.NewBizErr("获取可用节点失败", err)
}
if edgesResp.Total != count && len(edgesResp.Edges) != count {
2026-04-17 16:27:29 +08:00
return nil, core.NewBizErr("地区可用节点数量不足")
}
edges := edgesResp.Edges
// 准备通道数据
channels := make([]*m.Channel, count)
chanConfigs := make([]*g.PortConfigsReq, count)
edgeConfigs := make([]string, count)
for i := range count {
ch := chans[i]
edge := edges[i]
// 通道数据
channels[i] = &m.Channel{
UserID: user.ID,
ResourceID: resourceId,
BatchNo: batch,
ProxyID: proxy.ID,
Host: u.Else(proxy.Host, proxy.IP.String()),
Port: ch.Port(),
EdgeRef: u.P(edge.EdgeID),
FilterISP: filter.Isp,
FilterProv: filter.Prov,
FilterCity: filter.City,
ExpiredAt: expire,
}
// 通道配置数据
chanConfigs[i] = &g.PortConfigsReq{
Port: int(ch.Port()),
Status: true,
Edge: &[]string{edge.EdgeID},
}
// 白名单模式
if authWhitelist {
channels[i].Whitelists = u.P(strings.Join(whitelists, ","))
chanConfigs[i].Whitelist = &whitelists
}
// 密码模式
if authPassword {
username, password := genPassPair()
channels[i].Username = &username
channels[i].Password = &password
chanConfigs[i].Userpass = u.P(username + ":" + password)
}
// 连接配置数据
edgeConfigs[i] = edge.EdgeID
}
// 提交异步任务关闭通道
_, err = g.Asynq.Enqueue(
e.NewRemoveChannel(batch),
asynq.ProcessAt(expire),
)
if err != nil {
return nil, core.NewServErr("提交关闭通道任务失败", err)
}
// 保存数据
err = q.Q.Transaction(func(q *q.Query) error {
var rs gen.ResultInfo
// 根据套餐类型和模式更新使用记录
isShortType := resource.Type == m.ResourceTypeShort
isLongType := resource.Type == m.ResourceTypeLong
switch {
case isShortType:
rs, err = q.ResourceShort.
Where(
q.ResourceShort.ID.Eq(*resource.ShortId),
q.ResourceShort.Used.Eq(resource.Used),
q.ResourceShort.Daily.Eq(resource.Daily),
).
UpdateSimple(
q.ResourceShort.Used.Add(int32(count)),
q.ResourceShort.Daily.Value(int32(resource.Today+count)),
q.ResourceShort.LastAt.Value(now),
)
case isLongType:
2026-03-18 18:09:32 +08:00
rs, err = q.ResourceLong.
Where(
q.ResourceLong.ID.Eq(*resource.LongId),
q.ResourceLong.Used.Eq(resource.Used),
q.ResourceLong.Daily.Eq(resource.Daily),
).
UpdateSimple(
q.ResourceLong.Used.Add(int32(count)),
q.ResourceLong.Daily.Value(int32(resource.Today+count)),
q.ResourceLong.LastAt.Value(now),
)
default:
return core.NewServErr("套餐类型不正确,无法更新", nil)
}
if err != nil {
return core.NewServErr("更新套餐使用记录失败", err)
}
if rs.RowsAffected == 0 {
return core.NewServErr("套餐使用记录不存在")
}
// 保存通道
err = q.Channel.
Omit(field.AssociationFields).
Create(channels...)
if err != nil {
return core.NewServErr("保存通道失败", err)
}
// 保存提取记录
err = q.LogsUserUsage.Create(&m.LogsUserUsage{
UserID: user.ID,
ResourceID: resourceId,
BatchNo: batch,
Count: int32(count),
ISP: u.P(filter.Isp.String()),
Prov: filter.Prov,
City: filter.City,
IP: orm.Inet{Addr: source},
Time: now,
})
if err != nil {
return core.NewServErr("保存用户使用记录失败", err)
}
return nil
})
if err != nil {
return nil, err
}
// 提交配置
secret := strings.Split(u.Z(proxy.Secret), ":")
gateway := g.NewGateway(proxy.IP.String(), secret[0], secret[1])
2026-04-13 11:00:46 +08:00
if env.RunMode == env.RunModeProd {
// 连接节点到网关
err = g.Cloud.CloudConnect(&g.CloudConnectReq{
Uuid: proxy.Mac,
Edge: &edgeConfigs,
})
if err != nil {
return nil, core.NewServErr("连接云平台失败", err)
}
// 启用网关代理通道
err = gateway.GatewayPortConfigs(chanConfigs)
if err != nil {
return nil, core.NewServErr(fmt.Sprintf("配置代理 %s 端口失败", proxy.IP.String()), err)
}
} else {
slog.Debug("提交代理端口配置", "proxy", proxy.IP.String())
for _, item := range chanConfigs {
str, _ := json.Marshal(item)
fmt.Println(string(str))
}
}
return channels, nil
}
2025-12-18 14:22:56 +08:00
func (s *channelBaiyinProvider) RemoveChannels(batch string) error {
start := time.Now()
// 获取连接数据
channels, err := q.Channel.Where(q.Channel.BatchNo.Eq(batch)).Find()
if err != nil {
return core.NewServErr(fmt.Sprintf("获取通道数据失败batch%s", batch), err)
}
if len(channels) == 0 {
slog.Warn(fmt.Sprintf("未找到通道数据batch%s", batch))
return nil
}
proxy, err := q.Proxy.Where(q.Proxy.ID.Eq(channels[0].ProxyID)).Take()
if err != nil {
return core.NewServErr(fmt.Sprintf("获取代理数据失败batch%s", batch), err)
}
// 准备配置数据
edgeConfigs := make([]string, len(channels))
configs := make([]*g.PortConfigsReq, len(channels))
for i, channel := range channels {
if channel.EdgeRef != nil {
edgeConfigs[i] = *channel.EdgeRef
} else {
slog.Warn(fmt.Sprintf("通道 %d 没有保存节点引用", channel.ID))
}
configs[i] = &g.PortConfigsReq{
Status: false,
Port: int(channel.Port),
Edge: &[]string{},
}
}
// 提交配置
2026-04-13 11:00:46 +08:00
if env.RunMode == env.RunModeProd {
// 断开节点连接
g.Cloud.CloudDisconnect(&g.CloudDisconnectReq{
Uuid: proxy.Mac,
Edge: &edgeConfigs,
})
// 清空通道配置
2026-04-17 16:27:29 +08:00
secret := strings.Split(u.Z(proxy.Secret), ":")
gateway := g.NewGateway(proxy.IP.String(), secret[0], secret[1])
err := gateway.GatewayPortConfigs(configs)
if err != nil {
return core.NewServErr(fmt.Sprintf("清空代理 %s 端口配置失败", proxy.IP.String()), err)
}
} else {
slog.Debug("清除代理端口配置", "proxy", proxy.IP)
for _, item := range configs {
str, _ := json.Marshal(item)
fmt.Println(string(str))
}
}
// 释放端口
err = freeChans(proxy.ID, batch)
if err != nil {
return err
}
2026-04-17 16:27:29 +08:00
slog.Debug("清除代理端口配置", "duration", time.Since(start).String())
return nil
}