新增提取函数,实现通过套餐编号提取

This commit is contained in:
2026-05-23 13:50:52 +08:00
parent b436a6cade
commit 32e56b1a0f
4 changed files with 97 additions and 18 deletions

View File

@@ -102,13 +102,17 @@ func CreateChannel(c *fiber.Ctx) error {
}
// 创建通道
no, err := s.FindResourceNoById(req.ResourceId)
if err != nil {
return err
}
var isp *m.EdgeISP
if req.Isp != nil {
isp = u.X(m.ToEdgeISP(*req.Isp))
}
result, err := s.Channel.CreateChannels(
ip,
req.ResourceId,
ip, no,
req.AuthType == s.ChannelAuthTypeIp,
req.AuthType == s.ChannelAuthTypePass,
req.Count,
@@ -149,6 +153,69 @@ type CreateChannelReq struct {
Isp *int `json:"isp"`
}
// CreateChannelV2 创建新通道 v2使用 resource_no 替代 resource_id
func CreateChannelV2(c *fiber.Ctx) error {
// 不检查权限,允许 api 调用
// 解析参数
req := new(CreateChannelReqV2)
if err := g.Validator.ParseBody(c, req); err != nil {
return core.NewBizErr("解析参数失败", err)
}
ip, err := netip.ParseAddr(c.IP())
if err != nil {
return core.NewBizErr("获取客户端地址失败", err)
}
// 创建通道
var isp *m.EdgeISP
if req.Isp != nil {
isp = u.X(m.ToEdgeISP(*req.Isp))
}
result, err := s.Channel.CreateChannels(
ip,
req.ResourceNo,
req.AuthType == s.ChannelAuthTypeIp,
req.AuthType == s.ChannelAuthTypePass,
req.Count,
&s.EdgeFilter{
Isp: isp,
Prov: req.Prov,
City: req.City,
},
)
if err != nil {
return err
}
// 返回结果
var resp = make([]*CreateChannelRespItem, len(result))
for i, channel := range result {
resp[i] = &CreateChannelRespItem{
Proto: req.Protocol,
Host: channel.Host,
IP: channel.Proxy.IP.String(),
Port: channel.Port,
}
if req.AuthType == s.ChannelAuthTypePass {
resp[i].Username = channel.Username
resp[i].Password = channel.Password
}
}
return c.JSON(resp)
}
type CreateChannelReqV2 struct {
ResourceNo string `json:"resource_no" validate:"required"`
AuthType s.ChannelAuthType `json:"auth_type" validate:"required"`
Protocol int `json:"protocol" validate:"required"`
Count int `json:"count" validate:"required"`
Prov *string `json:"prov"`
City *string `json:"city"`
Isp *int `json:"isp"`
}
type CreateChannelRespItem struct {
Proto int `json:"-"`
Host string `json:"host"`

View File

@@ -95,6 +95,7 @@ func userRouter(api fiber.Router) {
channel := api.Group("/channel")
channel.Post("/list", handlers.ListChannel)
channel.Post("/create", handlers.CreateChannel)
channel.Post("/create/v2", handlers.CreateChannelV2)
// 交易
trade := api.Group("/trade")

View File

@@ -25,7 +25,7 @@ var Channel = &channelServer{
}
type ChannelServiceProvider interface {
CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter *EdgeFilter) ([]*m.Channel, error)
CreateChannels(source netip.Addr, resourceNo string, authWhitelist bool, authPassword bool, count int, edgeFilter *EdgeFilter) ([]*m.Channel, error)
RemoveChannels(batch string) error
ClearExpiredChannels(proxyId int32) (int, error)
}
@@ -34,8 +34,8 @@ type channelServer struct {
provider ChannelServiceProvider
}
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) CreateChannels(source netip.Addr, resourceNo string, authWhitelist bool, authPassword bool, count int, edgeFilter *EdgeFilter) ([]*m.Channel, error) {
return s.provider.CreateChannels(source, resourceNo, authWhitelist, authPassword, count, edgeFilter)
}
func (s *channelServer) RemoveChannels(batch string) error {
@@ -115,12 +115,23 @@ func genPassPair() (string, string) {
return string(username), string(password)
}
func FindResourceNoById(resourceId int32) (string, error) {
resource, err := q.Resource.
Select(q.Resource.ResourceNo).
Where(q.Resource.ID.Eq(resourceId)).
Take()
if err != nil {
return "", ErrResourceNotExist
}
return u.Z(resource.ResourceNo), nil
}
// 查找资源
func findResource(resourceId int32, now time.Time) (*ResourceView, error) {
func findResourceViewByNo(resourceNo string, now time.Time) (*ResourceView, error) {
resource, err := q.Resource.
Preload(field.Associations).
Where(
q.Resource.ID.Eq(resourceId),
q.Resource.ResourceNo.Eq(resourceNo),
q.Resource.Active.Is(true),
).
Take()
@@ -131,7 +142,7 @@ func findResource(resourceId int32, now time.Time) (*ResourceView, error) {
return nil, ErrResourceNotExist
}
var info = &ResourceView{
Id: resource.ID,
ID: resource.ID,
User: *resource.User,
Active: resource.Active,
Type: resource.Type,
@@ -177,7 +188,7 @@ func findResource(resourceId int32, now time.Time) (*ResourceView, error) {
// ResourceView 套餐数据的简化视图,便于直接获取主要数据
type ResourceView struct {
Id int32
ID int32
User m.User
Active bool
Type m.ResourceType
@@ -195,13 +206,13 @@ type ResourceView struct {
}
// 检查用户是否可提取
func ensure(now time.Time, source netip.Addr, resourceId int32, authWhitelist bool, count int) (*ResourceView, []string, error) {
func ensure(now time.Time, source netip.Addr, resourceNo string, authWhitelist bool, count int) (*ResourceView, []string, error) {
if count > 400 {
return nil, nil, core.NewBizErr("单次最多提取 400 个")
}
// 获取用户套餐
resource, err := findResource(resourceId, now)
resource, err := findResourceViewByNo(resourceNo, now)
if err != nil {
return nil, nil, err
}

View File

@@ -24,7 +24,7 @@ import (
type channelBaiyinProvider struct{}
func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, filter *EdgeFilter) ([]*m.Channel, error) {
func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceNo string, authWhitelist bool, authPassword bool, count int, filter *EdgeFilter) ([]*m.Channel, error) {
if filter == nil {
return nil, core.NewBizErr("缺少节点过滤条件")
}
@@ -34,9 +34,9 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
channels := make([]*m.Channel, count)
// 资源锁,防止并发扣减失败导致的端口悬空问题
err := g.Redsync.WithLock(lockChannelCreateKey(resourceId), func() error {
err := g.Redsync.WithLock(lockChannelCreateKey(resourceNo), func() error {
// 检查并获取套餐与白名单
resource, whitelists, err := ensure(now, source, resourceId, authWhitelist, count)
resource, whitelists, err := ensure(now, source, resourceNo, authWhitelist, count)
if err != nil {
return err
}
@@ -65,7 +65,7 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
// 通道数据
channels[i] = &m.Channel{
UserID: user.ID,
ResourceID: resourceId,
ResourceID: resource.ID,
BatchNo: batchNo,
ProxyID: proxy.ID,
Host: u.Else(proxy.Host, proxy.IP.String()),
@@ -181,7 +181,7 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
// 保存提取记录
err = q.LogsUserUsage.Create(&m.LogsUserUsage{
UserID: user.ID,
ResourceID: resourceId,
ResourceID: resource.ID,
BatchNo: batchNo,
Count: int32(count),
ISP: u.X(filter.Isp.String()),
@@ -335,8 +335,8 @@ func (s *channelBaiyinProvider) ClearExpiredChannels(proxyId int32) (int, error)
return len(batchSet), nil
}
func lockChannelCreateKey(resourceId int32) string {
return fmt.Sprintf("platform:channel:create:%d", resourceId)
func lockChannelCreateKey(resourceNo string) string {
return fmt.Sprintf("platform:channel:create:%s", resourceNo)
}
func lockChannelRemoveKey(bid string) string {