package model import ( "context" "database/sql/driver" "encoding/json" "errors" "fmt" "github.com/go-redis/redis/v8" "gorm.io/gorm" "samba/pkg/log" "samba/pkg/xtime" "samba/proto" "samba/util/rdbkey" "time" ) type Items map[int]int func (i *Items) Scan(v any) error { if i == nil { *i = make(Items) } switch js := v.(type) { case string: if len(js) <= 0 { return nil } return json.Unmarshal([]byte(js), i) case []byte: if len(js) <= 0 { return nil } return json.Unmarshal(js, i) default: return fmt.Errorf(fmt.Sprintf("item is %T but not string", v)) } } func (i *Items) Value() (driver.Value, error) { data, err := json.Marshal(i) return string(data), err } type Rule proto.Rule func (r *Rule) Scan(v any) error { if r == nil { *r = Rule{} } switch js := v.(type) { case string: if len(js) <= 0 { return nil } return json.Unmarshal([]byte(js), &r) case []byte: if len(js) <= 0 { return nil } return json.Unmarshal(js, &r) default: return fmt.Errorf(fmt.Sprintf("rule is %T but not string", v)) } } func (r *Rule) Value() (driver.Value, error) { data, err := json.Marshal(r) return string(data), err } // 兑换码 type RedeemCode struct { // 兑换码本身 Code string `json:"code" gorm:"column:code"` // 生效时间 StartTime time.Time `json:"start_time" gorm:"column:start_time"` // 截止时间 EndTime time.Time `json:"end_time" gorm:"column:end_time"` // 创建时间 CreateTime time.Time `json:"create_time" gorm:"column:create_time"` // 可兑换次数 Count int `json:"count" gorm:"column:count"` // 兑换物品 Item *Items `json:"item" gorm:"column:item"` // 兑换规则 Rule *Rule `json:"rule" gorm:"column:rule"` } func (*RedeemCode) TableName() string { return "t_redeem_code" } type RedeemCodeOp struct { db *gorm.DB rdb *redis.Client } func NewRedeemCodeOp() *RedeemCodeOp { return &RedeemCodeOp{ rdb: rdbBaseInfo, db: userDB, } } // IsExist 判断兑换码是否存在 func (op *RedeemCodeOp) IsExist(code string) (exist bool) { res := op.rdb.HGet(context.TODO(), rdbkey.RedeemCodeKey(), code) if res.Err() == nil && res.Val() != "" { return true } var r RedeemCode if err := op.db.Where("code=?", code).First(&r).Error; err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { // 非字段不存在错误 log.Error(fmt.Sprintf("RedeemCodeOp code=%s err:%s", code, err.Error())) } return false } // 回写redis op.insertRedis(&r) return true } // Insert 插入一条兑换码 func (op *RedeemCodeOp) Insert(code *RedeemCode) error { err := op.db.Create(code).Error if err != nil { return err } op.insertRedis(code) return nil } // insertRedis 插入redis func (op *RedeemCodeOp) insertRedis(code *RedeemCode) { data, err := json.Marshal(code) if err != nil { log.Error(fmt.Sprintf("RedeemCodeOp InsertRedis code=%s err:%s", code.Code, err.Error())) return } err = op.rdb.HSet(context.TODO(), rdbkey.RedeemCodeKey(), code.Code, string(data)).Err() if err != nil { log.Error(fmt.Sprintf("RedeemCodeOp InsertRedis code=%s err:%s", code.Code, err.Error())) } } // Get 获取兑换码 func (op *RedeemCodeOp) Get(code string) (r *RedeemCode, err error) { res := op.rdb.HGet(context.TODO(), rdbkey.RedeemCodeKey(), code) r = &RedeemCode{} if res.Err() == nil && res.Val() != "" { return r, json.Unmarshal([]byte(res.Val()), r) } err = op.db.Where("code=?", code).First(r).Error if err != nil { return nil, err } // 回写redis op.insertRedis(r) return r, nil } // GetList 获取兑换码列表 func (op *RedeemCodeOp) GetList(limit, offset int) (rs []*RedeemCode, err error) { return rs, op.db.Limit(limit).Offset(offset).Order("create_time desc").Find(&rs).Error } // Count 获取兑换码总数 func (op *RedeemCodeOp) Count() (count int64, err error) { return count, op.db.Model(&RedeemCode{}).Count(&count).Error } // 兑换码使用记录 type RedeemCodeUseRecord struct { // 兑换码本身 Code string `json:"code" gorm:"column:code"` // 使用者ID Uid int64 `json:"uid" gorm:"column:uid"` // 使用时间 UseTime time.Time `json:"use_time" gorm:"column:use_time"` // 用户昵称 MNick string `json:"mnick" gorm:"column:mnick"` // 用户注册时间 MTime int64 `json:"mtime" gorm:"column:mtime"` // 用户注册渠道 API int `json:"api" gorm:"column:api"` } func (*RedeemCodeUseRecord) TableName() string { return "t_redeem_code_record" } type RedeemCodeUseRecordOp struct { db *gorm.DB rdb *redis.Client } func NewRedeemCodeUseRecordOp() *RedeemCodeUseRecordOp { return &RedeemCodeUseRecordOp{ rdb: rdbBaseInfo, db: userDB, } } func (op *RedeemCodeUseRecordOp) GetByCode(code string, limit, offset int) ([]*RedeemCodeUseRecord, error) { var res []*RedeemCodeUseRecord return res, op.db.Where("code=?", code).Limit(limit).Offset(offset).Order("use_time desc").Find(&res).Error } func (op *RedeemCodeUseRecordOp) CountByCode(code string) (count int64, err error) { return count, op.db.Model(&RedeemCodeUseRecord{}).Where("code=?", code).Count(&count).Error } func (op *RedeemCodeUseRecordOp) GetByUid(uid int64, limit, offset int) ([]*RedeemCodeUseRecord, error) { var res []*RedeemCodeUseRecord return res, op.db.Where("uid=?", uid).Limit(limit).Offset(offset).Order("use_time desc").Find(&res).Error } func (op *RedeemCodeUseRecordOp) GetByUidAndCode(uid int64, code string) (*RedeemCodeUseRecord, error) { res := &RedeemCodeUseRecord{} return res, op.db.Where("uid=? and code=?", uid, code).First(&res).Error } // CodeUsedCount 查询兑换码的使用次数 func (op *RedeemCodeUseRecordOp) CodeUsedCount(code string) (int64, error) { count, err := op.rdb.HGet(context.TODO(), rdbkey.RedeemCodeUsedKey(), code).Int64() if err != nil { return op.updateUseCount(code) } return count, nil } // UserIsUsedCode 判断用户是否使用过兑换码 func (op *RedeemCodeUseRecordOp) UserIsUsedCode(uid int64, code string) (bool, error) { r, err := op.GetByUidAndCode(uid, code) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return false, nil } return false, err } return r != nil, nil } // Insert 插入一条兑换码使用记录 func (op *RedeemCodeUseRecordOp) Insert(uid int64, code string) error { uInfo, err := NewUserInfoOp().Load(uid) if err != nil { return err } record := &RedeemCodeUseRecord{ Code: code, Uid: uInfo.UID, UseTime: xtime.Now().StdTime(), MNick: uInfo.MNick, MTime: uInfo.MTime, API: uInfo.API, } err = op.db.Create(record).Error if err != nil { return err } _, _ = op.updateUseCount(code) return nil } func (op *RedeemCodeUseRecordOp) GetCodeUseList() { } // updateUseCount 更新兑换码使用次数 func (op *RedeemCodeUseRecordOp) updateUseCount(code string) (count int64, err error) { defer func() { if err != nil { log.Error(fmt.Sprintf("RedeemCodeUseRecordOp updateUseCount code=%s, err:%s", code, err.Error())) } }() err = op.db.Model(&RedeemCodeUseRecord{}).Where("code=?", code).Count(&count).Error if err != nil { return } err = op.rdb.HSet(context.TODO(), rdbkey.RedeemCodeUsedKey(), code, count).Err() return } // insertRedis 插入到redis缓存 // // 暂时先不用,双写有一致性问题 func (op *RedeemCodeUseRecordOp) insertRedis(rs ...*RedeemCodeUseRecord) { userMp := make(map[int64][]any) codeMp := make(map[string][]any) for _, r := range rs { data, err := json.Marshal(r) if err != nil { log.Error(fmt.Sprintf("RedeemCodeUseRecordOp InsertRedis code=%s uid=%d err:%s", r.Code, r.Uid, err.Error())) return } strData := string(data) userMp[r.Uid] = append(userMp[r.Uid], r.Code, strData) codeMp[r.Code] = append(codeMp[r.Code], r.Uid, strData) } for uid, kvs := range userMp { err := op.rdb.HSet(context.TODO(), rdbkey.RedeemCodeUserUseKey(uid), kvs...).Err() if err != nil { log.Error(fmt.Sprintf("RedeemCodeUseRecordOp InsertRedis uid=%d, err:%s", uid, err.Error())) } } for code, kvs := range codeMp { err := op.rdb.HSet(context.TODO(), rdbkey.RedeemCodeUseKey(code), kvs...).Err() if err != nil { log.Error(fmt.Sprintf("RedeemCodeUseRecordOp InsertRedis code=%s, err:%s", code, err.Error())) } } }