优化与代理服务的密钥存储与传递方式;更新套餐,账单查询对长效套餐的支持,新增长效套餐分页查询接口
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
package globals
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -29,15 +34,19 @@ type ProxyPermitConfig struct {
|
||||
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 {
|
||||
return err
|
||||
return fmt.Errorf("加密请求失败: %w", err)
|
||||
}
|
||||
|
||||
body := strings.NewReader(string(str))
|
||||
resp, err := http.Post(fmt.Sprintf("%s:8848%s", proxy, PermitEndpoint), "application/json", body)
|
||||
resp, err := http.Post(
|
||||
fmt.Sprintf("http://%s:8848%s", host, PermitEndpoint),
|
||||
"application/json",
|
||||
strings.NewReader(body),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -49,3 +58,61 @@ func (p *ProxyClient) Permit(proxy string, config []*ProxyPermitConfig) error {
|
||||
|
||||
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).
|
||||
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()).
|
||||
Offset(req.GetOffset()).
|
||||
Limit(req.GetLimit()).
|
||||
|
||||
@@ -2,6 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base32"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"log/slog"
|
||||
auth2 "platform/web/auth"
|
||||
@@ -44,7 +45,16 @@ func OnlineProxy(c *fiber.Ctx) (err error) {
|
||||
|
||||
// 创建代理
|
||||
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{
|
||||
Name: req.Name,
|
||||
Version: int32(req.Version),
|
||||
@@ -53,7 +63,7 @@ func OnlineProxy(c *fiber.Ctx) (err error) {
|
||||
Secret: secret,
|
||||
Status: 1,
|
||||
}
|
||||
err = q.Proxy.
|
||||
err = q.Proxy.Debug().
|
||||
Clauses(clause.OnConflict{
|
||||
UpdateAll: true,
|
||||
Columns: []clause.Column{
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// region ListResourceShort
|
||||
// region 查询套餐
|
||||
|
||||
type ListResourceShortReq struct {
|
||||
core.PageReq
|
||||
@@ -28,7 +28,6 @@ type ListResourceShortReq struct {
|
||||
ExpireBefore *time.Time `json:"expire_before"`
|
||||
}
|
||||
|
||||
// ListResourceShort 获取套餐列表
|
||||
func ListResourceShort(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authContext, err := auth.Protect(c, []auth.PayloadType{auth.PayloadUser}, []string{})
|
||||
@@ -43,8 +42,10 @@ func ListResourceShort(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// 查询套餐列表
|
||||
do := q.Resource.
|
||||
Where(q.Resource.UserID.Eq(authContext.Payload.Id))
|
||||
do := q.Resource.Where(
|
||||
q.Resource.UserID.Eq(authContext.Payload.Id),
|
||||
q.Resource.Type.Eq(int32(resource2.TypeShort)),
|
||||
)
|
||||
if req.ResourceNo != nil && *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)))
|
||||
}
|
||||
|
||||
resource, err := q.Resource.Where(do).
|
||||
resource, err := q.Resource.Debug().Where(do).
|
||||
Joins(q.Resource.Short).
|
||||
Order(q.Resource.CreatedAt.Desc()).
|
||||
Offset(req.GetOffset()).
|
||||
@@ -97,42 +98,136 @@ func ListResourceShort(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region AllResource
|
||||
|
||||
type AllResourceReq struct {
|
||||
type ListResourceLongReq struct {
|
||||
core.PageReq
|
||||
ResourceNo *string `json:"resource_no"`
|
||||
Active *bool `json:"active"`
|
||||
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{})
|
||||
if err != nil {
|
||||
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.
|
||||
Joins(q.Resource.Short).
|
||||
do := q.Resource.Where(
|
||||
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(authContext.Payload.Id),
|
||||
q.Resource.UserID.Eq(authCtx.Payload.Id),
|
||||
q.Resource.Active.Is(true),
|
||||
q.Resource.Where(
|
||||
short.Type.Eq(int32(resource2.ModeTime)),
|
||||
short.Expire.Gte(orm.LocalDateTime(time.Now())),
|
||||
q.Resource.Type.Eq(int32(resource2.TypeShort)),
|
||||
q.ResourceShort.As(q.Resource.Short.Name()).Where(
|
||||
short.Type.Eq(int32(resource2.ModeTime)),
|
||||
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(
|
||||
short.Type.Eq(int32(resource2.ModeCount)),
|
||||
short.Quota.GtCol(short.Used),
|
||||
),
|
||||
).Or(
|
||||
short.Type.Eq(int32(resource2.ModeCount)),
|
||||
short.Quota.GtCol(short.Used),
|
||||
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),
|
||||
),
|
||||
),
|
||||
q.Resource.Where(
|
||||
short.DailyLast.Lt(orm.LocalDateTime(u.Today())),
|
||||
).Or(
|
||||
short.DailyUsed.LtCol(short.DailyLimit),
|
||||
),
|
||||
)
|
||||
|
||||
resources, err := do.Debug().
|
||||
).
|
||||
Order(q.Resource.CreatedAt.Desc()).
|
||||
Find()
|
||||
if err != nil {
|
||||
@@ -144,7 +239,7 @@ func AllResource(c *fiber.Ctx) error {
|
||||
|
||||
// endregion
|
||||
|
||||
// region CreateResource
|
||||
// region 创建套餐
|
||||
|
||||
type CreateResourceReq struct {
|
||||
s.CreateResourceSerializer
|
||||
@@ -229,7 +324,7 @@ func CompleteCreateResource(c *fiber.Ctx) error {
|
||||
|
||||
// 完成创建套餐
|
||||
var now = time.Now()
|
||||
err = s.Resource.CompleteResource(req.TradeNo, now, nil)
|
||||
err = s.Resource.CompleteResource(req.TradeNo, now)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ func ApplyRouters(app *fiber.App) {
|
||||
// 套餐
|
||||
resource := api.Group("/resource")
|
||||
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/prepare", handlers.PrepareCreateResource)
|
||||
resource.Post("/create/complete", handlers.CompleteCreateResource)
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gen/field"
|
||||
"log/slog"
|
||||
"math"
|
||||
@@ -52,7 +51,7 @@ func (s *channelService) RemoveChannels(ctx context.Context, authCtx *auth.Conte
|
||||
// 检查权限,如果为用户操作的话,则只能删除自己的通道
|
||||
for _, channel := range channels {
|
||||
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,
|
||||
Whitelists: whitelist,
|
||||
AuthPass: authType == ChannelAuthTypePass,
|
||||
Expiration: now.Add(time.Duration(resource.Live) * time.Second),
|
||||
}
|
||||
|
||||
switch resource2.Type(resource.Type) {
|
||||
switch resource.Type {
|
||||
case resource2.TypeShort:
|
||||
config.Expiration = now.Add(time.Duration(resource.Live) * time.Second)
|
||||
channels, err = assignShortChannels(q, authCtx.Payload.Id, count, config, filter, now)
|
||||
case resource2.TypeLong:
|
||||
config.Expiration = now.Add(time.Duration(resource.Live) * time.Hour)
|
||||
channels, err = assignLongChannels(q, authCtx.Payload.Id, count, config, filter)
|
||||
}
|
||||
if err != nil {
|
||||
@@ -260,8 +260,8 @@ func findResource(q *q.Query, resourceId int32, userId int32, count int, now tim
|
||||
|
||||
resource, err := q.Resource.
|
||||
Preload(
|
||||
q.Resource.Short.On(q.Resource.Type.Eq(int32(resource2.TypeShort))),
|
||||
q.Resource.Long.On(q.Resource.Type.Eq(int32(resource2.TypeLong))),
|
||||
q.Resource.Short,
|
||||
q.Resource.Long,
|
||||
).
|
||||
Where(
|
||||
q.Resource.ID.Eq(resourceId),
|
||||
@@ -560,9 +560,22 @@ func assignLongChannels(q *q.Query, userId int32, count int, config ChannelCreat
|
||||
// 查询符合条件的节点,根据 channel 统计使用次数
|
||||
var edges = make([]struct {
|
||||
m.Edge
|
||||
Count int
|
||||
Host string
|
||||
Count int
|
||||
Host string
|
||||
Secret string
|
||||
}, 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.
|
||||
LeftJoin(q.Channel, q.Channel.EdgeID.EqCol(q.Edge.ID)).
|
||||
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.Channel.ALL.Count().As("count"),
|
||||
q.Proxy.Host,
|
||||
q.Proxy.Secret,
|
||||
).
|
||||
Group(q.Edge.ID).
|
||||
Where(
|
||||
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),
|
||||
).
|
||||
Group(q.Edge.ID, q.Proxy.Host, q.Proxy.Secret).
|
||||
Where(do).
|
||||
Order(field.NewField("", "count").Asc()).
|
||||
Scan(edges)
|
||||
Limit(count).
|
||||
Scan(&edges)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Printf("edges: %v\n", edges)
|
||||
if len(edges) == 0 {
|
||||
return nil, ErrEdgesNoAvailable
|
||||
}
|
||||
|
||||
// 计算分配负载(考虑去重,维护一个节点使用记录表,优先分配未使用节点,达到算法额定负载后再选择负载最少的节点)
|
||||
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 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 {
|
||||
if _, ok := proxies[edge.ProxyID]; !ok {
|
||||
proxies[edge.ProxyID] = &m.Proxy{
|
||||
ID: edge.ProxyID,
|
||||
Host: edge.Host,
|
||||
Secret: edge.Secret,
|
||||
}
|
||||
}
|
||||
|
||||
prev := edge.Count
|
||||
next := int(math.Max(float64(prev), float64(int(math.Min(float64(avg), float64(total))))))
|
||||
total -= next
|
||||
@@ -613,6 +634,8 @@ func assignLongChannels(q *q.Query, userId int32, count int, config ChannelCreat
|
||||
AuthIP: config.AuthIp,
|
||||
AuthPass: config.AuthPass,
|
||||
Expiration: orm.LocalDateTime(config.Expiration),
|
||||
ProxyHost: edge.Host,
|
||||
ProxyPort: edge.ProxyPort,
|
||||
}
|
||||
if config.AuthPass {
|
||||
username, password := genPassPair()
|
||||
@@ -634,15 +657,17 @@ func assignLongChannels(q *q.Query, userId int32, count int, config ChannelCreat
|
||||
req.Username = &channel.Username
|
||||
req.Password = &channel.Password
|
||||
}
|
||||
reqs[edge.Host] = append(reqs[edge.Host], req)
|
||||
|
||||
reqs[edge.ProxyID] = append(reqs[edge.ProxyID], req)
|
||||
}
|
||||
}
|
||||
|
||||
// 发送配置到网关
|
||||
if env.DebugExternalChange {
|
||||
var step = time.Now()
|
||||
for host, reqs := range reqs {
|
||||
err := g.Proxy.Permit(host, reqs)
|
||||
for id, reqs := range reqs {
|
||||
proxy := proxies[id]
|
||||
err := g.Proxy.Permit(proxy.Host, proxy.Secret, reqs)
|
||||
if err != nil {
|
||||
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) {
|
||||
if len(channels) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 缓存通道数据
|
||||
pipe := g.Redis.TxPipeline()
|
||||
@@ -687,7 +715,7 @@ func saveAssigns(q *q.Query, resource *ResourceInfo, channels []*m.Channel, now
|
||||
|
||||
// 更新套餐使用记录
|
||||
var count = len(channels)
|
||||
var last = time.Time(resource.DailyLast)
|
||||
var last = resource.DailyLast
|
||||
var dailyUsed int32
|
||||
if now.Year() != last.Year() || now.Month() != last.Month() || now.Day() != last.Day() {
|
||||
dailyUsed = int32(count)
|
||||
@@ -695,7 +723,7 @@ func saveAssigns(q *q.Query, resource *ResourceInfo, channels []*m.Channel, now
|
||||
dailyUsed = resource.DailyUsed + int32(count)
|
||||
}
|
||||
|
||||
switch resource2.Type(resource.Type) {
|
||||
switch resource.Type {
|
||||
case resource2.TypeShort:
|
||||
_, err = q.ResourceShort.
|
||||
Where(q.ResourceShort.ResourceID.Eq(resource.Id)).
|
||||
@@ -788,4 +816,6 @@ const (
|
||||
ErrResourceExhausted = ChannelServiceErr("套餐已用完")
|
||||
ErrResourceExpired = ChannelServiceErr("套餐已过期")
|
||||
ErrResourceDailyLimit = ChannelServiceErr("套餐每日配额已用完")
|
||||
ErrRemoveForbidden = ChannelServiceErr("删除通道失败,当前用户没有权限")
|
||||
ErrEdgesNoAvailable = ChannelServiceErr("没有可用的节点")
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/shopspring/decimal"
|
||||
bill2 "platform/web/domains/bill"
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@@ -154,7 +155,7 @@ func (s *resourceService) CompleteResource(tradeNo string, now time.Time, opResu
|
||||
|
||||
// 检查交易结果
|
||||
var rs *TransactionVerifyResult
|
||||
if len(opResult) > 0 {
|
||||
if len(opResult) > 0 && opResult[0] != nil {
|
||||
rs = opResult[0]
|
||||
} else {
|
||||
var err error
|
||||
@@ -296,7 +297,7 @@ type CreateResourceData interface {
|
||||
}
|
||||
|
||||
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"`
|
||||
Expire int32 `json:"expire"`
|
||||
DailyLimit int32 `json:"daily_limit" validate:"min=2000"`
|
||||
@@ -315,14 +316,13 @@ func (data *CreateShortResourceData) GetName() string {
|
||||
case 2:
|
||||
mode = "包量"
|
||||
}
|
||||
data.name = fmt.Sprintf("短效动态%s %d 天", mode, data.Live)
|
||||
data.name = fmt.Sprintf("短效动态%s %v 分钟", mode, data.Live/60)
|
||||
}
|
||||
return data.name
|
||||
}
|
||||
|
||||
func (data *CreateShortResourceData) GetPrice() decimal.Decimal {
|
||||
if data.price == nil {
|
||||
|
||||
var factor int32
|
||||
switch data.Mode {
|
||||
case 1:
|
||||
@@ -345,8 +345,8 @@ func (data *CreateShortResourceData) GetPrice() decimal.Decimal {
|
||||
}
|
||||
|
||||
type CreateLongResourceData struct {
|
||||
Live int32 `json:"live" validate:"required oneof=1,4,8,12,24"`
|
||||
Mode int32 `json:"mode" validate:"required oneof=1,2"`
|
||||
Live int32 `json:"live" validate:"required,oneof=1 4 8 12 24"`
|
||||
Mode int32 `json:"mode" validate:"required,oneof=1 2"`
|
||||
Expire int32 `json:"expire"`
|
||||
DailyLimit int32 `json:"daily_limit" validate:"min=100"`
|
||||
Quota int32 `json:"quota" validate:"min=500"`
|
||||
@@ -364,14 +364,13 @@ func (data *CreateLongResourceData) GetName() string {
|
||||
case 2:
|
||||
mode = "包量"
|
||||
}
|
||||
data.name = fmt.Sprintf("长效动态%s %d 天", mode, data.Live)
|
||||
data.name = fmt.Sprintf("长效动态%s %d 小时", mode, data.Live)
|
||||
}
|
||||
return data.name
|
||||
}
|
||||
|
||||
func (data *CreateLongResourceData) GetPrice() decimal.Decimal {
|
||||
if data.price == nil {
|
||||
|
||||
var factor int32 = 0
|
||||
switch resource2.Mode(data.Mode) {
|
||||
|
||||
@@ -404,14 +403,6 @@ func (data *CreateLongResourceData) GetPrice() decimal.Decimal {
|
||||
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 resource2.Type `json:"type" validate:"required"`
|
||||
Short *CreateShortResourceData `json:"short,omitempty"`
|
||||
@@ -441,3 +432,36 @@ func (s *CreateResourceSerializer) ByData(data CreateResourceData) error {
|
||||
}
|
||||
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