package promo import ( "context" "errors" "fmt" "github.com/perfect-panel/server/pkg/cache" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) const ( cachePromoRuleIdPrefix = "cache:promo_rule:id:" cacheSubscribePromoIdPrefix = "cache:subscribe_promo:id:" ) type Model interface { InsertRule(ctx context.Context, data *Rule) error FindRule(ctx context.Context, id int64) (*Rule, error) UpdateRule(ctx context.Context, data *Rule) error DeleteRule(ctx context.Context, id int64) error QueryRuleList(ctx context.Context, page, size int, ruleType string, enabled *bool, search string) (int64, []*Rule, error) UpsertPrices(ctx context.Context, ruleId int64, items []*SubscribePromo) error FindPrice(ctx context.Context, id int64) (*SubscribePromo, error) DeletePrice(ctx context.Context, id int64) error QueryPriceList(ctx context.Context, ruleId int64, page, size int) (int64, []*SubscribePromo, error) QueryUsageList(ctx context.Context, params UsageFilter) (int64, []*Usage, error) Transaction(ctx context.Context, fn func(db *gorm.DB) error) error } type UsageFilter struct { Page int Size int RuleId int64 UserId int64 SubscribeId int64 OrderNo string } type customPromoModel struct { cache.CachedConn table string } func NewModel(db *gorm.DB, c *redis.Client) Model { return &customPromoModel{ CachedConn: cache.NewConn(db, c), table: "`promo_rule`", } } func (m *customPromoModel) ruleCacheKey(id int64) string { return fmt.Sprintf("%s%d", cachePromoRuleIdPrefix, id) } func (m *customPromoModel) priceCacheKey(id int64) string { return fmt.Sprintf("%s%d", cacheSubscribePromoIdPrefix, id) } func (m *customPromoModel) InsertRule(ctx context.Context, data *Rule) error { return m.ExecCtx(ctx, func(conn *gorm.DB) error { return conn.Create(data).Error }, m.ruleCacheKey(data.Id)) } func (m *customPromoModel) FindRule(ctx context.Context, id int64) (*Rule, error) { var resp Rule err := m.QueryCtx(ctx, &resp, m.ruleCacheKey(id), func(conn *gorm.DB, v interface{}) error { return conn.Model(&Rule{}).Where("id = ?", id).First(v).Error }) if err != nil { return nil, err } return &resp, nil } func (m *customPromoModel) UpdateRule(ctx context.Context, data *Rule) error { old, err := m.FindRule(ctx, data.Id) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return err } keys := []string{m.ruleCacheKey(data.Id)} if old != nil { keys = append(keys, m.ruleCacheKey(old.Id)) } return m.ExecCtx(ctx, func(conn *gorm.DB) error { return conn.Save(data).Error }, keys...) } func (m *customPromoModel) DeleteRule(ctx context.Context, id int64) error { data, err := m.FindRule(ctx, id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil } return err } return m.ExecCtx(ctx, func(conn *gorm.DB) error { return conn.Delete(&Rule{}, id).Error }, m.ruleCacheKey(data.Id)) } func (m *customPromoModel) QueryRuleList(ctx context.Context, page, size int, ruleType string, enabled *bool, search string) (int64, []*Rule, error) { if page <= 0 { page = 1 } if size <= 0 { size = 10 } var total int64 var list []*Rule err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { db := conn.Model(&Rule{}) if ruleType != "" { db = db.Where("type = ?", ruleType) } if enabled != nil { db = db.Where("enabled = ?", *enabled) } if search != "" { db = db.Where("name LIKE ?", "%"+search+"%") } if err := db.Count(&total).Error; err != nil { return err } return db.Order("priority DESC").Order("id DESC").Limit(size).Offset((page - 1) * size).Find(v).Error }) return total, list, err } func (m *customPromoModel) UpsertPrices(ctx context.Context, ruleId int64, items []*SubscribePromo) error { return m.ExecCtx(ctx, func(conn *gorm.DB) error { for _, item := range items { if item == nil { continue } item.PromoRuleId = ruleId var existing SubscribePromo err := conn.Model(&SubscribePromo{}). Where("subscribe_id = ? AND quantity = ? AND promo_rule_id = ?", item.SubscribeId, item.Quantity, ruleId). First(&existing).Error if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return err } if errors.Is(err, gorm.ErrRecordNotFound) { if err := conn.Create(item).Error; err != nil { return err } continue } existing.Quantity = item.Quantity existing.PromoPrice = item.PromoPrice if err := conn.Save(&existing).Error; err != nil { return err } } return nil }) } func (m *customPromoModel) FindPrice(ctx context.Context, id int64) (*SubscribePromo, error) { var resp SubscribePromo err := m.QueryCtx(ctx, &resp, m.priceCacheKey(id), func(conn *gorm.DB, v interface{}) error { return conn.Model(&SubscribePromo{}).Where("id = ?", id).First(v).Error }) if err != nil { return nil, err } return &resp, nil } func (m *customPromoModel) DeletePrice(ctx context.Context, id int64) error { data, err := m.FindPrice(ctx, id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil } return err } return m.ExecCtx(ctx, func(conn *gorm.DB) error { return conn.Delete(&SubscribePromo{}, id).Error }, m.priceCacheKey(data.Id)) } func (m *customPromoModel) QueryPriceList(ctx context.Context, ruleId int64, page, size int) (int64, []*SubscribePromo, error) { if page <= 0 { page = 1 } if size <= 0 { size = 10 } var total int64 var list []*SubscribePromo err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { db := conn.Model(&SubscribePromo{}).Where("promo_rule_id = ?", ruleId) if err := db.Count(&total).Error; err != nil { return err } return db.Order("id DESC").Limit(size).Offset((page - 1) * size).Find(v).Error }) return total, list, err } func (m *customPromoModel) QueryUsageList(ctx context.Context, params UsageFilter) (int64, []*Usage, error) { if params.Page <= 0 { params.Page = 1 } if params.Size <= 0 { params.Size = 10 } var total int64 var list []*Usage err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error { db := conn.Model(&Usage{}) if params.RuleId > 0 { db = db.Where("promo_rule_id = ?", params.RuleId) } if params.UserId > 0 { db = db.Where("user_id = ?", params.UserId) } if params.SubscribeId > 0 { db = db.Where("subscribe_id = ?", params.SubscribeId) } if params.OrderNo != "" { db = db.Where("order_no = ?", params.OrderNo) } if err := db.Count(&total).Error; err != nil { return err } return db.Order("id DESC").Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(v).Error }) return total, list, err } func (m *customPromoModel) Transaction(ctx context.Context, fn func(db *gorm.DB) error) error { return m.TransactCtx(ctx, fn) }