优化与代理服务的密钥存储与传递方式;更新套餐,账单查询对长效套餐的支持,新增长效套餐分页查询接口
This commit is contained in:
@@ -1,6 +1,11 @@
|
|||||||
package globals
|
package globals
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base32"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -29,15 +34,19 @@ type ProxyPermitConfig struct {
|
|||||||
Expire time.Time `json:"expire"`
|
Expire time.Time `json:"expire"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProxyClient) Permit(proxy string, config []*ProxyPermitConfig) error {
|
func (p *ProxyClient) Permit(host string, secret string, config []*ProxyPermitConfig) error {
|
||||||
|
|
||||||
str, err := json.Marshal(config)
|
// 请求体加密
|
||||||
|
body, err := encrypt(config, secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("加密请求失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
body := strings.NewReader(string(str))
|
resp, err := http.Post(
|
||||||
resp, err := http.Post(fmt.Sprintf("%s:8848%s", proxy, PermitEndpoint), "application/json", body)
|
fmt.Sprintf("http://%s:8848%s", host, PermitEndpoint),
|
||||||
|
"application/json",
|
||||||
|
strings.NewReader(body),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -49,3 +58,61 @@ func (p *ProxyClient) Permit(proxy string, config []*ProxyPermitConfig) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func encrypt(req []*ProxyPermitConfig, secretStr string) (string, error) {
|
||||||
|
var encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||||
|
|
||||||
|
// 创建 AES 密钥
|
||||||
|
secret, err := encoding.DecodeString(secretStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("解码 AES 密钥字符串失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(secret)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("创建 AES 密钥失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gcm, err := cipher.NewGCM(block)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("创建 AES GCM 失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密内容
|
||||||
|
bytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("配置参数序列化失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nonceBytes := make([]byte, gcm.NonceSize())
|
||||||
|
if _, err := rand.Read(nonceBytes); err != nil {
|
||||||
|
return "", fmt.Errorf("生成随机数失败: %w", err)
|
||||||
|
}
|
||||||
|
nonce := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(nonceBytes)
|
||||||
|
|
||||||
|
timestamp := time.Now().UnixMilli()
|
||||||
|
aad := fmt.Sprintf("%s:%d", nonce, timestamp)
|
||||||
|
|
||||||
|
ciphertext := gcm.Seal(nil, nonceBytes, bytes, []byte(aad))
|
||||||
|
encoded := base64.StdEncoding.EncodeToString(ciphertext)
|
||||||
|
|
||||||
|
// 生成请求体
|
||||||
|
encrypted := EncryptReq{
|
||||||
|
Content: encoded,
|
||||||
|
Nonce: nonce,
|
||||||
|
Timestamp: timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(encrypted)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("请求参数序列化失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(body), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type EncryptReq struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
Nonce string `json:"nonce"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func ListBill(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
bills, err := q.Bill.Where(do).
|
bills, err := q.Bill.Where(do).
|
||||||
Preload(q.Bill.Resource, q.Bill.Trade, q.Bill.Refund).
|
Preload(q.Bill.Resource, q.Bill.Trade, q.Bill.Refund).
|
||||||
Preload(q.Bill.Resource.Short).
|
Preload(q.Bill.Resource.Short, q.Bill.Resource.Long).
|
||||||
Order(q.Bill.CreatedAt.Desc()).
|
Order(q.Bill.CreatedAt.Desc()).
|
||||||
Offset(req.GetOffset()).
|
Offset(req.GetOffset()).
|
||||||
Limit(req.GetLimit()).
|
Limit(req.GetLimit()).
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"encoding/base32"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
auth2 "platform/web/auth"
|
auth2 "platform/web/auth"
|
||||||
@@ -44,7 +45,16 @@ func OnlineProxy(c *fiber.Ctx) (err error) {
|
|||||||
|
|
||||||
// 创建代理
|
// 创建代理
|
||||||
var ip = c.Context().RemoteIP()
|
var ip = c.Context().RemoteIP()
|
||||||
var secret = rand.Text()
|
|
||||||
|
var secretBytes = make([]byte, 16)
|
||||||
|
if _, err := rand.Read(secretBytes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var secret = base32.StdEncoding.
|
||||||
|
WithPadding(base32.NoPadding).
|
||||||
|
EncodeToString(secretBytes)
|
||||||
|
|
||||||
|
slog.Debug("生成随机密钥", "ip", ip, "secret", secret)
|
||||||
var proxy = &m.Proxy{
|
var proxy = &m.Proxy{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Version: int32(req.Version),
|
Version: int32(req.Version),
|
||||||
@@ -53,7 +63,7 @@ func OnlineProxy(c *fiber.Ctx) (err error) {
|
|||||||
Secret: secret,
|
Secret: secret,
|
||||||
Status: 1,
|
Status: 1,
|
||||||
}
|
}
|
||||||
err = q.Proxy.
|
err = q.Proxy.Debug().
|
||||||
Clauses(clause.OnConflict{
|
Clauses(clause.OnConflict{
|
||||||
UpdateAll: true,
|
UpdateAll: true,
|
||||||
Columns: []clause.Column{
|
Columns: []clause.Column{
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// region ListResourceShort
|
// region 查询套餐
|
||||||
|
|
||||||
type ListResourceShortReq struct {
|
type ListResourceShortReq struct {
|
||||||
core.PageReq
|
core.PageReq
|
||||||
@@ -28,7 +28,6 @@ type ListResourceShortReq struct {
|
|||||||
ExpireBefore *time.Time `json:"expire_before"`
|
ExpireBefore *time.Time `json:"expire_before"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListResourceShort 获取套餐列表
|
|
||||||
func ListResourceShort(c *fiber.Ctx) error {
|
func ListResourceShort(c *fiber.Ctx) error {
|
||||||
// 检查权限
|
// 检查权限
|
||||||
authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
|
authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
|
||||||
@@ -43,8 +42,10 @@ func ListResourceShort(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 查询套餐列表
|
// 查询套餐列表
|
||||||
do := q.Resource.
|
do := q.Resource.Where(
|
||||||
Where(q.Resource.UserID.Eq(authContext.Payload.Id))
|
q.Resource.UserID.Eq(authContext.Payload.Id),
|
||||||
|
q.Resource.Type.Eq(int32(resource2.TypeShort)),
|
||||||
|
)
|
||||||
if req.ResourceNo != nil && *req.ResourceNo != "" {
|
if req.ResourceNo != nil && *req.ResourceNo != "" {
|
||||||
do.Where(q.Resource.ResourceNo.Eq(*req.ResourceNo))
|
do.Where(q.Resource.ResourceNo.Eq(*req.ResourceNo))
|
||||||
}
|
}
|
||||||
@@ -67,7 +68,7 @@ func ListResourceShort(c *fiber.Ctx) error {
|
|||||||
do.Where(q.ResourceShort.As(q.Resource.Short.Name()).Expire.Lte(orm.LocalDateTime(*req.ExpireBefore)))
|
do.Where(q.ResourceShort.As(q.Resource.Short.Name()).Expire.Lte(orm.LocalDateTime(*req.ExpireBefore)))
|
||||||
}
|
}
|
||||||
|
|
||||||
resource, err := q.Resource.Where(do).
|
resource, err := q.Resource.Debug().Where(do).
|
||||||
Joins(q.Resource.Short).
|
Joins(q.Resource.Short).
|
||||||
Order(q.Resource.CreatedAt.Desc()).
|
Order(q.Resource.CreatedAt.Desc()).
|
||||||
Offset(req.GetOffset()).
|
Offset(req.GetOffset()).
|
||||||
@@ -97,42 +98,136 @@ func ListResourceShort(c *fiber.Ctx) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
type ListResourceLongReq struct {
|
||||||
|
core.PageReq
|
||||||
// region AllResource
|
ResourceNo *string `json:"resource_no"`
|
||||||
|
Active *bool `json:"active"`
|
||||||
type AllResourceReq struct {
|
Type *int `json:"type"`
|
||||||
|
CreateAfter *time.Time `json:"create_after"`
|
||||||
|
CreateBefore *time.Time `json:"create_before"`
|
||||||
|
ExpireAfter *time.Time `json:"expire_after"`
|
||||||
|
ExpireBefore *time.Time `json:"expire_before"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func AllResource(c *fiber.Ctx) error {
|
func ListResourceLong(c *fiber.Ctx) error {
|
||||||
// 检查权限
|
// 检查权限
|
||||||
authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
|
authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解析请求参数
|
||||||
|
req := new(ListResourceLongReq)
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// 查询套餐列表
|
// 查询套餐列表
|
||||||
short := q.ResourceShort.As(q.Resource.Short.Name())
|
do := q.Resource.Where(
|
||||||
do := q.Resource.
|
|
||||||
Joins(q.Resource.Short).
|
|
||||||
Where(
|
|
||||||
q.Resource.UserID.Eq(authContext.Payload.Id),
|
q.Resource.UserID.Eq(authContext.Payload.Id),
|
||||||
|
q.Resource.Type.Eq(int32(resource2.TypeLong)),
|
||||||
|
)
|
||||||
|
if req.ResourceNo != nil && *req.ResourceNo != "" {
|
||||||
|
do.Where(q.Resource.ResourceNo.Eq(*req.ResourceNo))
|
||||||
|
}
|
||||||
|
if req.Active != nil {
|
||||||
|
do.Where(q.Resource.Active.Is(*req.Active))
|
||||||
|
}
|
||||||
|
if req.Type != nil {
|
||||||
|
do.Where(q.ResourceLong.As(q.Resource.Long.Name()).Type.Eq(int32(*req.Type)))
|
||||||
|
}
|
||||||
|
if req.CreateAfter != nil {
|
||||||
|
do.Where(q.Resource.CreatedAt.Gte(orm.LocalDateTime(*req.CreateAfter)))
|
||||||
|
}
|
||||||
|
if req.CreateBefore != nil {
|
||||||
|
do.Where(q.Resource.CreatedAt.Lte(orm.LocalDateTime(*req.CreateBefore)))
|
||||||
|
}
|
||||||
|
if req.ExpireAfter != nil {
|
||||||
|
do.Where(q.ResourceLong.As(q.Resource.Long.Name()).Expire.Gte(orm.LocalDateTime(*req.ExpireAfter)))
|
||||||
|
}
|
||||||
|
if req.ExpireBefore != nil {
|
||||||
|
do.Where(q.ResourceLong.As(q.Resource.Long.Name()).Expire.Lte(orm.LocalDateTime(*req.ExpireBefore)))
|
||||||
|
}
|
||||||
|
|
||||||
|
resource, err := q.Resource.Debug().Where(do).
|
||||||
|
Joins(q.Resource.Long).
|
||||||
|
Order(q.Resource.CreatedAt.Desc()).
|
||||||
|
Offset(req.GetOffset()).
|
||||||
|
Limit(req.GetLimit()).
|
||||||
|
Find()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var total int64
|
||||||
|
if len(resource) < req.GetLimit() {
|
||||||
|
total = int64(len(resource) + req.GetOffset())
|
||||||
|
} else {
|
||||||
|
total, err = q.Resource.
|
||||||
|
Where(do).
|
||||||
|
Count()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(core.PageResp{
|
||||||
|
Total: int(total),
|
||||||
|
Page: req.GetPage(),
|
||||||
|
Size: req.GetSize(),
|
||||||
|
List: resource,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type AllResourceReq struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func AllActiveResource(c *fiber.Ctx) error {
|
||||||
|
// 检查权限
|
||||||
|
authCtx, err := auth.NewProtect(c).Payload(auth.PayloadUser).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询套餐列表
|
||||||
|
var now = time.Now()
|
||||||
|
|
||||||
|
var short = q.ResourceShort.As(q.Resource.Short.Name())
|
||||||
|
var long = q.ResourceLong.As(q.Resource.Long.Name())
|
||||||
|
resources, err := q.Resource.
|
||||||
|
Joins(
|
||||||
|
q.Resource.Short,
|
||||||
|
q.Resource.Long,
|
||||||
|
).
|
||||||
|
Where(
|
||||||
|
q.Resource.UserID.Eq(authCtx.Payload.Id),
|
||||||
q.Resource.Active.Is(true),
|
q.Resource.Active.Is(true),
|
||||||
q.Resource.Where(
|
q.Resource.Where(
|
||||||
|
q.Resource.Type.Eq(int32(resource2.TypeShort)),
|
||||||
|
q.ResourceShort.As(q.Resource.Short.Name()).Where(
|
||||||
short.Type.Eq(int32(resource2.ModeTime)),
|
short.Type.Eq(int32(resource2.ModeTime)),
|
||||||
short.Expire.Gte(orm.LocalDateTime(time.Now())),
|
short.Expire.Gte(orm.LocalDateTime(now)),
|
||||||
|
q.ResourceShort.As(q.Resource.Short.Name()).
|
||||||
|
Where(short.DailyLast.Lt(orm.LocalDateTime(u.Today()))).
|
||||||
|
Or(short.DailyLimit.GtCol(short.DailyUsed)),
|
||||||
).Or(
|
).Or(
|
||||||
short.Type.Eq(int32(resource2.ModeCount)),
|
short.Type.Eq(int32(resource2.ModeCount)),
|
||||||
short.Quota.GtCol(short.Used),
|
short.Quota.GtCol(short.Used),
|
||||||
),
|
),
|
||||||
q.Resource.Where(
|
|
||||||
short.DailyLast.Lt(orm.LocalDateTime(u.Today())),
|
|
||||||
).Or(
|
).Or(
|
||||||
short.DailyUsed.LtCol(short.DailyLimit),
|
q.Resource.Type.Eq(int32(resource2.TypeLong)),
|
||||||
|
q.ResourceLong.As(q.Resource.Long.Name()).Where(
|
||||||
|
long.Type.Eq(int32(resource2.ModeTime)),
|
||||||
|
long.Expire.Gte(orm.LocalDateTime(now)),
|
||||||
|
q.ResourceLong.As(q.Resource.Long.Name()).
|
||||||
|
Where(long.DailyLast.Lt(orm.LocalDateTime(u.Today()))).
|
||||||
|
Or(long.DailyLimit.GtCol(long.DailyUsed)),
|
||||||
|
).Or(
|
||||||
|
long.Type.Eq(int32(resource2.ModeCount)),
|
||||||
|
long.Quota.GtCol(long.Used),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
).
|
||||||
resources, err := do.Debug().
|
|
||||||
Order(q.Resource.CreatedAt.Desc()).
|
Order(q.Resource.CreatedAt.Desc()).
|
||||||
Find()
|
Find()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -144,7 +239,7 @@ func AllResource(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region CreateResource
|
// region 创建套餐
|
||||||
|
|
||||||
type CreateResourceReq struct {
|
type CreateResourceReq struct {
|
||||||
s.CreateResourceSerializer
|
s.CreateResourceSerializer
|
||||||
@@ -229,7 +324,7 @@ func CompleteCreateResource(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
// 完成创建套餐
|
// 完成创建套餐
|
||||||
var now = time.Now()
|
var now = time.Now()
|
||||||
err = s.Resource.CompleteResource(req.TradeNo, now, nil)
|
err = s.Resource.CompleteResource(req.TradeNo, now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ func ApplyRouters(app *fiber.App) {
|
|||||||
// 套餐
|
// 套餐
|
||||||
resource := api.Group("/resource")
|
resource := api.Group("/resource")
|
||||||
resource.Post("/list/short", handlers.ListResourceShort)
|
resource.Post("/list/short", handlers.ListResourceShort)
|
||||||
resource.Post("/all", handlers.AllResource)
|
resource.Post("/list/long", handlers.ListResourceLong)
|
||||||
|
resource.Post("/all", handlers.AllActiveResource)
|
||||||
resource.Post("/create", handlers.CreateResource)
|
resource.Post("/create", handlers.CreateResource)
|
||||||
resource.Post("/create/prepare", handlers.PrepareCreateResource)
|
resource.Post("/create/prepare", handlers.PrepareCreateResource)
|
||||||
resource.Post("/create/complete", handlers.CompleteCreateResource)
|
resource.Post("/create/complete", handlers.CompleteCreateResource)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"gorm.io/gen/field"
|
"gorm.io/gen/field"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"math"
|
"math"
|
||||||
@@ -52,7 +51,7 @@ func (s *channelService) RemoveChannels(ctx context.Context, authCtx *auth.Conte
|
|||||||
// 检查权限,如果为用户操作的话,则只能删除自己的通道
|
// 检查权限,如果为用户操作的话,则只能删除自己的通道
|
||||||
for _, channel := range channels {
|
for _, channel := range channels {
|
||||||
if authCtx.Payload.Type == auth.PayloadUser && authCtx.Payload.Id != channel.UserID {
|
if authCtx.Payload.Type == auth.PayloadUser && authCtx.Payload.Id != channel.UserID {
|
||||||
return fiber.NewError(fiber.StatusForbidden)
|
return ErrRemoveForbidden
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,13 +227,14 @@ func (s *channelService) CreateChannel(
|
|||||||
AuthIp: authType == ChannelAuthTypeIp,
|
AuthIp: authType == ChannelAuthTypeIp,
|
||||||
Whitelists: whitelist,
|
Whitelists: whitelist,
|
||||||
AuthPass: authType == ChannelAuthTypePass,
|
AuthPass: authType == ChannelAuthTypePass,
|
||||||
Expiration: now.Add(time.Duration(resource.Live) * time.Second),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch resource2.Type(resource.Type) {
|
switch resource.Type {
|
||||||
case resource2.TypeShort:
|
case resource2.TypeShort:
|
||||||
|
config.Expiration = now.Add(time.Duration(resource.Live) * time.Second)
|
||||||
channels, err = assignShortChannels(q, authCtx.Payload.Id, count, config, filter, now)
|
channels, err = assignShortChannels(q, authCtx.Payload.Id, count, config, filter, now)
|
||||||
case resource2.TypeLong:
|
case resource2.TypeLong:
|
||||||
|
config.Expiration = now.Add(time.Duration(resource.Live) * time.Hour)
|
||||||
channels, err = assignLongChannels(q, authCtx.Payload.Id, count, config, filter)
|
channels, err = assignLongChannels(q, authCtx.Payload.Id, count, config, filter)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -260,8 +260,8 @@ func findResource(q *q.Query, resourceId int32, userId int32, count int, now tim
|
|||||||
|
|
||||||
resource, err := q.Resource.
|
resource, err := q.Resource.
|
||||||
Preload(
|
Preload(
|
||||||
q.Resource.Short.On(q.Resource.Type.Eq(int32(resource2.TypeShort))),
|
q.Resource.Short,
|
||||||
q.Resource.Long.On(q.Resource.Type.Eq(int32(resource2.TypeLong))),
|
q.Resource.Long,
|
||||||
).
|
).
|
||||||
Where(
|
Where(
|
||||||
q.Resource.ID.Eq(resourceId),
|
q.Resource.ID.Eq(resourceId),
|
||||||
@@ -562,7 +562,20 @@ func assignLongChannels(q *q.Query, userId int32, count int, config ChannelCreat
|
|||||||
m.Edge
|
m.Edge
|
||||||
Count int
|
Count int
|
||||||
Host string
|
Host string
|
||||||
|
Secret string
|
||||||
}, 0)
|
}, 0)
|
||||||
|
|
||||||
|
do := q.Edge.Where(q.Edge.Status.Eq(1))
|
||||||
|
if filter.Prov != "" {
|
||||||
|
do = do.Where(q.Edge.Prov.Eq(filter.Prov))
|
||||||
|
}
|
||||||
|
if filter.City != "" {
|
||||||
|
do = do.Where(q.Edge.City.Eq(filter.City))
|
||||||
|
}
|
||||||
|
if filter.Isp != "" {
|
||||||
|
do = do.Where(q.Edge.Isp.Eq(int32(edge2.ISPFromStr(filter.Isp))))
|
||||||
|
}
|
||||||
|
|
||||||
err := q.Edge.
|
err := q.Edge.
|
||||||
LeftJoin(q.Channel, q.Channel.EdgeID.EqCol(q.Edge.ID)).
|
LeftJoin(q.Channel, q.Channel.EdgeID.EqCol(q.Edge.ID)).
|
||||||
LeftJoin(q.Proxy, q.Proxy.ID.EqCol(q.Edge.ProxyID)).
|
LeftJoin(q.Proxy, q.Proxy.ID.EqCol(q.Edge.ProxyID)).
|
||||||
@@ -570,20 +583,19 @@ func assignLongChannels(q *q.Query, userId int32, count int, config ChannelCreat
|
|||||||
q.Edge.ALL,
|
q.Edge.ALL,
|
||||||
q.Channel.ALL.Count().As("count"),
|
q.Channel.ALL.Count().As("count"),
|
||||||
q.Proxy.Host,
|
q.Proxy.Host,
|
||||||
|
q.Proxy.Secret,
|
||||||
).
|
).
|
||||||
Group(q.Edge.ID).
|
Group(q.Edge.ID, q.Proxy.Host, q.Proxy.Secret).
|
||||||
Where(
|
Where(do).
|
||||||
q.Edge.Prov.Eq(filter.Prov),
|
|
||||||
q.Edge.City.Eq(filter.City),
|
|
||||||
q.Edge.Isp.Eq(int32(edge2.ISPFromStr(filter.Isp))),
|
|
||||||
q.Edge.Status.Eq(1),
|
|
||||||
).
|
|
||||||
Order(field.NewField("", "count").Asc()).
|
Order(field.NewField("", "count").Asc()).
|
||||||
Scan(edges)
|
Limit(count).
|
||||||
|
Scan(&edges)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
fmt.Printf("edges: %v\n", edges)
|
if len(edges) == 0 {
|
||||||
|
return nil, ErrEdgesNoAvailable
|
||||||
|
}
|
||||||
|
|
||||||
// 计算分配负载(考虑去重,维护一个节点使用记录表,优先分配未使用节点,达到算法额定负载后再选择负载最少的节点)
|
// 计算分配负载(考虑去重,维护一个节点使用记录表,优先分配未使用节点,达到算法额定负载后再选择负载最少的节点)
|
||||||
var total = count
|
var total = count
|
||||||
@@ -593,8 +605,17 @@ func assignLongChannels(q *q.Query, userId int32, count int, config ChannelCreat
|
|||||||
var avg = int(math.Ceil(float64(total) / float64(len(edges))))
|
var avg = int(math.Ceil(float64(total) / float64(len(edges))))
|
||||||
|
|
||||||
var channels = make([]*m.Channel, 0, count)
|
var channels = make([]*m.Channel, 0, count)
|
||||||
var reqs = make(map[string][]*g.ProxyPermitConfig)
|
var proxies = make(map[int32]*m.Proxy)
|
||||||
|
var reqs = make(map[int32][]*g.ProxyPermitConfig)
|
||||||
for _, edge := range edges {
|
for _, edge := range edges {
|
||||||
|
if _, ok := proxies[edge.ProxyID]; !ok {
|
||||||
|
proxies[edge.ProxyID] = &m.Proxy{
|
||||||
|
ID: edge.ProxyID,
|
||||||
|
Host: edge.Host,
|
||||||
|
Secret: edge.Secret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prev := edge.Count
|
prev := edge.Count
|
||||||
next := int(math.Max(float64(prev), float64(int(math.Min(float64(avg), float64(total))))))
|
next := int(math.Max(float64(prev), float64(int(math.Min(float64(avg), float64(total))))))
|
||||||
total -= next
|
total -= next
|
||||||
@@ -613,6 +634,8 @@ func assignLongChannels(q *q.Query, userId int32, count int, config ChannelCreat
|
|||||||
AuthIP: config.AuthIp,
|
AuthIP: config.AuthIp,
|
||||||
AuthPass: config.AuthPass,
|
AuthPass: config.AuthPass,
|
||||||
Expiration: orm.LocalDateTime(config.Expiration),
|
Expiration: orm.LocalDateTime(config.Expiration),
|
||||||
|
ProxyHost: edge.Host,
|
||||||
|
ProxyPort: edge.ProxyPort,
|
||||||
}
|
}
|
||||||
if config.AuthPass {
|
if config.AuthPass {
|
||||||
username, password := genPassPair()
|
username, password := genPassPair()
|
||||||
@@ -634,15 +657,17 @@ func assignLongChannels(q *q.Query, userId int32, count int, config ChannelCreat
|
|||||||
req.Username = &channel.Username
|
req.Username = &channel.Username
|
||||||
req.Password = &channel.Password
|
req.Password = &channel.Password
|
||||||
}
|
}
|
||||||
reqs[edge.Host] = append(reqs[edge.Host], req)
|
|
||||||
|
reqs[edge.ProxyID] = append(reqs[edge.ProxyID], req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送配置到网关
|
// 发送配置到网关
|
||||||
if env.DebugExternalChange {
|
if env.DebugExternalChange {
|
||||||
var step = time.Now()
|
var step = time.Now()
|
||||||
for host, reqs := range reqs {
|
for id, reqs := range reqs {
|
||||||
err := g.Proxy.Permit(host, reqs)
|
proxy := proxies[id]
|
||||||
|
err := g.Proxy.Permit(proxy.Host, proxy.Secret, reqs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -654,6 +679,9 @@ func assignLongChannels(q *q.Query, userId int32, count int, config ChannelCreat
|
|||||||
}
|
}
|
||||||
|
|
||||||
func saveAssigns(q *q.Query, resource *ResourceInfo, channels []*m.Channel, now time.Time) (err error) {
|
func saveAssigns(q *q.Query, resource *ResourceInfo, channels []*m.Channel, now time.Time) (err error) {
|
||||||
|
if len(channels) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 缓存通道数据
|
// 缓存通道数据
|
||||||
pipe := g.Redis.TxPipeline()
|
pipe := g.Redis.TxPipeline()
|
||||||
@@ -687,7 +715,7 @@ func saveAssigns(q *q.Query, resource *ResourceInfo, channels []*m.Channel, now
|
|||||||
|
|
||||||
// 更新套餐使用记录
|
// 更新套餐使用记录
|
||||||
var count = len(channels)
|
var count = len(channels)
|
||||||
var last = time.Time(resource.DailyLast)
|
var last = resource.DailyLast
|
||||||
var dailyUsed int32
|
var dailyUsed int32
|
||||||
if now.Year() != last.Year() || now.Month() != last.Month() || now.Day() != last.Day() {
|
if now.Year() != last.Year() || now.Month() != last.Month() || now.Day() != last.Day() {
|
||||||
dailyUsed = int32(count)
|
dailyUsed = int32(count)
|
||||||
@@ -695,7 +723,7 @@ func saveAssigns(q *q.Query, resource *ResourceInfo, channels []*m.Channel, now
|
|||||||
dailyUsed = resource.DailyUsed + int32(count)
|
dailyUsed = resource.DailyUsed + int32(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch resource2.Type(resource.Type) {
|
switch resource.Type {
|
||||||
case resource2.TypeShort:
|
case resource2.TypeShort:
|
||||||
_, err = q.ResourceShort.
|
_, err = q.ResourceShort.
|
||||||
Where(q.ResourceShort.ResourceID.Eq(resource.Id)).
|
Where(q.ResourceShort.ResourceID.Eq(resource.Id)).
|
||||||
@@ -788,4 +816,6 @@ const (
|
|||||||
ErrResourceExhausted = ChannelServiceErr("套餐已用完")
|
ErrResourceExhausted = ChannelServiceErr("套餐已用完")
|
||||||
ErrResourceExpired = ChannelServiceErr("套餐已过期")
|
ErrResourceExpired = ChannelServiceErr("套餐已过期")
|
||||||
ErrResourceDailyLimit = ChannelServiceErr("套餐每日配额已用完")
|
ErrResourceDailyLimit = ChannelServiceErr("套餐每日配额已用完")
|
||||||
|
ErrRemoveForbidden = ChannelServiceErr("删除通道失败,当前用户没有权限")
|
||||||
|
ErrEdgesNoAvailable = ChannelServiceErr("没有可用的节点")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
bill2 "platform/web/domains/bill"
|
bill2 "platform/web/domains/bill"
|
||||||
resource2 "platform/web/domains/resource"
|
resource2 "platform/web/domains/resource"
|
||||||
@@ -44,7 +43,7 @@ func (s *resourceService) CreateResource(uid int32, now time.Time, ser *CreateRe
|
|||||||
|
|
||||||
// 检查余额
|
// 检查余额
|
||||||
if user.Balance.Cmp(amount) < 0 {
|
if user.Balance.Cmp(amount) < 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "余额不足")
|
return ErrBalanceNotEnough
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存套餐
|
// 保存套餐
|
||||||
@@ -70,7 +69,9 @@ func (s *resourceService) CreateResource(uid int32, now time.Time, ser *CreateRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新用户余额
|
// 更新用户余额
|
||||||
_, err = q.User.UpdateSimple(q.User.Balance.Value(user.Balance.Sub(amount)))
|
_, err = q.User.
|
||||||
|
Where(q.User.ID.Eq(uid)).
|
||||||
|
UpdateSimple(q.User.Balance.Value(user.Balance.Sub(amount)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -115,7 +116,7 @@ func (s *resourceService) PrepareResource(uid int32, now time.Time, method trade
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 保存请求缓存
|
// 保存请求缓存
|
||||||
resourceSerializer := CreateResourceSerializer{}
|
resourceSerializer := new(CreateResourceSerializer)
|
||||||
if err := resourceSerializer.ByData(data); err != nil {
|
if err := resourceSerializer.ByData(data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -154,7 +155,7 @@ func (s *resourceService) CompleteResource(tradeNo string, now time.Time, opResu
|
|||||||
|
|
||||||
// 检查交易结果
|
// 检查交易结果
|
||||||
var rs *TransactionVerifyResult
|
var rs *TransactionVerifyResult
|
||||||
if len(opResult) > 0 {
|
if len(opResult) > 0 && opResult[0] != nil {
|
||||||
rs = opResult[0]
|
rs = opResult[0]
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
@@ -296,7 +297,7 @@ type CreateResourceData interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CreateShortResourceData struct {
|
type CreateShortResourceData struct {
|
||||||
Live int32 `json:"live" validate:"required min=180"`
|
Live int32 `json:"live" validate:"required,min=180"`
|
||||||
Mode int32 `json:"mode" validate:"required"`
|
Mode int32 `json:"mode" validate:"required"`
|
||||||
Expire int32 `json:"expire"`
|
Expire int32 `json:"expire"`
|
||||||
DailyLimit int32 `json:"daily_limit" validate:"min=2000"`
|
DailyLimit int32 `json:"daily_limit" validate:"min=2000"`
|
||||||
@@ -315,14 +316,13 @@ func (data *CreateShortResourceData) GetName() string {
|
|||||||
case 2:
|
case 2:
|
||||||
mode = "包量"
|
mode = "包量"
|
||||||
}
|
}
|
||||||
data.name = fmt.Sprintf("短效动态%s %d 天", mode, data.Live)
|
data.name = fmt.Sprintf("短效动态%s %v 分钟", mode, data.Live/60)
|
||||||
}
|
}
|
||||||
return data.name
|
return data.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (data *CreateShortResourceData) GetPrice() decimal.Decimal {
|
func (data *CreateShortResourceData) GetPrice() decimal.Decimal {
|
||||||
if data.price == nil {
|
if data.price == nil {
|
||||||
|
|
||||||
var factor int32
|
var factor int32
|
||||||
switch data.Mode {
|
switch data.Mode {
|
||||||
case 1:
|
case 1:
|
||||||
@@ -345,8 +345,8 @@ func (data *CreateShortResourceData) GetPrice() decimal.Decimal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CreateLongResourceData struct {
|
type CreateLongResourceData struct {
|
||||||
Live int32 `json:"live" validate:"required oneof=1,4,8,12,24"`
|
Live int32 `json:"live" validate:"required,oneof=1 4 8 12 24"`
|
||||||
Mode int32 `json:"mode" validate:"required oneof=1,2"`
|
Mode int32 `json:"mode" validate:"required,oneof=1 2"`
|
||||||
Expire int32 `json:"expire"`
|
Expire int32 `json:"expire"`
|
||||||
DailyLimit int32 `json:"daily_limit" validate:"min=100"`
|
DailyLimit int32 `json:"daily_limit" validate:"min=100"`
|
||||||
Quota int32 `json:"quota" validate:"min=500"`
|
Quota int32 `json:"quota" validate:"min=500"`
|
||||||
@@ -364,14 +364,13 @@ func (data *CreateLongResourceData) GetName() string {
|
|||||||
case 2:
|
case 2:
|
||||||
mode = "包量"
|
mode = "包量"
|
||||||
}
|
}
|
||||||
data.name = fmt.Sprintf("长效动态%s %d 天", mode, data.Live)
|
data.name = fmt.Sprintf("长效动态%s %d 小时", mode, data.Live)
|
||||||
}
|
}
|
||||||
return data.name
|
return data.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (data *CreateLongResourceData) GetPrice() decimal.Decimal {
|
func (data *CreateLongResourceData) GetPrice() decimal.Decimal {
|
||||||
if data.price == nil {
|
if data.price == nil {
|
||||||
|
|
||||||
var factor int32 = 0
|
var factor int32 = 0
|
||||||
switch resource2.Mode(data.Mode) {
|
switch resource2.Mode(data.Mode) {
|
||||||
|
|
||||||
@@ -404,14 +403,6 @@ func (data *CreateLongResourceData) GetPrice() decimal.Decimal {
|
|||||||
return *data.price
|
return *data.price
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateResourceCache struct {
|
|
||||||
Uid int32 `json:"uid"`
|
|
||||||
TradeId int32 `json:"trade_id"`
|
|
||||||
BillId int32 `json:"bill_id"`
|
|
||||||
Method trade2.Method `json:"method"`
|
|
||||||
CreateResourceSerializer
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateResourceSerializer struct {
|
type CreateResourceSerializer struct {
|
||||||
Type resource2.Type `json:"type" validate:"required"`
|
Type resource2.Type `json:"type" validate:"required"`
|
||||||
Short *CreateShortResourceData `json:"short,omitempty"`
|
Short *CreateShortResourceData `json:"short,omitempty"`
|
||||||
@@ -441,3 +432,36 @@ func (s *CreateResourceSerializer) ByData(data CreateResourceData) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateResourceCache struct {
|
||||||
|
Uid int32 `json:"uid"`
|
||||||
|
TradeId int32 `json:"trade_id"`
|
||||||
|
BillId int32 `json:"bill_id"`
|
||||||
|
Method trade2.Method `json:"method"`
|
||||||
|
*CreateResourceSerializer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CreateResourceCache) MarshalBinary() (data []byte, err error) {
|
||||||
|
data, err = json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CreateResourceCache) UnmarshalBinary(data []byte) error {
|
||||||
|
if err := json.Unmarshal(data, &c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResourceServiceErr string
|
||||||
|
|
||||||
|
func (e ResourceServiceErr) Error() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrBalanceNotEnough = ResourceServiceErr("余额不足")
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user