diff --git a/scripts/sql/init.sql b/scripts/sql/init.sql index ec4be59..2e7aefc 100644 --- a/scripts/sql/init.sql +++ b/scripts/sql/init.sql @@ -713,14 +713,13 @@ drop table if exists resource_short cascade; create table resource_short ( id int generated by default as identity primary key, resource_id int not null, - type int not null, live int not null, - expire timestamptz, - quota int, + type int not null, + quota int not null, + expire_at timestamptz, used int not null default 0, - daily_limit int not null default 0, - daily_used int not null default 0, - daily_last timestamptz + daily int not null default 0, + last_at timestamptz ); create index idx_resource_short_resource_id on resource_short (resource_id); @@ -728,28 +727,26 @@ create index idx_resource_short_resource_id on resource_short (resource_id); comment on table resource_short is '短效动态套餐表'; comment on column resource_short.id is 'ID'; comment on column resource_short.resource_id is '套餐ID'; -comment on column resource_short.type is '套餐类型:1-包时,2-包量'; comment on column resource_short.live is '可用时长(秒)'; -comment on column resource_short.quota is '配额数量'; -comment on column resource_short.used is '已用数量'; -comment on column resource_short.expire is '过期时间'; -comment on column resource_short.daily_limit is '每日限制'; -comment on column resource_short.daily_used is '今日已用数量'; -comment on column resource_short.daily_last is '今日最后使用时间'; +comment on column resource_short.type is '套餐类型:1-包时,2-包量'; +comment on column resource_short.quota is '每日配额(包时)或总配额(包量)'; +comment on column resource_short.expire_at is '套餐过期时间,包时模式可用'; +comment on column resource_short.used is '总用量'; +comment on column resource_short.daily is '当日用量'; +comment on column resource_short.last_at is '最后使用时间'; -- resource_long drop table if exists resource_long cascade; create table resource_long ( id int generated by default as identity primary key, resource_id int not null, - type int not null, live int not null, - expire timestamptz, - quota int, + type int not null, + quota int not null, + expire_at timestamptz, used int not null default 0, - daily_limit int not null default 0, - daily_used int not null default 0, - daily_last timestamptz + daily int not null default 0, + last_at timestamptz ); create index idx_resource_long_resource_id on resource_long (resource_id); @@ -757,14 +754,13 @@ create index idx_resource_long_resource_id on resource_long (resource_id); comment on table resource_long is '长效动态套餐表'; comment on column resource_long.id is 'ID'; comment on column resource_long.resource_id is '套餐ID'; +comment on column resource_long.live is '可用时长(小时)'; comment on column resource_long.type is '套餐类型:1-包时,2-包量'; -comment on column resource_long.live is '可用时长(天)'; -comment on column resource_long.quota is '配额数量'; -comment on column resource_long.used is '已用数量'; -comment on column resource_long.expire is '过期时间'; -comment on column resource_long.daily_limit is '每日限制'; -comment on column resource_long.daily_used is '今日已用数量'; -comment on column resource_long.daily_last is '今日最后使用时间'; +comment on column resource_long.quota is '每日配额(包时)或总配额(包量)'; +comment on column resource_long.expire_at is '套餐过期时间,包时模式可用'; +comment on column resource_long.used is '总用量'; +comment on column resource_long.daily is '当日用量'; +comment on column resource_long.last_at is '最后使用时间'; -- endregion diff --git a/web/handlers/resource.go b/web/handlers/resource.go index aba4e1f..1abb6b9 100644 --- a/web/handlers/resource.go +++ b/web/handlers/resource.go @@ -60,10 +60,10 @@ func ListResourceShort(c *fiber.Ctx) error { do.Where(q.Resource.CreatedAt.Lte(*req.CreateBefore)) } if req.ExpireAfter != nil { - do.Where(q.ResourceShort.As(q.Resource.Short.Name()).Expire.Gte(*req.ExpireAfter)) + do.Where(q.ResourceShort.As(q.Resource.Short.Name()).ExpireAt.Gte(*req.ExpireAfter)) } if req.ExpireBefore != nil { - do.Where(q.ResourceShort.As(q.Resource.Short.Name()).Expire.Lte(*req.ExpireBefore)) + do.Where(q.ResourceShort.As(q.Resource.Short.Name()).ExpireAt.Lte(*req.ExpireBefore)) } resource, err := q.Resource.Where(do). @@ -141,10 +141,10 @@ func ListResourceLong(c *fiber.Ctx) error { do.Where(q.Resource.CreatedAt.Lte(*req.CreateBefore)) } if req.ExpireAfter != nil { - do.Where(q.ResourceLong.As(q.Resource.Long.Name()).Expire.Gte(*req.ExpireAfter)) + do.Where(q.ResourceLong.As(q.Resource.Long.Name()).ExpireAt.Gte(*req.ExpireAfter)) } if req.ExpireBefore != nil { - do.Where(q.ResourceLong.As(q.Resource.Long.Name()).Expire.Lte(*req.ExpireBefore)) + do.Where(q.ResourceLong.As(q.Resource.Long.Name()).ExpireAt.Lte(*req.ExpireBefore)) } resource, err := q.Resource.Where(do). @@ -204,10 +204,10 @@ func AllActiveResource(c *fiber.Ctx) error { q.Resource.Type.Eq(int(m.ResourceTypeShort)), q.ResourceShort.As(q.Resource.Short.Name()).Where( short.Type.Eq(int(m.ResourceModeTime)), - short.Expire.Gte(now), + short.ExpireAt.Gte(now), q.ResourceShort.As(q.Resource.Short.Name()). - Where(short.DailyLast.Lt(u.Today())). - Or(short.DailyLimit.GtCol(short.DailyUsed)), + Where(short.LastAt.Lt(u.Today())). + Or(short.Quota.GtCol(short.Daily)), ).Or( short.Type.Eq(int(m.ResourceModeQuota)), short.Quota.GtCol(short.Used), @@ -216,10 +216,10 @@ func AllActiveResource(c *fiber.Ctx) error { q.Resource.Type.Eq(int(m.ResourceTypeLong)), q.ResourceLong.As(q.Resource.Long.Name()).Where( long.Type.Eq(int(m.ResourceModeTime)), - long.Expire.Gte(now), + long.ExpireAt.Gte(now), q.ResourceLong.As(q.Resource.Long.Name()). - Where(long.DailyLast.Lt(u.Today())). - Or(long.DailyLimit.GtCol(long.DailyUsed)), + Where(long.LastAt.Lt(u.Today())). + Or(long.Quota.GtCol(long.Daily)), ).Or( long.Type.Eq(int(m.ResourceModeQuota)), long.Quota.GtCol(long.Used), @@ -282,39 +282,39 @@ func StatisticResourceFree(c *fiber.Ctx) error { // 短效包量 case resource.Type == m.ResourceTypeShort && resource.Short.Type == m.ResourceModeQuota: - if u.Z(resource.Short.Quota) > resource.Short.Used { + if resource.Short.Quota > resource.Short.Used { shortCount++ - shortQuotaSum += int(u.Z(resource.Short.Quota) - resource.Short.Used) + shortQuotaSum += int(resource.Short.Quota - resource.Short.Used) } // 长效包量 case resource.Type == m.ResourceTypeLong && resource.Long.Type == m.ResourceModeQuota: - if u.Z(resource.Long.Quota) > resource.Long.Used { + if resource.Long.Quota > resource.Long.Used { longCount++ - longQuotaSum += int(u.Z(resource.Long.Quota) - resource.Long.Used) + longQuotaSum += int(resource.Long.Quota - resource.Long.Used) } // 短效包时 case resource.Type == m.ResourceTypeShort && resource.Short.Type == m.ResourceModeTime: - if time.Time(*resource.Short.Expire).After(time.Now()) { - if resource.Short.DailyLast == nil || u.IsToday(time.Time(*resource.Short.DailyLast)) == false { + if time.Time(*resource.Short.ExpireAt).After(time.Now()) { + if resource.Short.LastAt == nil || u.IsToday(time.Time(*resource.Short.LastAt)) == false { shortCount++ - shortDailyFreeSum += int(resource.Short.DailyLimit) - } else if resource.Short.DailyLimit > resource.Short.DailyUsed { + shortDailyFreeSum += int(resource.Short.Quota) + } else if resource.Short.Quota > resource.Short.Daily { shortCount++ - shortDailyFreeSum += int(resource.Short.DailyLimit - resource.Short.DailyUsed) + shortDailyFreeSum += int(resource.Short.Quota - resource.Short.Daily) } } // 长效包时 case resource.Type == m.ResourceTypeLong && resource.Long.Type == m.ResourceModeTime: - if time.Time(*resource.Long.Expire).After(time.Now()) { - if resource.Long.DailyLast == nil || u.IsToday(time.Time(*resource.Long.DailyLast)) == false { + if time.Time(*resource.Long.ExpireAt).After(time.Now()) { + if resource.Long.LastAt == nil || u.IsToday(time.Time(*resource.Long.LastAt)) == false { longCount++ - longDailyFreeSum += int(resource.Long.DailyLimit) - } else if resource.Long.DailyLimit > resource.Long.DailyUsed { + longDailyFreeSum += int(resource.Long.Quota) + } else if resource.Long.Quota > resource.Long.Daily { longCount++ - longDailyFreeSum += int(resource.Long.DailyLimit - resource.Long.DailyUsed) + longDailyFreeSum += int(resource.Long.Quota - resource.Long.Daily) } } } @@ -443,7 +443,11 @@ func ResourcePrice(c *fiber.Ctx) error { } // 获取套餐价格 + amount, err := req.GetAmount() + if err != nil { + return err + } return c.JSON(fiber.Map{ - "price": req.GetAmount().StringFixed(2), + "price": amount.StringFixed(2), }) } diff --git a/web/models/resource_long.go b/web/models/resource_long.go index ecddf5f..dde4be0 100644 --- a/web/models/resource_long.go +++ b/web/models/resource_long.go @@ -8,12 +8,11 @@ import ( type ResourceLong struct { ID int32 `json:"id" gorm:"column:id"` // ID ResourceID int32 `json:"resource_id" gorm:"column:resource_id"` // 套餐ID + Live int32 `json:"live" gorm:"column:live"` // 可用时长(小时) Type ResourceMode `json:"type" gorm:"column:type"` // 套餐类型:1-包时,2-包量 - Live int32 `json:"live" gorm:"column:live"` // 可用时长(天) - Expire *time.Time `json:"expire" gorm:"column:expire"` // 过期时间 - Quota *int32 `json:"quota" gorm:"column:quota"` // 配额数量 - Used int32 `json:"used" gorm:"column:used"` // 已用数量 - DailyLimit int32 `json:"daily_limit" gorm:"column:daily_limit"` // 每日限制 - DailyUsed int32 `json:"daily_used" gorm:"column:daily_used"` // 今日已用数量 - DailyLast *time.Time `json:"daily_last" gorm:"column:daily_last"` // 今日最后使用时间 + Quota int32 `json:"quota" gorm:"column:quota"` // 每日配额(包时)或总配额(包量) + ExpireAt *time.Time `json:"expire_at" gorm:"column:expire_at"` // 套餐过期时间,包时模式可用 + Used int32 `json:"used" gorm:"column:used"` // 总用量 + Daily int32 `json:"daily" gorm:"column:daily"` // 当日用量 + LastAt *time.Time `json:"last_at" gorm:"column:last_at"` // 最后使用时间 } diff --git a/web/models/resource_short.go b/web/models/resource_short.go index edc545f..404a30a 100644 --- a/web/models/resource_short.go +++ b/web/models/resource_short.go @@ -8,12 +8,11 @@ import ( type ResourceShort struct { ID int32 `json:"id" gorm:"column:id"` // ID ResourceID int32 `json:"resource_id" gorm:"column:resource_id"` // 套餐ID - Type ResourceMode `json:"type" gorm:"column:type"` // 套餐类型:1-包时,2-包量 Live int32 `json:"live" gorm:"column:live"` // 可用时长(秒) - Expire *time.Time `json:"expire" gorm:"column:expire"` // 过期时间 - Quota *int32 `json:"quota" gorm:"column:quota"` // 配额数量 - Used int32 `json:"used" gorm:"column:used"` // 已用数量 - DailyLimit int32 `json:"daily_limit" gorm:"column:daily_limit"` // 每日限制 - DailyUsed int32 `json:"daily_used" gorm:"column:daily_used"` // 今日已用数量 - DailyLast *time.Time `json:"daily_last" gorm:"column:daily_last"` // 今日最后使用时间 + Type ResourceMode `json:"type" gorm:"column:type"` // 套餐类型:1-包时,2-包量 + Quota int32 `json:"quota" gorm:"column:quota"` // 每日配额(包时)或总配额(包量) + ExpireAt *time.Time `json:"expire_at" gorm:"column:expire_at"` // 套餐过期时间,包时模式可用 + Used int32 `json:"used" gorm:"column:used"` // 总用量 + Daily int32 `json:"daily" gorm:"column:daily"` // 当日用量 + LastAt *time.Time `json:"last_at" gorm:"column:last_at"` // 最后使用时间 } diff --git a/web/queries/resource_long.gen.go b/web/queries/resource_long.gen.go index b868327..fed7226 100644 --- a/web/queries/resource_long.gen.go +++ b/web/queries/resource_long.gen.go @@ -29,14 +29,13 @@ func newResourceLong(db *gorm.DB, opts ...gen.DOOption) resourceLong { _resourceLong.ALL = field.NewAsterisk(tableName) _resourceLong.ID = field.NewInt32(tableName, "id") _resourceLong.ResourceID = field.NewInt32(tableName, "resource_id") - _resourceLong.Type = field.NewInt(tableName, "type") _resourceLong.Live = field.NewInt32(tableName, "live") - _resourceLong.Expire = field.NewTime(tableName, "expire") + _resourceLong.Type = field.NewInt(tableName, "type") _resourceLong.Quota = field.NewInt32(tableName, "quota") + _resourceLong.ExpireAt = field.NewTime(tableName, "expire_at") _resourceLong.Used = field.NewInt32(tableName, "used") - _resourceLong.DailyLimit = field.NewInt32(tableName, "daily_limit") - _resourceLong.DailyUsed = field.NewInt32(tableName, "daily_used") - _resourceLong.DailyLast = field.NewTime(tableName, "daily_last") + _resourceLong.Daily = field.NewInt32(tableName, "daily") + _resourceLong.LastAt = field.NewTime(tableName, "last_at") _resourceLong.fillFieldMap() @@ -49,14 +48,13 @@ type resourceLong struct { ALL field.Asterisk ID field.Int32 ResourceID field.Int32 - Type field.Int Live field.Int32 - Expire field.Time + Type field.Int Quota field.Int32 + ExpireAt field.Time Used field.Int32 - DailyLimit field.Int32 - DailyUsed field.Int32 - DailyLast field.Time + Daily field.Int32 + LastAt field.Time fieldMap map[string]field.Expr } @@ -75,14 +73,13 @@ func (r *resourceLong) updateTableName(table string) *resourceLong { r.ALL = field.NewAsterisk(table) r.ID = field.NewInt32(table, "id") r.ResourceID = field.NewInt32(table, "resource_id") - r.Type = field.NewInt(table, "type") r.Live = field.NewInt32(table, "live") - r.Expire = field.NewTime(table, "expire") + r.Type = field.NewInt(table, "type") r.Quota = field.NewInt32(table, "quota") + r.ExpireAt = field.NewTime(table, "expire_at") r.Used = field.NewInt32(table, "used") - r.DailyLimit = field.NewInt32(table, "daily_limit") - r.DailyUsed = field.NewInt32(table, "daily_used") - r.DailyLast = field.NewTime(table, "daily_last") + r.Daily = field.NewInt32(table, "daily") + r.LastAt = field.NewTime(table, "last_at") r.fillFieldMap() @@ -99,17 +96,16 @@ func (r *resourceLong) GetFieldByName(fieldName string) (field.OrderExpr, bool) } func (r *resourceLong) fillFieldMap() { - r.fieldMap = make(map[string]field.Expr, 10) + r.fieldMap = make(map[string]field.Expr, 9) r.fieldMap["id"] = r.ID r.fieldMap["resource_id"] = r.ResourceID - r.fieldMap["type"] = r.Type r.fieldMap["live"] = r.Live - r.fieldMap["expire"] = r.Expire + r.fieldMap["type"] = r.Type r.fieldMap["quota"] = r.Quota + r.fieldMap["expire_at"] = r.ExpireAt r.fieldMap["used"] = r.Used - r.fieldMap["daily_limit"] = r.DailyLimit - r.fieldMap["daily_used"] = r.DailyUsed - r.fieldMap["daily_last"] = r.DailyLast + r.fieldMap["daily"] = r.Daily + r.fieldMap["last_at"] = r.LastAt } func (r resourceLong) clone(db *gorm.DB) resourceLong { diff --git a/web/queries/resource_short.gen.go b/web/queries/resource_short.gen.go index d166635..8a72193 100644 --- a/web/queries/resource_short.gen.go +++ b/web/queries/resource_short.gen.go @@ -29,14 +29,13 @@ func newResourceShort(db *gorm.DB, opts ...gen.DOOption) resourceShort { _resourceShort.ALL = field.NewAsterisk(tableName) _resourceShort.ID = field.NewInt32(tableName, "id") _resourceShort.ResourceID = field.NewInt32(tableName, "resource_id") - _resourceShort.Type = field.NewInt(tableName, "type") _resourceShort.Live = field.NewInt32(tableName, "live") - _resourceShort.Expire = field.NewTime(tableName, "expire") + _resourceShort.Type = field.NewInt(tableName, "type") _resourceShort.Quota = field.NewInt32(tableName, "quota") + _resourceShort.ExpireAt = field.NewTime(tableName, "expire_at") _resourceShort.Used = field.NewInt32(tableName, "used") - _resourceShort.DailyLimit = field.NewInt32(tableName, "daily_limit") - _resourceShort.DailyUsed = field.NewInt32(tableName, "daily_used") - _resourceShort.DailyLast = field.NewTime(tableName, "daily_last") + _resourceShort.Daily = field.NewInt32(tableName, "daily") + _resourceShort.LastAt = field.NewTime(tableName, "last_at") _resourceShort.fillFieldMap() @@ -49,14 +48,13 @@ type resourceShort struct { ALL field.Asterisk ID field.Int32 ResourceID field.Int32 - Type field.Int Live field.Int32 - Expire field.Time + Type field.Int Quota field.Int32 + ExpireAt field.Time Used field.Int32 - DailyLimit field.Int32 - DailyUsed field.Int32 - DailyLast field.Time + Daily field.Int32 + LastAt field.Time fieldMap map[string]field.Expr } @@ -75,14 +73,13 @@ func (r *resourceShort) updateTableName(table string) *resourceShort { r.ALL = field.NewAsterisk(table) r.ID = field.NewInt32(table, "id") r.ResourceID = field.NewInt32(table, "resource_id") - r.Type = field.NewInt(table, "type") r.Live = field.NewInt32(table, "live") - r.Expire = field.NewTime(table, "expire") + r.Type = field.NewInt(table, "type") r.Quota = field.NewInt32(table, "quota") + r.ExpireAt = field.NewTime(table, "expire_at") r.Used = field.NewInt32(table, "used") - r.DailyLimit = field.NewInt32(table, "daily_limit") - r.DailyUsed = field.NewInt32(table, "daily_used") - r.DailyLast = field.NewTime(table, "daily_last") + r.Daily = field.NewInt32(table, "daily") + r.LastAt = field.NewTime(table, "last_at") r.fillFieldMap() @@ -99,17 +96,16 @@ func (r *resourceShort) GetFieldByName(fieldName string) (field.OrderExpr, bool) } func (r *resourceShort) fillFieldMap() { - r.fieldMap = make(map[string]field.Expr, 10) + r.fieldMap = make(map[string]field.Expr, 9) r.fieldMap["id"] = r.ID r.fieldMap["resource_id"] = r.ResourceID - r.fieldMap["type"] = r.Type r.fieldMap["live"] = r.Live - r.fieldMap["expire"] = r.Expire + r.fieldMap["type"] = r.Type r.fieldMap["quota"] = r.Quota + r.fieldMap["expire_at"] = r.ExpireAt r.fieldMap["used"] = r.Used - r.fieldMap["daily_limit"] = r.DailyLimit - r.fieldMap["daily_used"] = r.DailyUsed - r.fieldMap["daily_last"] = r.DailyLast + r.fieldMap["daily"] = r.Daily + r.fieldMap["last_at"] = r.LastAt } func (r resourceShort) clone(db *gorm.DB) resourceShort { diff --git a/web/services/channel.go b/web/services/channel.go index caeba5d..58208f7 100644 --- a/web/services/channel.go +++ b/web/services/channel.go @@ -2,9 +2,11 @@ package services import ( "context" + "errors" "fmt" "math/rand/v2" "net/netip" + "platform/pkg/u" "platform/web/core" g "platform/web/globals" m "platform/web/models" @@ -66,7 +68,7 @@ func genPassPair() (string, string) { } // 查找资源 -func findResource(resourceId int32) (*ResourceView, error) { +func findResource(resourceId int32, now time.Time) (*ResourceView, error) { resource, err := q.Resource. Preload(field.Associations). Where( @@ -82,57 +84,43 @@ func findResource(resourceId int32) (*ResourceView, error) { } var info = &ResourceView{ Id: resource.ID, + User: *resource.User, Active: resource.Active, Type: resource.Type, - User: *resource.User, } switch resource.Type { case m.ResourceTypeShort: var sub = resource.Short - var dailyLast = time.Time{} - if sub.DailyLast != nil { - dailyLast = time.Time(*sub.DailyLast) - } - var expire = time.Time{} - if sub.Expire != nil { - expire = time.Time(*sub.Expire) - } - var quota int32 - if sub.Quota != nil { - quota = *sub.Quota - } - info.Mode = sub.Type + info.ShortId = &sub.ID + info.ExpireAt = sub.ExpireAt info.Live = time.Duration(sub.Live) * time.Second - info.DailyLimit = sub.DailyLimit - info.DailyUsed = sub.DailyUsed - info.DailyLast = dailyLast - info.Expire = expire - info.Quota = quota + info.Mode = sub.Type + info.Quota = sub.Quota info.Used = sub.Used + info.Daily = sub.Daily + info.LastAt = sub.LastAt + if sub.LastAt != nil && u.IsSameDate(*sub.LastAt, now) { + info.Today = int(sub.Daily) + } case m.ResourceTypeLong: var sub = resource.Long - var dailyLast = time.Time{} - if sub.DailyLast != nil { - dailyLast = time.Time(*sub.DailyLast) - } - var expire = time.Time{} - if sub.Expire != nil { - expire = time.Time(*sub.Expire) - } - var quota int32 - if sub.Quota != nil { - quota = *sub.Quota - } + info.LongId = &sub.ID + info.ExpireAt = sub.ExpireAt + info.Live = time.Duration(sub.Live) * time.Hour info.Mode = sub.Type - info.Live = time.Duration(sub.Live) * time.Hour * 24 - info.DailyLimit = sub.DailyLimit - info.DailyUsed = sub.DailyUsed - info.DailyLast = dailyLast - info.Expire = expire - info.Quota = quota + info.Quota = sub.Quota info.Used = sub.Used + info.Daily = sub.Daily + info.LastAt = sub.LastAt + if sub.LastAt != nil && u.IsSameDate(*sub.LastAt, now) { + info.Today = int(sub.Daily) + } + } + + if info.Mode == m.ResourceModeTime && info.ExpireAt == nil { + return nil, errors.New("检查套餐获取时间失败") } return info, nil @@ -140,18 +128,20 @@ func findResource(resourceId int32) (*ResourceView, error) { // ResourceView 套餐数据的简化视图,便于直接获取主要数据 type ResourceView struct { - Id int32 - Active bool - Type m.ResourceType - Mode m.ResourceMode - Live time.Duration - DailyLimit int32 - DailyUsed int32 - DailyLast time.Time - Quota int32 - Used int32 - Expire time.Time - User m.User + Id int32 + User m.User + Active bool + Type m.ResourceType + ShortId *int32 + LongId *int32 + Live time.Duration + Mode m.ResourceMode + Quota int32 + ExpireAt *time.Time + Used int32 + Daily int32 + LastAt *time.Time + Today int // 今日用量 } // 检查用户是否可提取 @@ -161,7 +151,7 @@ func ensure(now time.Time, source netip.Addr, resourceId int32, count int) (*Res } // 获取用户套餐 - resource, err := findResource(resourceId) + resource, err := findResource(resourceId, now) if err != nil { return nil, nil, err } @@ -200,16 +190,11 @@ func ensure(now time.Time, source netip.Addr, resourceId int32, count int) (*Res // 包时 case m.ResourceModeTime: // 检查过期时间 - if resource.Expire.Before(now) { + if resource.ExpireAt.Before(now) { return nil, nil, ErrResourceExpired } // 检查每日限额 - used := 0 - if now.Format("2006-01-02") == resource.DailyLast.Format("2006-01-02") { - used = int(resource.DailyUsed) - } - excess := used+count > int(resource.DailyLimit) - if excess { + if count+resource.Today > int(resource.Quota) { return nil, nil, ErrResourceDailyLimit } diff --git a/web/services/channel_baiyin.go b/web/services/channel_baiyin.go index d438a7d..beec589 100644 --- a/web/services/channel_baiyin.go +++ b/web/services/channel_baiyin.go @@ -17,6 +17,7 @@ import ( "time" "github.com/hibiken/asynq" + "gorm.io/gen" "gorm.io/gen/field" ) @@ -152,44 +153,48 @@ func (s *channelBaiyinService) CreateChannels(source netip.Addr, resourceId int3 // 保存数据 err = q.Q.Transaction(func(q *q.Query) error { + var rs gen.ResultInfo - // 更新套餐用量 - used := int32(count) - if u.IsSameDate(now, resource.DailyLast) { - used += resource.DailyUsed - } + // 根据套餐类型和模式更新使用记录 + isShortType := resource.Type == m.ResourceTypeShort + isLongType := resource.Type == m.ResourceTypeLong - switch resource.Type { - case m.ResourceTypeShort: - _, err = q.ResourceShort. + switch { + case isShortType: + rs, err = q.ResourceShort.Debug(). Where( - q.ResourceShort.ResourceID.Eq(resource.Id), + q.ResourceShort.ID.Eq(*resource.ShortId), q.ResourceShort.Used.Eq(resource.Used), - q.ResourceShort.DailyUsed.Eq(resource.DailyUsed), - q.ResourceShort.DailyLast.Eq(resource.DailyLast), + q.ResourceShort.Daily.Eq(resource.Daily), ). UpdateSimple( q.ResourceShort.Used.Add(int32(count)), - q.ResourceShort.DailyUsed.Value(used), - q.ResourceShort.DailyLast.Value(now), + q.ResourceShort.Daily.Value(int32(resource.Today+count)), + q.ResourceShort.LastAt.Value(now), ) - case m.ResourceTypeLong: - _, err = q.ResourceLong. + + case isLongType: + rs, err = q.ResourceLong.Debug(). Where( - q.ResourceLong.ResourceID.Eq(resource.Id), + q.ResourceLong.ID.Eq(*resource.LongId), q.ResourceLong.Used.Eq(resource.Used), - q.ResourceLong.DailyUsed.Eq(resource.DailyUsed), - q.ResourceLong.DailyLast.Eq(resource.DailyLast), + q.ResourceLong.Daily.Eq(resource.Daily), ). UpdateSimple( q.ResourceLong.Used.Add(int32(count)), - q.ResourceLong.DailyUsed.Value(used), - q.ResourceLong.DailyLast.Value(now), + 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. diff --git a/web/services/resource.go b/web/services/resource.go index 48f4145..bf98ed0 100644 --- a/web/services/resource.go +++ b/web/services/resource.go @@ -2,6 +2,7 @@ package services import ( "encoding/json" + "errors" "fmt" "platform/pkg/u" "platform/web/core" @@ -29,15 +30,19 @@ func (s *resourceService) CreateResourceByBalance(uid int32, now time.Time, data } // 检查余额 - var amount = user.Balance.Sub(data.GetAmount()) - if amount.IsNegative() { + amount, err := data.GetAmount() + if err != nil { + return err + } + balance := user.Balance.Sub(amount) + if balance.IsNegative() { return ErrBalanceNotEnough } // 更新用户余额 _, err = q.User. Where(q.User.ID.Eq(uid), q.User.Balance.Eq(user.Balance)). - UpdateSimple(q.User.Balance.Value(amount)) + UpdateSimple(q.User.Balance.Value(balance)) if err != nil { return core.NewServErr("更新用户余额失败", err) } @@ -49,7 +54,11 @@ func (s *resourceService) CreateResourceByBalance(uid int32, now time.Time, data } // 生成账单 - err = q.Bill.Create(newForConsume(uid, Bill.GenNo(), data.GetSubject(), data.GetAmount(), resource)) + subject, err := data.GetSubject() + if err != nil { + return err + } + err = q.Bill.Create(newForConsume(uid, Bill.GenNo(), subject, amount, resource)) if err != nil { return core.NewServErr("生成账单失败", err) } @@ -61,6 +70,13 @@ func (s *resourceService) CreateResourceByBalance(uid int32, now time.Time, data func (s *resourceService) CreateResourceByTrade(uid int32, now time.Time, data *CreateResourceData, trade *m.Trade) error { return q.Q.Transaction(func(q *q.Query) error { + // 检查交易 + if trade == nil { + return core.NewBizErr("交易数据不能为空") + } + if trade.Status != m.TradeStatusSuccess { + return core.NewBizErr("交易状态不正确") + } // 保存套餐 resource, err := createResource(q, uid, now, data) @@ -69,7 +85,15 @@ func (s *resourceService) CreateResourceByTrade(uid int32, now time.Time, data * } // 生成账单 - err = q.Bill.Create(newForConsume(uid, Bill.GenNo(), data.GetSubject(), data.GetAmount(), resource, trade)) + subject, err := data.GetSubject() + if err != nil { + return err + } + amount, err := data.GetAmount() + if err != nil { + return err + } + err = q.Bill.Create(newForConsume(uid, Bill.GenNo(), subject, amount, resource, trade)) if err != nil { return core.NewServErr("生成账单失败", err) } @@ -95,13 +119,17 @@ func createResource(q *q.Query, uid int32, now time.Time, data *CreateResourceDa if short == nil { return nil, core.NewBizErr("短效套餐数据不能为空") } - var duration = time.Duration(short.Expire) * 24 * time.Hour resource.Short = &m.ResourceShort{ - Type: short.Mode, - Live: short.Live, - Quota: &short.Quota, - Expire: u.P(now.Add(duration)), - DailyLimit: short.DailyLimit, + Live: short.Live, + Type: short.Mode, + Quota: short.Quota, + } + if short.Mode == m.ResourceModeTime { + if short.Expire == nil { + return nil, core.NewBizErr("包时套餐过期时间不能为空") + } + var duration = time.Duration(*short.Expire) * 24 * time.Hour + resource.Short.ExpireAt = u.P(now.Add(duration)) } // 长效套餐 @@ -110,13 +138,17 @@ func createResource(q *q.Query, uid int32, now time.Time, data *CreateResourceDa if long == nil { return nil, core.NewBizErr("长效套餐数据不能为空") } - var duration = time.Duration(long.Expire) * 24 * time.Hour resource.Long = &m.ResourceLong{ - Type: long.Mode, - Live: long.Live, - Quota: &long.Quota, - Expire: u.P(now.Add(duration)), - DailyLimit: long.DailyLimit, + Live: long.Live, + Type: long.Mode, + Quota: long.Quota, + } + if long.Mode == m.ResourceModeTime { + if long.Expire == nil { + return nil, core.NewBizErr("包时套餐过期时间不能为空") + } + var duration = time.Duration(*long.Expire) * 24 * time.Hour + resource.Long.ExpireAt = u.P(now.Add(duration)) } default: return nil, core.NewBizErr("不支持的套餐类型") @@ -137,22 +169,20 @@ type CreateResourceData struct { } type CreateShortResourceData struct { - Live int32 `json:"live" validate:"required,min=180"` - Mode m.ResourceMode `json:"mode" validate:"required"` - Expire int32 `json:"expire"` - DailyLimit int32 `json:"daily_limit" validate:"min=2000"` - Quota int32 `json:"quota" validate:"min=10000"` + Live int32 `json:"live" validate:"required,min=180"` + Mode m.ResourceMode `json:"mode" validate:"required"` + Quota int32 `json:"quota"` + Expire *int32 `json:"expire"` name string price *decimal.Decimal } type CreateLongResourceData struct { - Live int32 `json:"live" validate:"required,oneof=1 4 8 12 24"` - Mode m.ResourceMode `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"` + Live int32 `json:"live" validate:"required"` + Mode m.ResourceMode `json:"mode" validate:"required"` + Quota int32 `json:"quota" validate:"required"` + Expire *int32 `json:"expire" validate:"required"` name string price *decimal.Decimal @@ -162,24 +192,28 @@ func (c *CreateResourceData) GetType() m.TradeType { return m.TradeTypePurchase } -func (c *CreateResourceData) GetSubject() string { +func (c *CreateResourceData) GetSubject() (string, error) { switch c.Type { + default: + return "", errors.New("无效的套餐类型") + case m.ResourceTypeShort: return c.Short.GetSubject() case m.ResourceTypeLong: return c.Long.GetSubject() } - panic("类型对应的数据为空") } -func (c *CreateResourceData) GetAmount() decimal.Decimal { +func (c *CreateResourceData) GetAmount() (decimal.Decimal, error) { switch c.Type { + default: + return decimal.Zero, errors.New("无效的套餐类型") + case m.ResourceTypeShort: return c.Short.GetAmount() case m.ResourceTypeLong: return c.Long.GetAmount() } - panic("类型对应的数据为空") } func (c *CreateResourceData) Serialize() (string, error) { @@ -191,27 +225,37 @@ func (c *CreateResourceData) Deserialize(str string) error { return json.Unmarshal([]byte(str), c) } -func (data *CreateShortResourceData) GetSubject() string { +func (data *CreateShortResourceData) GetSubject() (string, error) { if data.name == "" { var mode string switch data.Mode { - case 1: + default: + return "", errors.New("无效的套餐模式") + + case m.ResourceModeTime: mode = "包时" - case 2: + case m.ResourceModeQuota: mode = "包量" } + data.name = fmt.Sprintf("短效动态%s %v 分钟", mode, data.Live/60) } - return data.name + return data.name, nil } -func (data *CreateShortResourceData) GetAmount() decimal.Decimal { +func (data *CreateShortResourceData) GetAmount() (decimal.Decimal, error) { if data.price == nil { var factor int32 switch data.Mode { - case 1: - factor = data.DailyLimit * data.Expire - case 2: + default: + return decimal.Zero, errors.New("无效的套餐模式") + + case m.ResourceModeTime: + if data.Expire == nil { + return decimal.Zero, errors.New("包时套餐过期时间不能为空") + } + factor = data.Quota * *data.Expire + case m.ResourceModeQuota: factor = data.Quota } @@ -223,38 +267,53 @@ func (data *CreateShortResourceData) GetAmount() decimal.Decimal { var dec = decimal.Decimal{}. Add(decimal.NewFromInt32(base * factor)). Div(decimal.NewFromInt(30000)) + if dec.IsZero() { + return decimal.Zero, errors.New("计算金额错误") + } + data.price = &dec } - return *data.price + return *data.price, nil } -func (data *CreateLongResourceData) GetSubject() string { +func (data *CreateLongResourceData) GetSubject() (string, error) { if data.name == "" { var mode string switch data.Mode { - case 1: + default: + return "", errors.New("无效的套餐模式") + + case m.ResourceModeTime: mode = "包时" - case 2: + case m.ResourceModeQuota: mode = "包量" } + data.name = fmt.Sprintf("长效动态%s %d 小时", mode, data.Live) } - return data.name + return data.name, nil } -func (data *CreateLongResourceData) GetAmount() decimal.Decimal { +func (data *CreateLongResourceData) GetAmount() (decimal.Decimal, error) { if data.price == nil { var factor int32 = 0 switch data.Mode { + default: + return decimal.Zero, errors.New("无效的套餐模式") case m.ResourceModeTime: - factor = data.Expire * data.DailyLimit + if data.Expire == nil { + return decimal.Zero, errors.New("包时套餐过期时间不能为空") + } + factor = *data.Expire * data.Quota case m.ResourceModeQuota: factor = data.Quota } var base int32 switch data.Live { + default: + return decimal.Zero, errors.New("无效的套餐时长") case 1: base = 30 case 4: @@ -271,9 +330,13 @@ func (data *CreateLongResourceData) GetAmount() decimal.Decimal { var dec = decimal.Decimal{}. Add(decimal.NewFromInt32(base * factor)). Div(decimal.NewFromInt(100)) + if dec.IsZero() { + return decimal.Zero, errors.New("计算金额错误") + } + data.price = &dec } - return *data.price + return *data.price, nil } type ResourceOnTradeComplete struct{} diff --git a/web/services/trade.go b/web/services/trade.go index 537c1f3..1ff40f1 100644 --- a/web/services/trade.go +++ b/web/services/trade.go @@ -35,12 +35,18 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa platform := data.Platform method := data.Method tType := data.Product.GetType() - subject := data.Product.GetSubject() - amount := data.Product.GetAmount() expire := time.Now().Add(30 * time.Minute) + subject, err := data.Product.GetSubject() + if err != nil { + return nil, err + } + amount, err := data.Product.GetAmount() + if err != nil { + return nil, err + } // 实际支付金额,只在创建真实订单时使用 - var amountReal = data.Product.GetAmount() + amountReal := amount if env.RunMode == env.RunModeDev { amountReal = decimal.NewFromFloat(0.01) } @@ -60,7 +66,7 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa return nil, err } - var expireAt = time.Time(u.Z(coupon.ExpireAt)) + expireAt := time.Time(u.Z(coupon.ExpireAt)) if !expireAt.IsZero() && expireAt.Before(now) { _, err = q.Coupon. Where(q.Coupon.ID.Eq(coupon.ID)). @@ -99,7 +105,7 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa } // 生成订单号 - var tradeNo, err = ID.GenSerial() + tradeNo, err := ID.GenSerial() if err != nil { return nil, core.NewServErr("生成订单号失败", err) } @@ -692,8 +698,8 @@ type OnTradeCompletedData struct { type ProductInfo interface { GetType() m.TradeType - GetSubject() string - GetAmount() decimal.Decimal + GetSubject() (string, error) + GetAmount() (decimal.Decimal, error) Serialize() (string, error) Deserialize(str string) error } diff --git a/web/services/user.go b/web/services/user.go index cfef225..8169a1d 100644 --- a/web/services/user.go +++ b/web/services/user.go @@ -25,7 +25,15 @@ func (s *userService) UpdateBalanceByTrade(uid int32, info *RechargeProductInfo, } // 生成账单 - err = q.Bill.Create(newForRecharge(uid, Bill.GenNo(), info.GetSubject(), info.GetAmount(), trade)) + subject, err := info.GetSubject() + if err != nil { + return err + } + amount, err := info.GetAmount() + if err != nil { + return err + } + err = q.Bill.Create(newForRecharge(uid, Bill.GenNo(), subject, amount, trade)) if err != nil { return core.NewServErr("生成账单失败", err) } @@ -39,23 +47,25 @@ func (s *userService) UpdateBalanceByTrade(uid int32, info *RechargeProductInfo, return nil } -func updateBalance(q *q.Query, uid int32, info *RechargeProductInfo) (err error) { - - // 更新余额 +func updateBalance(q *q.Query, uid int32, info *RechargeProductInfo) error { user, err := q.User. Where(q.User.ID.Eq(uid)).Take() if err != nil { return core.NewServErr("查询用户失败", err) } - var amount = user.Balance.Add(info.GetAmount()) - if amount.IsNegative() { + amount, err := info.GetAmount() + if err != nil { + return err + } + balance := user.Balance.Add(amount) + if balance.IsNegative() { return core.NewServErr("用户余额不足") } _, err = q.User. Where(q.User.ID.Eq(user.ID)). - UpdateSimple(q.User.Balance.Value(amount)) + UpdateSimple(q.User.Balance.Value(balance)) if err != nil { return core.NewServErr("更新用户余额失败", err) } @@ -75,12 +85,13 @@ func (r *RechargeProductInfo) GetType() m.TradeType { return m.TradeTypeRecharge } -func (r *RechargeProductInfo) GetSubject() string { - return fmt.Sprintf("账户充值 - %s元", r.GetAmount().StringFixed(2)) +func (r *RechargeProductInfo) GetSubject() (string, error) { + amount, _ := r.GetAmount() + return fmt.Sprintf("账户充值 - %s元", amount.StringFixed(2)), nil } -func (r *RechargeProductInfo) GetAmount() decimal.Decimal { - return decimal.NewFromInt(int64(r.Amount)).Div(decimal.NewFromInt(100)) +func (r *RechargeProductInfo) GetAmount() (decimal.Decimal, error) { + return decimal.NewFromInt(int64(r.Amount)).Div(decimal.NewFromInt(100)), nil } func (r *RechargeProductInfo) Serialize() (string, error) {