新功能(#74): 新增促销系统模型层

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
2026-05-26 22:11:02 -07:00
parent 5ebfe0a981
commit e61988d78f
14 changed files with 720 additions and 25 deletions
+106
View File
@@ -0,0 +1,106 @@
package promo_rule
import (
"context"
"errors"
"fmt"
"github.com/perfect-panel/server/pkg/cache"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
var _ Model = (*customPromoRuleModel)(nil)
var cachePromoRuleIdPrefix = "cache:promo_rule:id:"
type (
Model interface {
promoRuleModel
customPromoRuleLogicModel
}
promoRuleModel interface {
Insert(ctx context.Context, data *PromoRule, tx ...*gorm.DB) error
FindOne(ctx context.Context, id int64) (*PromoRule, error)
Update(ctx context.Context, data *PromoRule, tx ...*gorm.DB) error
Delete(ctx context.Context, id int64, tx ...*gorm.DB) error
Transaction(ctx context.Context, fn func(db *gorm.DB) error) error
}
customPromoRuleModel struct {
*defaultPromoRuleModel
}
defaultPromoRuleModel struct {
cache.CachedConn
table string
}
)
func newPromoRuleModel(db *gorm.DB, c *redis.Client) *defaultPromoRuleModel {
return &defaultPromoRuleModel{
CachedConn: cache.NewConn(db, c),
table: "`promo_rule`",
}
}
func (m *defaultPromoRuleModel) getCacheKeys(data *PromoRule) []string {
if data == nil {
return []string{}
}
return []string{fmt.Sprintf("%s%v", cachePromoRuleIdPrefix, data.Id)}
}
func (m *defaultPromoRuleModel) Insert(ctx context.Context, data *PromoRule, tx ...*gorm.DB) error {
return m.ExecCtx(ctx, func(conn *gorm.DB) error {
if len(tx) > 0 {
conn = tx[0]
}
return conn.Create(data).Error
}, m.getCacheKeys(data)...)
}
func (m *defaultPromoRuleModel) FindOne(ctx context.Context, id int64) (*PromoRule, error) {
key := fmt.Sprintf("%s%v", cachePromoRuleIdPrefix, id)
var resp PromoRule
err := m.QueryCtx(ctx, &resp, key, func(conn *gorm.DB, v interface{}) error {
return conn.Model(&PromoRule{}).Where("`id` = ?", id).First(v).Error
})
if err != nil {
return nil, err
}
return &resp, nil
}
func (m *defaultPromoRuleModel) Update(ctx context.Context, data *PromoRule, tx ...*gorm.DB) error {
old, err := m.FindOne(ctx, data.Id)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
return m.ExecCtx(ctx, func(conn *gorm.DB) error {
if len(tx) > 0 {
conn = tx[0]
}
return conn.Save(data).Error
}, m.getCacheKeys(old)...)
}
func (m *defaultPromoRuleModel) Delete(ctx context.Context, id int64, tx ...*gorm.DB) error {
data, err := m.FindOne(ctx, id)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return err
}
return m.ExecCtx(ctx, func(conn *gorm.DB) error {
if len(tx) > 0 {
conn = tx[0]
}
return conn.Delete(&PromoRule{}, id).Error
}, m.getCacheKeys(data)...)
}
func (m *defaultPromoRuleModel) Transaction(ctx context.Context, fn func(db *gorm.DB) error) error {
return m.TransactCtx(ctx, fn)
}
+44
View File
@@ -0,0 +1,44 @@
package promo_rule
import (
"context"
"time"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
type customPromoRuleLogicModel interface {
QueryEnabledByPriority(ctx context.Context) ([]*PromoRule, error)
QueryAvailableByPriority(ctx context.Context, now time.Time) ([]*PromoRule, error)
}
func NewModel(conn *gorm.DB, c *redis.Client) Model {
return &customPromoRuleModel{
defaultPromoRuleModel: newPromoRuleModel(conn, c),
}
}
func (m *customPromoRuleModel) QueryEnabledByPriority(ctx context.Context) ([]*PromoRule, error) {
var list []*PromoRule
err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
return conn.Model(&PromoRule{}).
Where("enabled = ?", true).
Order("priority DESC, id DESC").
Find(v).Error
})
return list, err
}
func (m *customPromoRuleModel) QueryAvailableByPriority(ctx context.Context, now time.Time) ([]*PromoRule, error) {
var list []*PromoRule
err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
return conn.Model(&PromoRule{}).
Where("enabled = ?", true).
Where("(start_time IS NULL OR start_time <= ?)", now).
Where("(end_time IS NULL OR end_time >= ?)", now).
Order("priority DESC, id DESC").
Find(v).Error
})
return list, err
}
+73
View File
@@ -0,0 +1,73 @@
package promo_rule
import (
"database/sql/driver"
"encoding/json"
"time"
"gorm.io/gorm"
)
type JSONRawMessage json.RawMessage
func (j *JSONRawMessage) Scan(value interface{}) error {
if value == nil {
*j = JSONRawMessage([]byte("{}"))
return nil
}
switch v := value.(type) {
case []byte:
*j = JSONRawMessage(v)
case string:
*j = JSONRawMessage([]byte(v))
default:
*j = JSONRawMessage([]byte("{}"))
}
return nil
}
func (j JSONRawMessage) Value() (driver.Value, error) {
if len(j) == 0 {
return "{}", nil
}
data, err := json.RawMessage(j).MarshalJSON()
if err != nil {
return nil, err
}
return string(data), nil
}
func (j JSONRawMessage) MarshalJSON() ([]byte, error) {
if len(j) == 0 {
return []byte("{}"), nil
}
return json.RawMessage(j).MarshalJSON()
}
func (j *JSONRawMessage) UnmarshalJSON(data []byte) error {
if len(data) == 0 {
*j = JSONRawMessage([]byte("{}"))
return nil
}
*j = JSONRawMessage(data)
return nil
}
type PromoRule struct {
Id int64 `gorm:"type:bigint unsigned;primaryKey"`
Name string `gorm:"type:varchar(100);not null;default:'';comment:Rule Name"`
Type string `gorm:"type:varchar(32);not null;default:'';comment:Rule Type: new_user, inactive_user, campaign"`
Params JSONRawMessage `gorm:"type:json;not null;comment:Rule Params"`
Priority int64 `gorm:"type:int;not null;default:0;index:idx_enabled_priority,priority:2,sort:desc;comment:Priority"`
Enabled bool `gorm:"type:tinyint(1);not null;default:1;index:idx_enabled_priority,priority:1;comment:Enabled"`
StartTime *time.Time `gorm:"type:datetime;default:null;comment:Start Time"`
EndTime *time.Time `gorm:"type:datetime;default:null;comment:End Time"`
CreatedAt time.Time `gorm:"<-:create;comment:Create Time"`
UpdatedAt time.Time `gorm:"comment:Update Time"`
DeletedAt gorm.DeletedAt `gorm:"index;comment:Delete Time"`
}
func (PromoRule) TableName() string {
return "promo_rule"
}