2 Commits
v1.6.0 ... main

12 changed files with 61 additions and 18 deletions

View File

@@ -762,6 +762,7 @@ create table product_sku (
price_min decimal not null, price_min decimal not null,
status int not null default 1, status int not null default 1,
sort int not null default 0, sort int not null default 0,
count_min int not null default 1,
created_at timestamptz default current_timestamp, created_at timestamptz default current_timestamp,
updated_at timestamptz default current_timestamp, updated_at timestamptz default current_timestamp,
deleted_at timestamptz deleted_at timestamptz
@@ -780,6 +781,7 @@ comment on column product_sku.name is 'SKU 可读名称';
comment on column product_sku.price_min is '最低价格'; comment on column product_sku.price_min is '最低价格';
comment on column product_sku.status is 'SKU状态0-禁用1-正常'; comment on column product_sku.status is 'SKU状态0-禁用1-正常';
comment on column product_sku.sort is '排序'; comment on column product_sku.sort is '排序';
comment on column product_sku.count_min is '最小购买数量';
comment on column product_sku.created_at is '创建时间'; comment on column product_sku.created_at is '创建时间';
comment on column product_sku.updated_at is '更新时间'; comment on column product_sku.updated_at is '更新时间';
comment on column product_sku.deleted_at is '删除时间'; comment on column product_sku.deleted_at is '删除时间';
@@ -816,6 +818,7 @@ create table resource (
code text, code text,
type int not null, type int not null,
active bool not null default true, active bool not null default true,
checkip bool not null default true,
created_at timestamptz default current_timestamp, created_at timestamptz default current_timestamp,
updated_at timestamptz default current_timestamp, updated_at timestamptz default current_timestamp,
deleted_at timestamptz deleted_at timestamptz
@@ -830,9 +833,10 @@ comment on table resource is '套餐表';
comment on column resource.id is '套餐ID'; comment on column resource.id is '套餐ID';
comment on column resource.user_id is '用户ID'; comment on column resource.user_id is '用户ID';
comment on column resource.resource_no is '套餐编号'; comment on column resource.resource_no is '套餐编号';
comment on column resource.active is '套餐状态';
comment on column resource.type is '套餐类型1-短效动态2-长效动态';
comment on column resource.code is '产品编码'; comment on column resource.code is '产品编码';
comment on column resource.type is '套餐类型1-短效动态2-长效动态';
comment on column resource.active is '套餐状态';
comment on column resource.checkip is '提取时是否检查 ip 地址';
comment on column resource.created_at is '创建时间'; comment on column resource.created_at is '创建时间';
comment on column resource.updated_at is '更新时间'; comment on column resource.updated_at is '更新时间';
comment on column resource.deleted_at is '删除时间'; comment on column resource.deleted_at is '删除时间';

View File

@@ -182,6 +182,9 @@ func TradeCreate(c *fiber.Ctx) error {
if err != nil { if err != nil {
return err return err
} }
if authCtx.User.IDType == m.UserIDTypeUnverified {
return core.NewBizErr("请先实名认证后再购买")
}
// 解析请求参数 // 解析请求参数
req := new(TradeCreateReq) req := new(TradeCreateReq)

View File

@@ -17,6 +17,7 @@ type ProductSku struct {
PriceMin decimal.Decimal `json:"price_min" gorm:"column:price_min"` // 最低价格 PriceMin decimal.Decimal `json:"price_min" gorm:"column:price_min"` // 最低价格
Status SkuStatus `json:"status" gorm:"column:status"` // SKU 状态0-禁用1-正常 Status SkuStatus `json:"status" gorm:"column:status"` // SKU 状态0-禁用1-正常
Sort int32 `json:"sort" gorm:"column:sort"` // 排序 Sort int32 `json:"sort" gorm:"column:sort"` // 排序
CountMin int32 `json:"count_min" gorm:"column:count_min"` // 最小购买数量
Product *Product `json:"product,omitempty" gorm:"foreignKey:ProductID"` Product *Product `json:"product,omitempty" gorm:"foreignKey:ProductID"`
Discount *ProductDiscount `json:"discount,omitempty" gorm:"foreignKey:DiscountId"` Discount *ProductDiscount `json:"discount,omitempty" gorm:"foreignKey:DiscountId"`

View File

@@ -12,6 +12,7 @@ type Resource struct {
Active bool `json:"active" gorm:"column:active"` // 套餐状态 Active bool `json:"active" gorm:"column:active"` // 套餐状态
Type ResourceType `json:"type" gorm:"column:type"` // 套餐类型1-短效动态2-长效动态 Type ResourceType `json:"type" gorm:"column:type"` // 套餐类型1-短效动态2-长效动态
Code string `json:"code" gorm:"column:code"` // 产品编码 Code string `json:"code" gorm:"column:code"` // 产品编码
CheckIP bool `json:"checkip" gorm:"column:checkip"` // 是否检查IP
User *User `json:"user,omitempty" gorm:"foreignKey:UserID"` User *User `json:"user,omitempty" gorm:"foreignKey:UserID"`
Short *ResourceShort `json:"short,omitempty" gorm:"foreignKey:ResourceID"` Short *ResourceShort `json:"short,omitempty" gorm:"foreignKey:ResourceID"`

View File

@@ -39,6 +39,7 @@ func newProductSku(db *gorm.DB, opts ...gen.DOOption) productSku {
_productSku.PriceMin = field.NewField(tableName, "price_min") _productSku.PriceMin = field.NewField(tableName, "price_min")
_productSku.Status = field.NewInt32(tableName, "status") _productSku.Status = field.NewInt32(tableName, "status")
_productSku.Sort = field.NewInt32(tableName, "sort") _productSku.Sort = field.NewInt32(tableName, "sort")
_productSku.CountMin = field.NewInt32(tableName, "count_min")
_productSku.Product = productSkuBelongsToProduct{ _productSku.Product = productSkuBelongsToProduct{
db: db.Session(&gorm.Session{}), db: db.Session(&gorm.Session{}),
@@ -93,6 +94,7 @@ type productSku struct {
PriceMin field.Field PriceMin field.Field
Status field.Int32 Status field.Int32
Sort field.Int32 Sort field.Int32
CountMin field.Int32
Product productSkuBelongsToProduct Product productSkuBelongsToProduct
Discount productSkuBelongsToDiscount Discount productSkuBelongsToDiscount
@@ -124,6 +126,7 @@ func (p *productSku) updateTableName(table string) *productSku {
p.PriceMin = field.NewField(table, "price_min") p.PriceMin = field.NewField(table, "price_min")
p.Status = field.NewInt32(table, "status") p.Status = field.NewInt32(table, "status")
p.Sort = field.NewInt32(table, "sort") p.Sort = field.NewInt32(table, "sort")
p.CountMin = field.NewInt32(table, "count_min")
p.fillFieldMap() p.fillFieldMap()
@@ -140,7 +143,7 @@ func (p *productSku) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
} }
func (p *productSku) fillFieldMap() { func (p *productSku) fillFieldMap() {
p.fieldMap = make(map[string]field.Expr, 14) p.fieldMap = make(map[string]field.Expr, 15)
p.fieldMap["id"] = p.ID p.fieldMap["id"] = p.ID
p.fieldMap["created_at"] = p.CreatedAt p.fieldMap["created_at"] = p.CreatedAt
p.fieldMap["updated_at"] = p.UpdatedAt p.fieldMap["updated_at"] = p.UpdatedAt
@@ -153,6 +156,7 @@ func (p *productSku) fillFieldMap() {
p.fieldMap["price_min"] = p.PriceMin p.fieldMap["price_min"] = p.PriceMin
p.fieldMap["status"] = p.Status p.fieldMap["status"] = p.Status
p.fieldMap["sort"] = p.Sort p.fieldMap["sort"] = p.Sort
p.fieldMap["count_min"] = p.CountMin
} }

View File

@@ -36,6 +36,7 @@ func newResource(db *gorm.DB, opts ...gen.DOOption) resource {
_resource.Active = field.NewBool(tableName, "active") _resource.Active = field.NewBool(tableName, "active")
_resource.Type = field.NewInt(tableName, "type") _resource.Type = field.NewInt(tableName, "type")
_resource.Code = field.NewString(tableName, "code") _resource.Code = field.NewString(tableName, "code")
_resource.CheckIP = field.NewBool(tableName, "checkip")
_resource.Short = resourceHasOneShort{ _resource.Short = resourceHasOneShort{
db: db.Session(&gorm.Session{}), db: db.Session(&gorm.Session{}),
@@ -185,6 +186,7 @@ type resource struct {
Active field.Bool Active field.Bool
Type field.Int Type field.Int
Code field.String Code field.String
CheckIP field.Bool
Short resourceHasOneShort Short resourceHasOneShort
Long resourceHasOneLong Long resourceHasOneLong
@@ -217,6 +219,7 @@ func (r *resource) updateTableName(table string) *resource {
r.Active = field.NewBool(table, "active") r.Active = field.NewBool(table, "active")
r.Type = field.NewInt(table, "type") r.Type = field.NewInt(table, "type")
r.Code = field.NewString(table, "code") r.Code = field.NewString(table, "code")
r.CheckIP = field.NewBool(table, "checkip")
r.fillFieldMap() r.fillFieldMap()
@@ -233,7 +236,7 @@ func (r *resource) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
} }
func (r *resource) fillFieldMap() { func (r *resource) fillFieldMap() {
r.fieldMap = make(map[string]field.Expr, 13) r.fieldMap = make(map[string]field.Expr, 14)
r.fieldMap["id"] = r.ID r.fieldMap["id"] = r.ID
r.fieldMap["created_at"] = r.CreatedAt r.fieldMap["created_at"] = r.CreatedAt
r.fieldMap["updated_at"] = r.UpdatedAt r.fieldMap["updated_at"] = r.UpdatedAt
@@ -243,6 +246,7 @@ func (r *resource) fillFieldMap() {
r.fieldMap["active"] = r.Active r.fieldMap["active"] = r.Active
r.fieldMap["type"] = r.Type r.fieldMap["type"] = r.Type
r.fieldMap["code"] = r.Code r.fieldMap["code"] = r.Code
r.fieldMap["checkip"] = r.CheckIP
} }

View File

@@ -87,6 +87,7 @@ func findResource(resourceId int32, now time.Time) (*ResourceView, error) {
User: *resource.User, User: *resource.User,
Active: resource.Active, Active: resource.Active,
Type: resource.Type, Type: resource.Type,
CheckIP: resource.CheckIP,
} }
switch resource.Type { switch resource.Type {
@@ -142,6 +143,7 @@ type ResourceView struct {
Daily int32 Daily int32
LastAt *time.Time LastAt *time.Time
Today int // 今日用量 Today int // 今日用量
CheckIP bool
} }
// 检查用户是否可提取 // 检查用户是否可提取
@@ -178,7 +180,7 @@ func ensure(now time.Time, source netip.Addr, resourceId int32, count int) (*Res
pass = true pass = true
} }
} }
if !pass { if resource.CheckIP && !pass {
return nil, nil, core.NewBizErr(fmt.Sprintf("IP 地址 %s 不在白名单内", source.String())) return nil, nil, core.NewBizErr(fmt.Sprintf("IP 地址 %s 不在白名单内", source.String()))
} }

View File

@@ -50,6 +50,7 @@ func (s *productService) AllProductSaleInfos() ([]*m.Product, error) {
q.ProductSku.Name, q.ProductSku.Name,
q.ProductSku.Code, q.ProductSku.Code,
q.ProductSku.Price, q.ProductSku.Price,
q.ProductSku.CountMin,
). ).
Where( Where(
q.ProductSku.ProductID.In(pids...), q.ProductSku.ProductID.In(pids...),

View File

@@ -47,6 +47,11 @@ func (s *productSkuService) Create(create CreateProductSkuData) (err error) {
return core.NewBizErr("产品最低价格的格式不正确", err) return core.NewBizErr("产品最低价格的格式不正确", err)
} }
countMin := int32(1)
if create.CountMin != nil {
countMin = *create.CountMin
}
return q.ProductSku.Create(&m.ProductSku{ return q.ProductSku.Create(&m.ProductSku{
ProductID: create.ProductID, ProductID: create.ProductID,
DiscountId: create.DiscountID, DiscountId: create.DiscountID,
@@ -54,6 +59,8 @@ func (s *productSkuService) Create(create CreateProductSkuData) (err error) {
Name: create.Name, Name: create.Name,
Price: price, Price: price,
PriceMin: priceMin, PriceMin: priceMin,
Sort: create.Sort,
CountMin: countMin,
}) })
} }
@@ -64,6 +71,8 @@ type CreateProductSkuData struct {
Name string `json:"name"` Name string `json:"name"`
Price string `json:"price"` Price string `json:"price"`
PriceMin string `json:"price_min"` PriceMin string `json:"price_min"`
Sort int32 `json:"sort"`
CountMin *int32 `json:"count_min"`
} }
func (s *productSkuService) Update(update UpdateProductSkuData) (err error) { func (s *productSkuService) Update(update UpdateProductSkuData) (err error) {
@@ -95,6 +104,12 @@ func (s *productSkuService) Update(update UpdateProductSkuData) (err error) {
if update.Status != nil { if update.Status != nil {
do = append(do, q.ProductSku.Status.Value(*update.Status)) do = append(do, q.ProductSku.Status.Value(*update.Status))
} }
if update.Sort != nil {
do = append(do, q.ProductSku.Sort.Value(*update.Sort))
}
if update.CountMin != nil {
do = append(do, q.ProductSku.CountMin.Value(*update.CountMin))
}
_, err = q.ProductSku.Where(q.ProductSku.ID.Eq(update.ID)).UpdateSimple(do...) _, err = q.ProductSku.Where(q.ProductSku.ID.Eq(update.ID)).UpdateSimple(do...)
return err return err
@@ -108,6 +123,8 @@ type UpdateProductSkuData struct {
Price *string `json:"price"` Price *string `json:"price"`
PriceMin string `json:"price_min"` PriceMin string `json:"price_min"`
Status *int32 `json:"status"` Status *int32 `json:"status"`
Sort *int32 `json:"sort"`
CountMin *int32 `json:"count_min"`
} }
func (s *productSkuService) Delete(id int32) (err error) { func (s *productSkuService) Delete(id int32) (err error) {

View File

@@ -188,7 +188,7 @@ func (s *proxyService) Remove(id int32) error {
type UpdateProxyStatus struct { type UpdateProxyStatus struct {
ID int32 `json:"id" validate:"required"` ID int32 `json:"id" validate:"required"`
Status m.ProxyStatus `json:"status" validate:"required"` Status m.ProxyStatus `json:"status"`
} }
func (s *proxyService) UpdateStatus(update *UpdateProxyStatus) error { func (s *proxyService) UpdateStatus(update *UpdateProxyStatus) error {

View File

@@ -121,14 +121,13 @@ func (s *resourceService) Create(q *q.Query, uid int32, now time.Time, data *Cre
} }
func (s *resourceService) Update(data *UpdateResourceData) error { func (s *resourceService) Update(data *UpdateResourceData) error {
if data.Active == nil {
return core.NewBizErr("更新套餐失败active 不能为空")
}
do := make([]field.AssignExpr, 0) do := make([]field.AssignExpr, 0)
if data.Active != nil { if data.Active != nil {
do = append(do, q.Resource.Active.Value(*data.Active)) do = append(do, q.Resource.Active.Value(*data.Active))
} }
if data.CheckIP != nil {
do = append(do, q.Resource.CheckIP.Value(*data.CheckIP))
}
_, err := q.Resource. _, err := q.Resource.
Where(q.Resource.ID.Eq(data.Id)). Where(q.Resource.ID.Eq(data.Id)).
@@ -143,9 +142,13 @@ func (s *resourceService) Update(data *UpdateResourceData) error {
type UpdateResourceData struct { type UpdateResourceData struct {
core.IdReq core.IdReq
Active *bool `json:"active"` Active *bool `json:"active"`
CheckIP *bool `json:"checkip"`
} }
func (s *resourceService) CalcPrice(skuCode string, count int32, user *m.User, cuid *int32) (*m.ProductSku, *m.ProductDiscount, *m.CouponUser, decimal.Decimal, decimal.Decimal, decimal.Decimal, error) { func (s *resourceService) CalcPrice(skuCode string, count int32, user *m.User, cuid *int32) (*m.ProductSku, *m.ProductDiscount, *m.CouponUser, decimal.Decimal, decimal.Decimal, decimal.Decimal, error) {
if count <= 0 {
return nil, nil, nil, decimal.Zero, decimal.Zero, decimal.Zero, core.NewBizErr("购买数量必须大于 0")
}
sku, err := q.ProductSku. sku, err := q.ProductSku.
Joins(q.ProductSku.Discount). Joins(q.ProductSku.Discount).
@@ -153,7 +156,7 @@ func (s *resourceService) CalcPrice(skuCode string, count int32, user *m.User, c
Take() Take()
if err != nil { if err != nil {
slog.Debug("查询产品失败", "skuCode", skuCode) slog.Debug("查询产品失败", "skuCode", skuCode)
return nil, nil, nil, decimal.Zero, decimal.Zero, decimal.Zero, core.NewBizErr(fmt.Sprintf("产品不可用", skuCode), err) return nil, nil, nil, decimal.Zero, decimal.Zero, decimal.Zero, core.NewBizErr("产品不可用", err)
} }
// 原价 // 原价
@@ -163,7 +166,7 @@ func (s *resourceService) CalcPrice(skuCode string, count int32, user *m.User, c
// 折扣价 // 折扣价
discount := sku.Discount discount := sku.Discount
if discount == nil { if discount == nil {
return nil, nil, nil, decimal.Zero, decimal.Zero, decimal.Zero, core.NewServErr("价格查询失败", err) return nil, nil, nil, decimal.Zero, decimal.Zero, decimal.Zero, core.NewServErr("产品未配置折扣", err)
} }
discountRate := discount.Rate() discountRate := discount.Rate()
@@ -201,7 +204,7 @@ func (s *resourceService) CalcPrice(skuCode string, count int32, user *m.User, c
couponApplied = amountMin.Copy() couponApplied = amountMin.Copy()
} }
return sku, discount, coupon, amount, discounted, couponApplied, nil return sku, discount, coupon, amount.RoundCeil(2), discounted.RoundCeil(2), couponApplied.RoundCeil(2), nil
} }
type CreateResourceData struct { type CreateResourceData struct {

View File

@@ -88,6 +88,9 @@ type UpdateBalanceData struct {
} }
func (data *UpdateBalanceData) TradeDetail(user *m.User) (*TradeDetail, error) { func (data *UpdateBalanceData) TradeDetail(user *m.User) (*TradeDetail, error) {
if data.Amount <= 0 {
return nil, core.NewBizErr("充值金额必须大于0")
}
amount := decimal.NewFromInt(int64(data.Amount)).Div(decimal.NewFromInt(100)) amount := decimal.NewFromInt(int64(data.Amount)).Div(decimal.NewFromInt(100))
return &TradeDetail{ return &TradeDetail{
data, data,