samba/util/model/redeemCode.go
2025-06-04 09:51:39 +08:00

330 lines
8.0 KiB
Go

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()))
}
}
}