From 15ffccf554b79bd7dbdd93c09677b20b9361ac8b Mon Sep 17 00:00:00 2001 From: luorijun Date: Thu, 22 May 2025 14:55:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=8E=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E7=9A=84=E5=AF=86=E9=92=A5=E5=AD=98=E5=82=A8?= =?UTF-8?q?=E4=B8=8E=E4=BC=A0=E9=80=92=E6=96=B9=E5=BC=8F=EF=BC=9B=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=A5=97=E9=A4=90=EF=BC=8C=E8=B4=A6=E5=8D=95=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E5=AF=B9=E9=95=BF=E6=95=88=E5=A5=97=E9=A4=90=E7=9A=84?= =?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=8C=E6=96=B0=E5=A2=9E=E9=95=BF=E6=95=88?= =?UTF-8?q?=E5=A5=97=E9=A4=90=E5=88=86=E9=A1=B5=E6=9F=A5=E8=AF=A2=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/globals/proxy.go | 77 ++++++++++++++++++-- web/handlers/bill.go | 2 +- web/handlers/proxy.go | 14 +++- web/handlers/resource.go | 153 +++++++++++++++++++++++++++++++-------- web/router.go | 3 +- web/services/channel.go | 76 +++++++++++++------ web/services/resource.go | 64 +++++++++++----- 7 files changed, 308 insertions(+), 81 deletions(-) diff --git a/web/globals/proxy.go b/web/globals/proxy.go index 48addd2..a8c439a 100644 --- a/web/globals/proxy.go +++ b/web/globals/proxy.go @@ -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"` +} diff --git a/web/handlers/bill.go b/web/handlers/bill.go index beab01f..269df6a 100644 --- a/web/handlers/bill.go +++ b/web/handlers/bill.go @@ -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()). diff --git a/web/handlers/proxy.go b/web/handlers/proxy.go index a13c0e7..ca94c9c 100644 --- a/web/handlers/proxy.go +++ b/web/handlers/proxy.go @@ -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{ diff --git a/web/handlers/resource.go b/web/handlers/resource.go index 238f667..a1531b7 100644 --- a/web/handlers/resource.go +++ b/web/handlers/resource.go @@ -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 } diff --git a/web/router.go b/web/router.go index 3859f62..9d29de7 100644 --- a/web/router.go +++ b/web/router.go @@ -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) diff --git a/web/services/channel.go b/web/services/channel.go index 1108a45..09aa017 100644 --- a/web/services/channel.go +++ b/web/services/channel.go @@ -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("没有可用的节点") ) diff --git a/web/services/resource.go b/web/services/resource.go index 46e9029..a7fe067 100644 --- a/web/services/resource.go +++ b/web/services/resource.go @@ -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("余额不足") +)