gpt
This commit is contained in:
+104
-1
@@ -346,6 +346,80 @@ func (d *UserDailyUsageDao) UserDailyUsageIncrementClosetShare(ctx context.Conte
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserDailyUsageAdjust 原子调整每日使用量;正数使用 upsert,负数保证不低于 0。
|
||||
func (d *UserDailyUsageDao) UserDailyUsageAdjust(ctx context.Context, userID, statDate int64, quotaType int8, delta int64) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserDailyUsageAdjust")
|
||||
defer span.End()
|
||||
column := ""
|
||||
switch quotaType {
|
||||
case 2:
|
||||
column = "ai_recommend_count"
|
||||
case 3:
|
||||
column = "outfit_share_count"
|
||||
case 4:
|
||||
column = "closet_share_count"
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
if delta > 0 {
|
||||
usage := &mysqlmodel.UserDailyUsage{UserID: userID, StatDate: statDate}
|
||||
switch quotaType {
|
||||
case 2:
|
||||
usage.AIRecommendCount = delta
|
||||
case 3:
|
||||
usage.OutfitShareCount = delta
|
||||
case 4:
|
||||
usage.ClosetShareCount = delta
|
||||
}
|
||||
err = d.db.WithContext(ctx).Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "user_id"}, {Name: "stat_date"}},
|
||||
DoUpdates: clause.Assignments(map[string]any{column: gorm.Expr(column+" + ?", delta)}),
|
||||
}).Create(usage).Error
|
||||
} else if delta < 0 {
|
||||
err = d.db.WithContext(ctx).Model(&mysqlmodel.UserDailyUsage{}).
|
||||
Where("user_id = ? AND stat_date = ?", userID, statDate).
|
||||
UpdateColumn(column, gorm.Expr("GREATEST(0, "+column+" + ?)", delta)).Error
|
||||
}
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// UserDailyUsageConsume 以 MySQL 为权威账本原子预占每日额度,避免 Redis 丢键后重复消费。
|
||||
func (d *UserDailyUsageDao) UserDailyUsageConsume(ctx context.Context, userID, statDate int64, quotaType int8, n, limit int64) (bool, error) {
|
||||
column := ""
|
||||
switch quotaType {
|
||||
case 2:
|
||||
column = "ai_recommend_count"
|
||||
case 3:
|
||||
column = "outfit_share_count"
|
||||
case 4:
|
||||
column = "closet_share_count"
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
var consumed bool
|
||||
err := d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
usage := &mysqlmodel.UserDailyUsage{UserID: userID, StatDate: statDate}
|
||||
if err := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "user_id"}, {Name: "stat_date"}}, DoNothing: true,
|
||||
}).Create(usage).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
res := tx.Model(&mysqlmodel.UserDailyUsage{}).
|
||||
Where("user_id = ? AND stat_date = ? AND "+column+" + ? <= ?", userID, statDate, n, limit).
|
||||
UpdateColumn(column, gorm.Expr(column+" + ?", n))
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
consumed = res.RowsAffected > 0
|
||||
return nil
|
||||
})
|
||||
return consumed, err
|
||||
}
|
||||
|
||||
// UserDailyUsageGetByUserIDAndDate 查询用户某日使用计数
|
||||
func (d *UserDailyUsageDao) UserDailyUsageGetByUserIDAndDate(ctx context.Context, userID int64, statDate int64) (*mysqlmodel.UserDailyUsage, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserDailyUsageGetByUserIDAndDate")
|
||||
@@ -431,7 +505,7 @@ func NewQuotaGrantDao() *QuotaGrantDao {
|
||||
// QuotaGrantCreateWithFlow 事务内创建额度凭证并写发放流水(购买赠送/活动赠送)。
|
||||
// 返回值 created 表示本次是否新建了凭证:依赖 uk_grant_ref_type(ref_id, quota_type) 唯一索引实现幂等,
|
||||
// 凭证已存在时返回 (false, nil) 且不写流水,避免重复回调/补偿重试重复记账
|
||||
//(流水侧另有 (order_no, quota_type, change_type) 唯一索引兜底)。
|
||||
// (流水侧另有 (order_no, quota_type, change_type) 唯一索引兜底)。
|
||||
func (d *QuotaGrantDao) QuotaGrantCreateWithFlow(ctx context.Context, grant *mysqlmodel.QuotaGrant, flow *mysqlmodel.QuotaFlowLog) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaGrantCreateWithFlow")
|
||||
defer span.End()
|
||||
@@ -459,6 +533,35 @@ func (d *QuotaGrantDao) QuotaGrantCreateWithFlow(ctx context.Context, grant *mys
|
||||
return created, nil
|
||||
}
|
||||
|
||||
// AdRewardGrantCreateWithFlow 在用户行锁内执行每日限额、事件幂等、凭证与流水原子写入。
|
||||
func (d *QuotaGrantDao) AdRewardGrantCreateWithFlow(ctx context.Context, grant *mysqlmodel.QuotaGrant, flow *mysqlmodel.QuotaFlowLog, dayStart, dayEnd int64, maxClaims int) (bool, error) {
|
||||
created := false
|
||||
err := d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var user mysqlmodel.User
|
||||
if err := tx.Select("id").Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", grant.UserID).First(&user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
var count int64
|
||||
if err := tx.Model(&mysqlmodel.QuotaGrant{}).Where("user_id = ? AND quota_type = ? AND source = ? AND created_at >= ? AND created_at < ?", grant.UserID, grant.QuotaType, grant.Source, dayStart, dayEnd).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count >= int64(maxClaims) {
|
||||
return nil
|
||||
}
|
||||
res := tx.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "ref_id"}, {Name: "quota_type"}}, DoNothing: true}).Create(grant)
|
||||
if res.Error != nil || res.RowsAffected == 0 {
|
||||
return res.Error
|
||||
}
|
||||
ensureFlowOrderNo(flow)
|
||||
if err := tx.Create(flow).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
created = true
|
||||
return nil
|
||||
})
|
||||
return created, err
|
||||
}
|
||||
|
||||
// QuotaGrantListByUserIDAndType 查询用户某类型全部凭证(未删除)。
|
||||
// 按到期时间升序(expire_time=0 表示跟随 VIP,排最后),配合「先到期先用」扣减策略。
|
||||
func (d *QuotaGrantDao) QuotaGrantListByUserIDAndType(ctx context.Context, userID int64, quotaType int8) ([]mysqlmodel.QuotaGrant, error) {
|
||||
|
||||
Reference in New Issue
Block a user