重构优化套餐数据结构,修复提取计数问题

This commit is contained in:
2025-12-10 20:07:33 +08:00
parent c8c86081d9
commit 05fba68b3e
11 changed files with 310 additions and 250 deletions

View File

@@ -713,14 +713,13 @@ drop table if exists resource_short cascade;
create table resource_short ( create table resource_short (
id int generated by default as identity primary key, id int generated by default as identity primary key,
resource_id int not null, resource_id int not null,
type int not null,
live int not null, live int not null,
expire timestamptz, type int not null,
quota int, quota int not null,
expire_at timestamptz,
used int not null default 0, used int not null default 0,
daily_limit int not null default 0, daily int not null default 0,
daily_used int not null default 0, last_at timestamptz
daily_last timestamptz
); );
create index idx_resource_short_resource_id on resource_short (resource_id); 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 table resource_short is '短效动态套餐表';
comment on column resource_short.id is 'ID'; comment on column resource_short.id is 'ID';
comment on column resource_short.resource_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.live is '可用时长(秒)';
comment on column resource_short.quota is '配额数'; comment on column resource_short.type is '套餐类型1-包时2-包';
comment on column resource_short.used is '已用数量'; comment on column resource_short.quota is '每日配额(包时)或总配额(包量)';
comment on column resource_short.expire is '过期时间'; comment on column resource_short.expire_at is '套餐过期时间,包时模式可用';
comment on column resource_short.daily_limit is '每日限制'; comment on column resource_short.used is '总用量';
comment on column resource_short.daily_used is '今日已用数'; comment on column resource_short.daily is '当日用';
comment on column resource_short.daily_last is '今日最后使用时间'; comment on column resource_short.last_at is '最后使用时间';
-- resource_long -- resource_long
drop table if exists resource_long cascade; drop table if exists resource_long cascade;
create table resource_long ( create table resource_long (
id int generated by default as identity primary key, id int generated by default as identity primary key,
resource_id int not null, resource_id int not null,
type int not null,
live int not null, live int not null,
expire timestamptz, type int not null,
quota int, quota int not null,
expire_at timestamptz,
used int not null default 0, used int not null default 0,
daily_limit int not null default 0, daily int not null default 0,
daily_used int not null default 0, last_at timestamptz
daily_last timestamptz
); );
create index idx_resource_long_resource_id on resource_long (resource_id); 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 table resource_long is '长效动态套餐表';
comment on column resource_long.id is 'ID'; comment on column resource_long.id is 'ID';
comment on column resource_long.resource_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.type is '套餐类型1-包时2-包量';
comment on column resource_long.live is '可用时长(天)'; comment on column resource_long.quota 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.used is '总用';
comment on column resource_long.expire is '过期时间'; comment on column resource_long.daily is '当日用量';
comment on column resource_long.daily_limit is '每日限制'; comment on column resource_long.last_at is '最后使用时间';
comment on column resource_long.daily_used is '今日已用数量';
comment on column resource_long.daily_last is '今日最后使用时间';
-- endregion -- endregion

View File

@@ -60,10 +60,10 @@ func ListResourceShort(c *fiber.Ctx) error {
do.Where(q.Resource.CreatedAt.Lte(*req.CreateBefore)) do.Where(q.Resource.CreatedAt.Lte(*req.CreateBefore))
} }
if req.ExpireAfter != nil { 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 { 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). resource, err := q.Resource.Where(do).
@@ -141,10 +141,10 @@ func ListResourceLong(c *fiber.Ctx) error {
do.Where(q.Resource.CreatedAt.Lte(*req.CreateBefore)) do.Where(q.Resource.CreatedAt.Lte(*req.CreateBefore))
} }
if req.ExpireAfter != nil { 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 { 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). resource, err := q.Resource.Where(do).
@@ -204,10 +204,10 @@ func AllActiveResource(c *fiber.Ctx) error {
q.Resource.Type.Eq(int(m.ResourceTypeShort)), q.Resource.Type.Eq(int(m.ResourceTypeShort)),
q.ResourceShort.As(q.Resource.Short.Name()).Where( q.ResourceShort.As(q.Resource.Short.Name()).Where(
short.Type.Eq(int(m.ResourceModeTime)), short.Type.Eq(int(m.ResourceModeTime)),
short.Expire.Gte(now), short.ExpireAt.Gte(now),
q.ResourceShort.As(q.Resource.Short.Name()). q.ResourceShort.As(q.Resource.Short.Name()).
Where(short.DailyLast.Lt(u.Today())). Where(short.LastAt.Lt(u.Today())).
Or(short.DailyLimit.GtCol(short.DailyUsed)), Or(short.Quota.GtCol(short.Daily)),
).Or( ).Or(
short.Type.Eq(int(m.ResourceModeQuota)), short.Type.Eq(int(m.ResourceModeQuota)),
short.Quota.GtCol(short.Used), short.Quota.GtCol(short.Used),
@@ -216,10 +216,10 @@ func AllActiveResource(c *fiber.Ctx) error {
q.Resource.Type.Eq(int(m.ResourceTypeLong)), q.Resource.Type.Eq(int(m.ResourceTypeLong)),
q.ResourceLong.As(q.Resource.Long.Name()).Where( q.ResourceLong.As(q.Resource.Long.Name()).Where(
long.Type.Eq(int(m.ResourceModeTime)), long.Type.Eq(int(m.ResourceModeTime)),
long.Expire.Gte(now), long.ExpireAt.Gte(now),
q.ResourceLong.As(q.Resource.Long.Name()). q.ResourceLong.As(q.Resource.Long.Name()).
Where(long.DailyLast.Lt(u.Today())). Where(long.LastAt.Lt(u.Today())).
Or(long.DailyLimit.GtCol(long.DailyUsed)), Or(long.Quota.GtCol(long.Daily)),
).Or( ).Or(
long.Type.Eq(int(m.ResourceModeQuota)), long.Type.Eq(int(m.ResourceModeQuota)),
long.Quota.GtCol(long.Used), 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: 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++ 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: 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++ 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: case resource.Type == m.ResourceTypeShort && resource.Short.Type == m.ResourceModeTime:
if time.Time(*resource.Short.Expire).After(time.Now()) { if time.Time(*resource.Short.ExpireAt).After(time.Now()) {
if resource.Short.DailyLast == nil || u.IsToday(time.Time(*resource.Short.DailyLast)) == false { if resource.Short.LastAt == nil || u.IsToday(time.Time(*resource.Short.LastAt)) == false {
shortCount++ shortCount++
shortDailyFreeSum += int(resource.Short.DailyLimit) shortDailyFreeSum += int(resource.Short.Quota)
} else if resource.Short.DailyLimit > resource.Short.DailyUsed { } else if resource.Short.Quota > resource.Short.Daily {
shortCount++ 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: case resource.Type == m.ResourceTypeLong && resource.Long.Type == m.ResourceModeTime:
if time.Time(*resource.Long.Expire).After(time.Now()) { if time.Time(*resource.Long.ExpireAt).After(time.Now()) {
if resource.Long.DailyLast == nil || u.IsToday(time.Time(*resource.Long.DailyLast)) == false { if resource.Long.LastAt == nil || u.IsToday(time.Time(*resource.Long.LastAt)) == false {
longCount++ longCount++
longDailyFreeSum += int(resource.Long.DailyLimit) longDailyFreeSum += int(resource.Long.Quota)
} else if resource.Long.DailyLimit > resource.Long.DailyUsed { } else if resource.Long.Quota > resource.Long.Daily {
longCount++ 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{ return c.JSON(fiber.Map{
"price": req.GetAmount().StringFixed(2), "price": amount.StringFixed(2),
}) })
} }

View File

@@ -8,12 +8,11 @@ import (
type ResourceLong struct { type ResourceLong struct {
ID int32 `json:"id" gorm:"column:id"` // ID ID int32 `json:"id" gorm:"column:id"` // ID
ResourceID int32 `json:"resource_id" gorm:"column:resource_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-包量 Type ResourceMode `json:"type" gorm:"column:type"` // 套餐类型1-包时2-包量
Live int32 `json:"live" gorm:"column:live"` // 可用时长(天) Quota int32 `json:"quota" gorm:"column:quota"` // 每日配额(包时)或总配额(包量)
Expire *time.Time `json:"expire" gorm:"column:expire"` // 过期时间 ExpireAt *time.Time `json:"expire_at" gorm:"column:expire_at"` // 套餐过期时间,包时模式可用
Quota *int32 `json:"quota" gorm:"column:quota"` // 配额数 Used int32 `json:"used" gorm:"column:used"` // 总用
Used int32 `json:"used" gorm:"column:used"` // 已用数 Daily int32 `json:"daily" gorm:"column:daily"` // 当日用
DailyLimit int32 `json:"daily_limit" gorm:"column:daily_limit"` // 每日限制 LastAt *time.Time `json:"last_at" gorm:"column:last_at"` // 最后使用时间
DailyUsed int32 `json:"daily_used" gorm:"column:daily_used"` // 今日已用数量
DailyLast *time.Time `json:"daily_last" gorm:"column:daily_last"` // 今日最后使用时间
} }

View File

@@ -8,12 +8,11 @@ import (
type ResourceShort struct { type ResourceShort struct {
ID int32 `json:"id" gorm:"column:id"` // ID ID int32 `json:"id" gorm:"column:id"` // ID
ResourceID int32 `json:"resource_id" gorm:"column:resource_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"` // 可用时长(秒) Live int32 `json:"live" gorm:"column:live"` // 可用时长(秒)
Expire *time.Time `json:"expire" gorm:"column:expire"` // 过期时间 Type ResourceMode `json:"type" gorm:"column:type"` // 套餐类型1-包时2-包量
Quota *int32 `json:"quota" gorm:"column:quota"` // 配额数量 Quota int32 `json:"quota" gorm:"column:quota"` // 每日配额(包时)或总配额(包量)
Used int32 `json:"used" gorm:"column:used"` // 已用数量 ExpireAt *time.Time `json:"expire_at" gorm:"column:expire_at"` // 套餐过期时间,包时模式可用
DailyLimit int32 `json:"daily_limit" gorm:"column:daily_limit"` // 每日限制 Used int32 `json:"used" gorm:"column:used"` // 总用量
DailyUsed int32 `json:"daily_used" gorm:"column:daily_used"` // 今日已用数 Daily int32 `json:"daily" gorm:"column:daily"` // 当日用
DailyLast *time.Time `json:"daily_last" gorm:"column:daily_last"` // 今日最后使用时间 LastAt *time.Time `json:"last_at" gorm:"column:last_at"` // 最后使用时间
} }

View File

@@ -29,14 +29,13 @@ func newResourceLong(db *gorm.DB, opts ...gen.DOOption) resourceLong {
_resourceLong.ALL = field.NewAsterisk(tableName) _resourceLong.ALL = field.NewAsterisk(tableName)
_resourceLong.ID = field.NewInt32(tableName, "id") _resourceLong.ID = field.NewInt32(tableName, "id")
_resourceLong.ResourceID = field.NewInt32(tableName, "resource_id") _resourceLong.ResourceID = field.NewInt32(tableName, "resource_id")
_resourceLong.Type = field.NewInt(tableName, "type")
_resourceLong.Live = field.NewInt32(tableName, "live") _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.Quota = field.NewInt32(tableName, "quota")
_resourceLong.ExpireAt = field.NewTime(tableName, "expire_at")
_resourceLong.Used = field.NewInt32(tableName, "used") _resourceLong.Used = field.NewInt32(tableName, "used")
_resourceLong.DailyLimit = field.NewInt32(tableName, "daily_limit") _resourceLong.Daily = field.NewInt32(tableName, "daily")
_resourceLong.DailyUsed = field.NewInt32(tableName, "daily_used") _resourceLong.LastAt = field.NewTime(tableName, "last_at")
_resourceLong.DailyLast = field.NewTime(tableName, "daily_last")
_resourceLong.fillFieldMap() _resourceLong.fillFieldMap()
@@ -49,14 +48,13 @@ type resourceLong struct {
ALL field.Asterisk ALL field.Asterisk
ID field.Int32 ID field.Int32
ResourceID field.Int32 ResourceID field.Int32
Type field.Int
Live field.Int32 Live field.Int32
Expire field.Time Type field.Int
Quota field.Int32 Quota field.Int32
ExpireAt field.Time
Used field.Int32 Used field.Int32
DailyLimit field.Int32 Daily field.Int32
DailyUsed field.Int32 LastAt field.Time
DailyLast field.Time
fieldMap map[string]field.Expr fieldMap map[string]field.Expr
} }
@@ -75,14 +73,13 @@ func (r *resourceLong) updateTableName(table string) *resourceLong {
r.ALL = field.NewAsterisk(table) r.ALL = field.NewAsterisk(table)
r.ID = field.NewInt32(table, "id") r.ID = field.NewInt32(table, "id")
r.ResourceID = field.NewInt32(table, "resource_id") r.ResourceID = field.NewInt32(table, "resource_id")
r.Type = field.NewInt(table, "type")
r.Live = field.NewInt32(table, "live") 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.Quota = field.NewInt32(table, "quota")
r.ExpireAt = field.NewTime(table, "expire_at")
r.Used = field.NewInt32(table, "used") r.Used = field.NewInt32(table, "used")
r.DailyLimit = field.NewInt32(table, "daily_limit") r.Daily = field.NewInt32(table, "daily")
r.DailyUsed = field.NewInt32(table, "daily_used") r.LastAt = field.NewTime(table, "last_at")
r.DailyLast = field.NewTime(table, "daily_last")
r.fillFieldMap() r.fillFieldMap()
@@ -99,17 +96,16 @@ func (r *resourceLong) GetFieldByName(fieldName string) (field.OrderExpr, bool)
} }
func (r *resourceLong) fillFieldMap() { 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["id"] = r.ID
r.fieldMap["resource_id"] = r.ResourceID r.fieldMap["resource_id"] = r.ResourceID
r.fieldMap["type"] = r.Type
r.fieldMap["live"] = r.Live r.fieldMap["live"] = r.Live
r.fieldMap["expire"] = r.Expire r.fieldMap["type"] = r.Type
r.fieldMap["quota"] = r.Quota r.fieldMap["quota"] = r.Quota
r.fieldMap["expire_at"] = r.ExpireAt
r.fieldMap["used"] = r.Used r.fieldMap["used"] = r.Used
r.fieldMap["daily_limit"] = r.DailyLimit r.fieldMap["daily"] = r.Daily
r.fieldMap["daily_used"] = r.DailyUsed r.fieldMap["last_at"] = r.LastAt
r.fieldMap["daily_last"] = r.DailyLast
} }
func (r resourceLong) clone(db *gorm.DB) resourceLong { func (r resourceLong) clone(db *gorm.DB) resourceLong {

View File

@@ -29,14 +29,13 @@ func newResourceShort(db *gorm.DB, opts ...gen.DOOption) resourceShort {
_resourceShort.ALL = field.NewAsterisk(tableName) _resourceShort.ALL = field.NewAsterisk(tableName)
_resourceShort.ID = field.NewInt32(tableName, "id") _resourceShort.ID = field.NewInt32(tableName, "id")
_resourceShort.ResourceID = field.NewInt32(tableName, "resource_id") _resourceShort.ResourceID = field.NewInt32(tableName, "resource_id")
_resourceShort.Type = field.NewInt(tableName, "type")
_resourceShort.Live = field.NewInt32(tableName, "live") _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.Quota = field.NewInt32(tableName, "quota")
_resourceShort.ExpireAt = field.NewTime(tableName, "expire_at")
_resourceShort.Used = field.NewInt32(tableName, "used") _resourceShort.Used = field.NewInt32(tableName, "used")
_resourceShort.DailyLimit = field.NewInt32(tableName, "daily_limit") _resourceShort.Daily = field.NewInt32(tableName, "daily")
_resourceShort.DailyUsed = field.NewInt32(tableName, "daily_used") _resourceShort.LastAt = field.NewTime(tableName, "last_at")
_resourceShort.DailyLast = field.NewTime(tableName, "daily_last")
_resourceShort.fillFieldMap() _resourceShort.fillFieldMap()
@@ -49,14 +48,13 @@ type resourceShort struct {
ALL field.Asterisk ALL field.Asterisk
ID field.Int32 ID field.Int32
ResourceID field.Int32 ResourceID field.Int32
Type field.Int
Live field.Int32 Live field.Int32
Expire field.Time Type field.Int
Quota field.Int32 Quota field.Int32
ExpireAt field.Time
Used field.Int32 Used field.Int32
DailyLimit field.Int32 Daily field.Int32
DailyUsed field.Int32 LastAt field.Time
DailyLast field.Time
fieldMap map[string]field.Expr fieldMap map[string]field.Expr
} }
@@ -75,14 +73,13 @@ func (r *resourceShort) updateTableName(table string) *resourceShort {
r.ALL = field.NewAsterisk(table) r.ALL = field.NewAsterisk(table)
r.ID = field.NewInt32(table, "id") r.ID = field.NewInt32(table, "id")
r.ResourceID = field.NewInt32(table, "resource_id") r.ResourceID = field.NewInt32(table, "resource_id")
r.Type = field.NewInt(table, "type")
r.Live = field.NewInt32(table, "live") 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.Quota = field.NewInt32(table, "quota")
r.ExpireAt = field.NewTime(table, "expire_at")
r.Used = field.NewInt32(table, "used") r.Used = field.NewInt32(table, "used")
r.DailyLimit = field.NewInt32(table, "daily_limit") r.Daily = field.NewInt32(table, "daily")
r.DailyUsed = field.NewInt32(table, "daily_used") r.LastAt = field.NewTime(table, "last_at")
r.DailyLast = field.NewTime(table, "daily_last")
r.fillFieldMap() r.fillFieldMap()
@@ -99,17 +96,16 @@ func (r *resourceShort) GetFieldByName(fieldName string) (field.OrderExpr, bool)
} }
func (r *resourceShort) fillFieldMap() { 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["id"] = r.ID
r.fieldMap["resource_id"] = r.ResourceID r.fieldMap["resource_id"] = r.ResourceID
r.fieldMap["type"] = r.Type
r.fieldMap["live"] = r.Live r.fieldMap["live"] = r.Live
r.fieldMap["expire"] = r.Expire r.fieldMap["type"] = r.Type
r.fieldMap["quota"] = r.Quota r.fieldMap["quota"] = r.Quota
r.fieldMap["expire_at"] = r.ExpireAt
r.fieldMap["used"] = r.Used r.fieldMap["used"] = r.Used
r.fieldMap["daily_limit"] = r.DailyLimit r.fieldMap["daily"] = r.Daily
r.fieldMap["daily_used"] = r.DailyUsed r.fieldMap["last_at"] = r.LastAt
r.fieldMap["daily_last"] = r.DailyLast
} }
func (r resourceShort) clone(db *gorm.DB) resourceShort { func (r resourceShort) clone(db *gorm.DB) resourceShort {

View File

@@ -2,9 +2,11 @@ package services
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"math/rand/v2" "math/rand/v2"
"net/netip" "net/netip"
"platform/pkg/u"
"platform/web/core" "platform/web/core"
g "platform/web/globals" g "platform/web/globals"
m "platform/web/models" 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. resource, err := q.Resource.
Preload(field.Associations). Preload(field.Associations).
Where( Where(
@@ -82,57 +84,43 @@ func findResource(resourceId int32) (*ResourceView, error) {
} }
var info = &ResourceView{ var info = &ResourceView{
Id: resource.ID, Id: resource.ID,
User: *resource.User,
Active: resource.Active, Active: resource.Active,
Type: resource.Type, Type: resource.Type,
User: *resource.User,
} }
switch resource.Type { switch resource.Type {
case m.ResourceTypeShort: case m.ResourceTypeShort:
var sub = resource.Short var sub = resource.Short
var dailyLast = time.Time{} info.ShortId = &sub.ID
if sub.DailyLast != nil { info.ExpireAt = sub.ExpireAt
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.Live = time.Duration(sub.Live) * time.Second info.Live = time.Duration(sub.Live) * time.Second
info.DailyLimit = sub.DailyLimit info.Mode = sub.Type
info.DailyUsed = sub.DailyUsed info.Quota = sub.Quota
info.DailyLast = dailyLast
info.Expire = expire
info.Quota = quota
info.Used = sub.Used 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: case m.ResourceTypeLong:
var sub = resource.Long var sub = resource.Long
var dailyLast = time.Time{} info.LongId = &sub.ID
if sub.DailyLast != nil { info.ExpireAt = sub.ExpireAt
dailyLast = time.Time(*sub.DailyLast) info.Live = time.Duration(sub.Live) * time.Hour
}
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.Mode = sub.Type
info.Live = time.Duration(sub.Live) * time.Hour * 24 info.Quota = sub.Quota
info.DailyLimit = sub.DailyLimit
info.DailyUsed = sub.DailyUsed
info.DailyLast = dailyLast
info.Expire = expire
info.Quota = quota
info.Used = sub.Used 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 return info, nil
@@ -140,18 +128,20 @@ func findResource(resourceId int32) (*ResourceView, error) {
// ResourceView 套餐数据的简化视图,便于直接获取主要数据 // ResourceView 套餐数据的简化视图,便于直接获取主要数据
type ResourceView struct { type ResourceView struct {
Id int32 Id int32
Active bool User m.User
Type m.ResourceType Active bool
Mode m.ResourceMode Type m.ResourceType
Live time.Duration ShortId *int32
DailyLimit int32 LongId *int32
DailyUsed int32 Live time.Duration
DailyLast time.Time Mode m.ResourceMode
Quota int32 Quota int32
Used int32 ExpireAt *time.Time
Expire time.Time Used int32
User m.User 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -200,16 +190,11 @@ func ensure(now time.Time, source netip.Addr, resourceId int32, count int) (*Res
// 包时 // 包时
case m.ResourceModeTime: case m.ResourceModeTime:
// 检查过期时间 // 检查过期时间
if resource.Expire.Before(now) { if resource.ExpireAt.Before(now) {
return nil, nil, ErrResourceExpired return nil, nil, ErrResourceExpired
} }
// 检查每日限额 // 检查每日限额
used := 0 if count+resource.Today > int(resource.Quota) {
if now.Format("2006-01-02") == resource.DailyLast.Format("2006-01-02") {
used = int(resource.DailyUsed)
}
excess := used+count > int(resource.DailyLimit)
if excess {
return nil, nil, ErrResourceDailyLimit return nil, nil, ErrResourceDailyLimit
} }

View File

@@ -17,6 +17,7 @@ import (
"time" "time"
"github.com/hibiken/asynq" "github.com/hibiken/asynq"
"gorm.io/gen"
"gorm.io/gen/field" "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 { err = q.Q.Transaction(func(q *q.Query) error {
var rs gen.ResultInfo
// 更新套餐用量 // 根据套餐类型和模式更新使用记录
used := int32(count) isShortType := resource.Type == m.ResourceTypeShort
if u.IsSameDate(now, resource.DailyLast) { isLongType := resource.Type == m.ResourceTypeLong
used += resource.DailyUsed
}
switch resource.Type { switch {
case m.ResourceTypeShort: case isShortType:
_, err = q.ResourceShort. rs, err = q.ResourceShort.Debug().
Where( Where(
q.ResourceShort.ResourceID.Eq(resource.Id), q.ResourceShort.ID.Eq(*resource.ShortId),
q.ResourceShort.Used.Eq(resource.Used), q.ResourceShort.Used.Eq(resource.Used),
q.ResourceShort.DailyUsed.Eq(resource.DailyUsed), q.ResourceShort.Daily.Eq(resource.Daily),
q.ResourceShort.DailyLast.Eq(resource.DailyLast),
). ).
UpdateSimple( UpdateSimple(
q.ResourceShort.Used.Add(int32(count)), q.ResourceShort.Used.Add(int32(count)),
q.ResourceShort.DailyUsed.Value(used), q.ResourceShort.Daily.Value(int32(resource.Today+count)),
q.ResourceShort.DailyLast.Value(now), q.ResourceShort.LastAt.Value(now),
) )
case m.ResourceTypeLong:
_, err = q.ResourceLong. case isLongType:
rs, err = q.ResourceLong.Debug().
Where( Where(
q.ResourceLong.ResourceID.Eq(resource.Id), q.ResourceLong.ID.Eq(*resource.LongId),
q.ResourceLong.Used.Eq(resource.Used), q.ResourceLong.Used.Eq(resource.Used),
q.ResourceLong.DailyUsed.Eq(resource.DailyUsed), q.ResourceLong.Daily.Eq(resource.Daily),
q.ResourceLong.DailyLast.Eq(resource.DailyLast),
). ).
UpdateSimple( UpdateSimple(
q.ResourceLong.Used.Add(int32(count)), q.ResourceLong.Used.Add(int32(count)),
q.ResourceLong.DailyUsed.Value(used), q.ResourceLong.Daily.Value(int32(resource.Today+count)),
q.ResourceLong.DailyLast.Value(now), q.ResourceLong.LastAt.Value(now),
) )
default:
return core.NewServErr("套餐类型不正确,无法更新", nil)
} }
if err != nil { if err != nil {
return core.NewServErr("更新套餐使用记录失败", err) return core.NewServErr("更新套餐使用记录失败", err)
} }
if rs.RowsAffected == 0 {
return core.NewServErr("套餐使用记录不存在")
}
// 保存通道 // 保存通道
err = q.Channel. err = q.Channel.

View File

@@ -2,6 +2,7 @@ package services
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"platform/pkg/u" "platform/pkg/u"
"platform/web/core" "platform/web/core"
@@ -29,15 +30,19 @@ func (s *resourceService) CreateResourceByBalance(uid int32, now time.Time, data
} }
// 检查余额 // 检查余额
var amount = user.Balance.Sub(data.GetAmount()) amount, err := data.GetAmount()
if amount.IsNegative() { if err != nil {
return err
}
balance := user.Balance.Sub(amount)
if balance.IsNegative() {
return ErrBalanceNotEnough return ErrBalanceNotEnough
} }
// 更新用户余额 // 更新用户余额
_, err = q.User. _, err = q.User.
Where(q.User.ID.Eq(uid), q.User.Balance.Eq(user.Balance)). 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 { if err != nil {
return core.NewServErr("更新用户余额失败", err) 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 { if err != nil {
return core.NewServErr("生成账单失败", err) 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 { func (s *resourceService) CreateResourceByTrade(uid int32, now time.Time, data *CreateResourceData, trade *m.Trade) error {
return q.Q.Transaction(func(q *q.Query) 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) 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 { if err != nil {
return core.NewServErr("生成账单失败", err) return core.NewServErr("生成账单失败", err)
} }
@@ -95,13 +119,17 @@ func createResource(q *q.Query, uid int32, now time.Time, data *CreateResourceDa
if short == nil { if short == nil {
return nil, core.NewBizErr("短效套餐数据不能为空") return nil, core.NewBizErr("短效套餐数据不能为空")
} }
var duration = time.Duration(short.Expire) * 24 * time.Hour
resource.Short = &m.ResourceShort{ resource.Short = &m.ResourceShort{
Type: short.Mode, Live: short.Live,
Live: short.Live, Type: short.Mode,
Quota: &short.Quota, Quota: short.Quota,
Expire: u.P(now.Add(duration)), }
DailyLimit: short.DailyLimit, 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 { if long == nil {
return nil, core.NewBizErr("长效套餐数据不能为空") return nil, core.NewBizErr("长效套餐数据不能为空")
} }
var duration = time.Duration(long.Expire) * 24 * time.Hour
resource.Long = &m.ResourceLong{ resource.Long = &m.ResourceLong{
Type: long.Mode, Live: long.Live,
Live: long.Live, Type: long.Mode,
Quota: &long.Quota, Quota: long.Quota,
Expire: u.P(now.Add(duration)), }
DailyLimit: long.DailyLimit, 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: default:
return nil, core.NewBizErr("不支持的套餐类型") return nil, core.NewBizErr("不支持的套餐类型")
@@ -137,22 +169,20 @@ type CreateResourceData struct {
} }
type CreateShortResourceData struct { type CreateShortResourceData struct {
Live int32 `json:"live" validate:"required,min=180"` Live int32 `json:"live" validate:"required,min=180"`
Mode m.ResourceMode `json:"mode" validate:"required"` Mode m.ResourceMode `json:"mode" validate:"required"`
Expire int32 `json:"expire"` Quota int32 `json:"quota"`
DailyLimit int32 `json:"daily_limit" validate:"min=2000"` Expire *int32 `json:"expire"`
Quota int32 `json:"quota" validate:"min=10000"`
name string name string
price *decimal.Decimal price *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"`
Mode m.ResourceMode `json:"mode" validate:"required,oneof=1 2"` Mode m.ResourceMode `json:"mode" validate:"required"`
Expire int32 `json:"expire"` Quota int32 `json:"quota" validate:"required"`
DailyLimit int32 `json:"daily_limit" validate:"min=100"` Expire *int32 `json:"expire" validate:"required"`
Quota int32 `json:"quota" validate:"min=500"`
name string name string
price *decimal.Decimal price *decimal.Decimal
@@ -162,24 +192,28 @@ func (c *CreateResourceData) GetType() m.TradeType {
return m.TradeTypePurchase return m.TradeTypePurchase
} }
func (c *CreateResourceData) GetSubject() string { func (c *CreateResourceData) GetSubject() (string, error) {
switch c.Type { switch c.Type {
default:
return "", errors.New("无效的套餐类型")
case m.ResourceTypeShort: case m.ResourceTypeShort:
return c.Short.GetSubject() return c.Short.GetSubject()
case m.ResourceTypeLong: case m.ResourceTypeLong:
return c.Long.GetSubject() return c.Long.GetSubject()
} }
panic("类型对应的数据为空")
} }
func (c *CreateResourceData) GetAmount() decimal.Decimal { func (c *CreateResourceData) GetAmount() (decimal.Decimal, error) {
switch c.Type { switch c.Type {
default:
return decimal.Zero, errors.New("无效的套餐类型")
case m.ResourceTypeShort: case m.ResourceTypeShort:
return c.Short.GetAmount() return c.Short.GetAmount()
case m.ResourceTypeLong: case m.ResourceTypeLong:
return c.Long.GetAmount() return c.Long.GetAmount()
} }
panic("类型对应的数据为空")
} }
func (c *CreateResourceData) Serialize() (string, error) { func (c *CreateResourceData) Serialize() (string, error) {
@@ -191,27 +225,37 @@ func (c *CreateResourceData) Deserialize(str string) error {
return json.Unmarshal([]byte(str), c) return json.Unmarshal([]byte(str), c)
} }
func (data *CreateShortResourceData) GetSubject() string { func (data *CreateShortResourceData) GetSubject() (string, error) {
if data.name == "" { if data.name == "" {
var mode string var mode string
switch data.Mode { switch data.Mode {
case 1: default:
return "", errors.New("无效的套餐模式")
case m.ResourceModeTime:
mode = "包时" mode = "包时"
case 2: case m.ResourceModeQuota:
mode = "包量" mode = "包量"
} }
data.name = fmt.Sprintf("短效动态%s %v 分钟", mode, data.Live/60) 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 { if data.price == nil {
var factor int32 var factor int32
switch data.Mode { switch data.Mode {
case 1: default:
factor = data.DailyLimit * data.Expire return decimal.Zero, errors.New("无效的套餐模式")
case 2:
case m.ResourceModeTime:
if data.Expire == nil {
return decimal.Zero, errors.New("包时套餐过期时间不能为空")
}
factor = data.Quota * *data.Expire
case m.ResourceModeQuota:
factor = data.Quota factor = data.Quota
} }
@@ -223,38 +267,53 @@ func (data *CreateShortResourceData) GetAmount() decimal.Decimal {
var dec = decimal.Decimal{}. var dec = decimal.Decimal{}.
Add(decimal.NewFromInt32(base * factor)). Add(decimal.NewFromInt32(base * factor)).
Div(decimal.NewFromInt(30000)) Div(decimal.NewFromInt(30000))
if dec.IsZero() {
return decimal.Zero, errors.New("计算金额错误")
}
data.price = &dec data.price = &dec
} }
return *data.price return *data.price, nil
} }
func (data *CreateLongResourceData) GetSubject() string { func (data *CreateLongResourceData) GetSubject() (string, error) {
if data.name == "" { if data.name == "" {
var mode string var mode string
switch data.Mode { switch data.Mode {
case 1: default:
return "", errors.New("无效的套餐模式")
case m.ResourceModeTime:
mode = "包时" mode = "包时"
case 2: case m.ResourceModeQuota:
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, nil
} }
func (data *CreateLongResourceData) GetAmount() decimal.Decimal { func (data *CreateLongResourceData) GetAmount() (decimal.Decimal, error) {
if data.price == nil { if data.price == nil {
var factor int32 = 0 var factor int32 = 0
switch data.Mode { switch data.Mode {
default:
return decimal.Zero, errors.New("无效的套餐模式")
case m.ResourceModeTime: 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: case m.ResourceModeQuota:
factor = data.Quota factor = data.Quota
} }
var base int32 var base int32
switch data.Live { switch data.Live {
default:
return decimal.Zero, errors.New("无效的套餐时长")
case 1: case 1:
base = 30 base = 30
case 4: case 4:
@@ -271,9 +330,13 @@ func (data *CreateLongResourceData) GetAmount() decimal.Decimal {
var dec = decimal.Decimal{}. var dec = decimal.Decimal{}.
Add(decimal.NewFromInt32(base * factor)). Add(decimal.NewFromInt32(base * factor)).
Div(decimal.NewFromInt(100)) Div(decimal.NewFromInt(100))
if dec.IsZero() {
return decimal.Zero, errors.New("计算金额错误")
}
data.price = &dec data.price = &dec
} }
return *data.price return *data.price, nil
} }
type ResourceOnTradeComplete struct{} type ResourceOnTradeComplete struct{}

View File

@@ -35,12 +35,18 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa
platform := data.Platform platform := data.Platform
method := data.Method method := data.Method
tType := data.Product.GetType() tType := data.Product.GetType()
subject := data.Product.GetSubject()
amount := data.Product.GetAmount()
expire := time.Now().Add(30 * time.Minute) 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 { if env.RunMode == env.RunModeDev {
amountReal = decimal.NewFromFloat(0.01) amountReal = decimal.NewFromFloat(0.01)
} }
@@ -60,7 +66,7 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa
return nil, err return nil, err
} }
var expireAt = time.Time(u.Z(coupon.ExpireAt)) expireAt := time.Time(u.Z(coupon.ExpireAt))
if !expireAt.IsZero() && expireAt.Before(now) { if !expireAt.IsZero() && expireAt.Before(now) {
_, err = q.Coupon. _, err = q.Coupon.
Where(q.Coupon.ID.Eq(coupon.ID)). 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 { if err != nil {
return nil, core.NewServErr("生成订单号失败", err) return nil, core.NewServErr("生成订单号失败", err)
} }
@@ -692,8 +698,8 @@ type OnTradeCompletedData struct {
type ProductInfo interface { type ProductInfo interface {
GetType() m.TradeType GetType() m.TradeType
GetSubject() string GetSubject() (string, error)
GetAmount() decimal.Decimal GetAmount() (decimal.Decimal, error)
Serialize() (string, error) Serialize() (string, error)
Deserialize(str string) error Deserialize(str string) error
} }

View File

@@ -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 { if err != nil {
return core.NewServErr("生成账单失败", err) return core.NewServErr("生成账单失败", err)
} }
@@ -39,23 +47,25 @@ func (s *userService) UpdateBalanceByTrade(uid int32, info *RechargeProductInfo,
return nil 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. user, err := q.User.
Where(q.User.ID.Eq(uid)).Take() Where(q.User.ID.Eq(uid)).Take()
if err != nil { if err != nil {
return core.NewServErr("查询用户失败", err) return core.NewServErr("查询用户失败", err)
} }
var amount = user.Balance.Add(info.GetAmount()) amount, err := info.GetAmount()
if amount.IsNegative() { if err != nil {
return err
}
balance := user.Balance.Add(amount)
if balance.IsNegative() {
return core.NewServErr("用户余额不足") return core.NewServErr("用户余额不足")
} }
_, err = q.User. _, err = q.User.
Where(q.User.ID.Eq(user.ID)). Where(q.User.ID.Eq(user.ID)).
UpdateSimple(q.User.Balance.Value(amount)) UpdateSimple(q.User.Balance.Value(balance))
if err != nil { if err != nil {
return core.NewServErr("更新用户余额失败", err) return core.NewServErr("更新用户余额失败", err)
} }
@@ -75,12 +85,13 @@ func (r *RechargeProductInfo) GetType() m.TradeType {
return m.TradeTypeRecharge return m.TradeTypeRecharge
} }
func (r *RechargeProductInfo) GetSubject() string { func (r *RechargeProductInfo) GetSubject() (string, error) {
return fmt.Sprintf("账户充值 - %s元", r.GetAmount().StringFixed(2)) amount, _ := r.GetAmount()
return fmt.Sprintf("账户充值 - %s元", amount.StringFixed(2)), nil
} }
func (r *RechargeProductInfo) GetAmount() decimal.Decimal { func (r *RechargeProductInfo) GetAmount() (decimal.Decimal, error) {
return decimal.NewFromInt(int64(r.Amount)).Div(decimal.NewFromInt(100)) return decimal.NewFromInt(int64(r.Amount)).Div(decimal.NewFromInt(100)), nil
} }
func (r *RechargeProductInfo) Serialize() (string, error) { func (r *RechargeProductInfo) Serialize() (string, error) {