gpt
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// MemberQuotaGrantCreateWithFlow 按用户、会员商品、额度类型仅发放一次。
|
||||
// 锁定用户行将同一用户的并发续费串行化,检查与写入使用同一事务。
|
||||
// 保留原始订单号用于退款追溯;历史凭证(包括已退款软删除的凭证)也参与去重,
|
||||
// 因而额度用完、会员过期或退款后再次购买都不会重新领取首次赠送。
|
||||
func (d *QuotaGrantDao) MemberQuotaGrantCreateWithFlow(ctx context.Context, goodsID int64, grant *mysqlmodel.QuotaGrant, flow *mysqlmodel.QuotaFlowLog) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.MemberQuotaGrantCreateWithFlow")
|
||||
defer span.End()
|
||||
if goodsID <= 0 || grant == nil || flow == nil || grant.UserID <= 0 || grant.RefID == "" || grant.Source != 1 {
|
||||
return false, fmt.Errorf("invalid member quota grant")
|
||||
}
|
||||
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
|
||||
}
|
||||
// 当前读确保 MySQL REPEATABLE READ 下等待用户锁后可见上一事务的发放。
|
||||
// 关联订单快照识别商品,不依赖当前商品名称或用户是否仍为新用户。
|
||||
var previous []mysqlmodel.QuotaGrant
|
||||
if err := tx.Unscoped().Model(&mysqlmodel.QuotaGrant{}).
|
||||
Select("quota_grants.*").
|
||||
Joins("JOIN orders ON orders.order_no = quota_grants.ref_id AND orders.user_id = quota_grants.user_id").
|
||||
Where("quota_grants.user_id = ? AND orders.goods_id = ? AND quota_grants.quota_type = ? AND quota_grants.source = ?", grant.UserID, goodsID, grant.QuotaType, grant.Source).
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).Limit(1).Find(&previous).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(previous) > 0 {
|
||||
return nil
|
||||
}
|
||||
if err := tx.Create(grant).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
ensureFlowOrderNo(flow)
|
||||
if err := tx.Create(flow).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
created = true
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
return false, err
|
||||
}
|
||||
return created, nil
|
||||
}
|
||||
Reference in New Issue
Block a user