package dao import ( "context" "encoding/json" "fmt" "math/rand" "strconv" "sync" "time" v12 "go-common/app/service/live/rc/api/liverpc/v1" "github.com/pkg/errors" "go-common/app/service/live/xlottery/api/grpc/v1" "go-common/library/cache/redis" "go-common/library/database/sql" "go-common/library/log" "go-common/library/queue/databus/report" ) const ( _userCapsuleCoinMysql = "select normal_score, colorful_score from capsule_%d where uid = ?" _addCapsuleCoinMysql = "insert into capsule_%d(uid, normal_score, colorful_score) values(?, ?, ?)" _userInfoMysql = "select score from capsule_info_%d where uid = ? and type = ?" _addInfoMysql = "insert into capsule_info_%d(uid,type,score) values(?, ?, ?)" _updateUserCapsuleMysql = "update capsule_%d set %s_score = %s_score + ? where uid = ?" _updateUserInfoMysql = "update capsule_info_%d set score = score + ? where uid = ? and type = ?" _transUserCapsuleMysql = "update capsule_%d set colorful_score = 0, normal_score = normal_score + ? where uid = ?" _reportCapsuleChangeMysql = "insert into capsule_log_%s(uid, type, score, action, platform, pre_normal_score, pre_colorful_score, cur_normal_score, cur_colorful_score) values(?,?,?,?,?,?,?,?,?)" _userCapsuleCoinRedis = "hash:capsule:user:%d" _userInfoRedis = "capsule:user:info:%d:%d" _openHistoryRedis = "list:capsule:%s:list" _openLockRedis = "capsule-pay-%d" _capsuleConfRand = "capsule:rand" _historyOpenCount = "hash:capsule:count" _historyGiftCount = "capsule:gift:count:%d:%s" _capsuleNotice = "capsule:notice:%s:%d" _whiteUserPrizeRedis = "capsule:white:user:%d" ) const ( _ = iota // CapsulePrizeGift1Type 辣条 CapsulePrizeGift1Type // 辣条 // CapsulePrizeTitleType 头衔 CapsulePrizeTitleType // CapsulePrizeStuff1Type 经验原石 CapsulePrizeStuff1Type // CapsulePrizeStuff2Type 经验曜石 CapsulePrizeStuff2Type // CapsulePrizeStuff3Type 贤者之石 CapsulePrizeStuff3Type // CapsulePrizeSmallTvType 小电视 CapsulePrizeSmallTvType // CapsulePrizeGuard3Type 舰长体验 CapsulePrizeGuard3Type // CapsulePrizeGuard2Type 提督体验 CapsulePrizeGuard2Type // CapsulePrizeGuard1Type 总督体验 CapsulePrizeGuard1Type // CapsulePrizeScoreAdd 积分加成卡 CapsulePrizeScoreAdd // CapsulePrizeSmallStar 小星星 CapsulePrizeSmallStar // CapsulePrizeWeekScore 抽奖券 CapsulePrizeWeekScore // CapsulePrizeDanmuColor 弹幕颜色 CapsulePrizeDanmuColor // CapsulePrizeLplScore lpl抽奖券 CapsulePrizeLplScore // CapsulePrizeLplProduct1 lpl实物奖励1 CapsulePrizeLplProduct1 // CapsulePrizeLplProduct2 lpl实物奖励2 CapsulePrizeLplProduct2 // CapsulePrizeLplProduct3 lpl实物奖励3 CapsulePrizeLplProduct3 ) const ( // CapsulePrizeProduct1 . CapsulePrizeProduct1 = 100000 // 实物奖励 // CapsulePrizeProduct2 . CapsulePrizeProduct2 = 100001 // 实物奖励 // CapsulePrizeProduct3 . CapsulePrizeProduct3 = 100002 // CapsulePrizeProduct4 . CapsulePrizeProduct4 = 100003 // 实物奖励 // CapsulePrizeProduct5 . CapsulePrizeProduct5 = 100004 // 实物奖励 // CapsulePrizeProduct6 . CapsulePrizeProduct6 = 100005 ) const ( // CapsulePrizeCoupon1 . CapsulePrizeCoupon1 = 200000 // CapsulePrizeCoupon2 . CapsulePrizeCoupon2 = 200001 // CapsulePrizeCoupon3 . CapsulePrizeCoupon3 = 200002 ) const ( // CapsulePrizeExpire1Day 过期时间24小时 CapsulePrizeExpire1Day = 1 // CapsulePrizeExpire3Day 过期时间72小时 CapsulePrizeExpire3Day = 10 // CapsulePrizeExpire1Week 过期时间1周 CapsulePrizeExpire1Week = 20 // CapsulePrizeExpire3Month 过期时间3个月 CapsulePrizeExpire3Month = 30 // CapsulePrizeExpireForever 过期时间永久 CapsulePrizeExpireForever = 100 ) const ( _ = iota // ProTypeNormal 概率 ProTypeNormal // ProTypeFixDay 每天固定数量 ProTypeFixDay // ProTypeFixWeek 每周固定数量 ProTypeFixWeek // ProTypeWhite 白名单 ProTypeWhite ) const ( // CapsuleGiftTypeAll gift_type 为全部道具 CapsuleGiftTypeAll = 1 ) const ( // NormalCoinId 普通扭蛋id NormalCoinId = 1 // ColorfulCoinId 梦幻扭蛋id ColorfulCoinId = 2 // WeekCoinId 梦幻扭蛋id WeekCoinId = 3 // LplCoinId 梦幻扭蛋id LplCoinId = 4 // BlessCoinId 祈福券 BlessCoinId = 5 // OpenHistoryNum 开奖历史 OpenHistoryNum = 30 // NormalCoinString 普通扭蛋字符串标识,数据库和redis NormalCoinString = "normal" // ColorfulCoinString 梦幻扭蛋字符串标识,数据库和redis ColorfulCoinString = "colorful" // WeekCoinString 周星扭蛋字符串标识,数据库和redis WeekCoinString = "week" // LplCoinString lpl扭蛋字符串标识,数据库和redis LplCoinString = "lpl" // BlessCoinString 新年祈愿扭蛋字符串标识,数据库和redis BlessCoinString = "bless" // GetCapsuleDetailFromRoom 接口来源 GetCapsuleDetailFromRoom = "room" // GetCapsuleDetailFromWeb 接口来源 GetCapsuleDetailFromWeb = "web" // GetCapsuleDetailFromH5 接口来源 GetCapsuleDetailFromH5 = "h5" ) const ( //IsBottomPool 是保底奖池 IsBottomPool = 1 //CapsuleActionTrans 转换扭蛋 CapsuleActionTrans = "trans" ) // CapsuleConf 扭蛋全局配置 type CapsuleConf struct { CoinConfMap map[int64]*CapsuleCoinConf CacheTime int64 ChangeFlag int64 RwLock sync.RWMutex } // CapsuleCoinConf 扭蛋币配置 type CapsuleCoinConf struct { Id int64 Title string GiftType int64 ChangeNum int64 StartTime int64 EndTime int64 Status int64 GiftMap map[int64]struct{} AreaMap map[int64]struct{} PoolConf *CapsulePoolConf AllPoolConf []*CapsulePoolConf } // CapsulePoolConf 奖池配置 type CapsulePoolConf struct { Id int64 CoinId int64 Title string Rule string StartTime, EndTime int64 Status int64 IsBottom int64 PoolPrize []*CapsulePoolPrize } // CapsulePoolPrize 奖池奖品 type CapsulePoolPrize struct { Id, PoolId, Type, Num, ObjectId, Expire int64 Name, WebImage, MobileImage, Description, JumpUrl string ProType int64 Chance int64 LoopNum, LimitNum, Weight int64 WhiteUserMap map[int64]struct{} } // HistoryOpenInfo 开奖历史 type HistoryOpenInfo struct { Uid int64 `json:"uid"` Name string `json:"name"` Date string `json:"date"` Num int64 `json:"num"` } var ( // CoinIdIntMap map CoinIdIntMap map[int64]string // CoinIdStringMap map CoinIdStringMap map[string]int64 // ReprotConfig map ReprotConfig map[int64]string // PrizeNameMap map PrizeNameMap map[int64]string // PrizeExpireMap map PrizeExpireMap map[int64]string // UnLockGetWrong flag UnLockGetWrong = "UnLockGetWrong" // ErrUnLockGet error ErrUnLockGet = errors.New(UnLockGetWrong) capsuleConf CapsuleConf whitePrizeMap sync.Map ) func init() { CoinIdIntMap = make(map[int64]string) CoinIdIntMap[NormalCoinId] = NormalCoinString CoinIdIntMap[ColorfulCoinId] = ColorfulCoinString CoinIdIntMap[WeekCoinId] = WeekCoinString CoinIdIntMap[LplCoinId] = LplCoinString CoinIdIntMap[BlessCoinId] = BlessCoinString CoinIdStringMap = make(map[string]int64) CoinIdStringMap[NormalCoinString] = NormalCoinId CoinIdStringMap[ColorfulCoinString] = ColorfulCoinId CoinIdStringMap[WeekCoinString] = WeekCoinId CoinIdStringMap[LplCoinString] = LplCoinId CoinIdStringMap[BlessCoinString] = BlessCoinId ReprotConfig = make(map[int64]string) ReprotConfig[0] = "未知" ReprotConfig[1] = "增加普通扭蛋" ReprotConfig[2] = "增加梦幻扭蛋" ReprotConfig[3] = "减少普通扭蛋" ReprotConfig[4] = "减少梦幻扭蛋" ReprotConfig[5] = "梦幻转化普通" PrizeNameMap = make(map[int64]string) PrizeNameMap[CapsulePrizeGift1Type] = "辣条" PrizeNameMap[CapsulePrizeTitleType] = "头衔" PrizeNameMap[CapsulePrizeStuff1Type] = "经验原石" PrizeNameMap[CapsulePrizeStuff2Type] = "经验曜石" PrizeNameMap[CapsulePrizeStuff3Type] = "贤者之石" PrizeNameMap[CapsulePrizeSmallTvType] = "小电视抱枕" PrizeNameMap[CapsulePrizeGuard3Type] = "舰长体验" PrizeNameMap[CapsulePrizeGuard2Type] = "提督体验" PrizeNameMap[CapsulePrizeScoreAdd] = "积分加成卡" PrizeNameMap[CapsulePrizeSmallStar] = "小星星" PrizeNameMap[CapsulePrizeWeekScore] = "抽奖券" PrizeNameMap[CapsulePrizeDanmuColor] = "金色弹幕" PrizeNameMap[CapsulePrizeLplScore] = "LPL抽奖券" PrizeNameMap[CapsulePrizeLplProduct1] = "2019拜年祭小电视猪" PrizeNameMap[CapsulePrizeLplProduct2] = "小电视毛绒公仔" PrizeNameMap[CapsulePrizeLplProduct3] = "机械之心桌垫" PrizeNameMap[CapsulePrizeProduct1] = "迎新礼盒+新年台历" PrizeNameMap[CapsulePrizeProduct2] = "新年锦鲤围巾" PrizeNameMap[CapsulePrizeProduct3] = "拜年祭挂件+拜年祭立牌+年夜饭挂画" PrizeNameMap[CapsulePrizeProduct4] = "拜年祭耳罩" PrizeNameMap[CapsulePrizeProduct5] = "小电视猪挂件+小电视猪公仔" PrizeNameMap[CapsulePrizeProduct6] = "拜年祭徽章" PrizeNameMap[CapsulePrizeCoupon1] = "会员购20元优惠券" PrizeNameMap[CapsulePrizeCoupon2] = "会员购40元优惠券" PrizeNameMap[CapsulePrizeCoupon3] = "会员购60元优惠券" PrizeExpireMap = make(map[int64]string) PrizeExpireMap[CapsulePrizeExpire1Day] = "1天" PrizeExpireMap[CapsulePrizeExpire3Day] = "3天" PrizeExpireMap[CapsulePrizeExpire1Week] = "1周" PrizeExpireMap[CapsulePrizeExpire3Month] = "3个月" PrizeExpireMap[CapsulePrizeExpireForever] = "永久" } // GetCapsuleConf 获取扭蛋币配置 func (d *Dao) GetCapsuleConf(ctx context.Context) (conf map[int64]*CapsuleCoinConf, err error) { capsuleConf.RwLock.RLock() tmpConf := capsuleConf.CoinConfMap capsuleConf.RwLock.RUnlock() if len(tmpConf) == 0 { redisChangeFlag, _ := d.GetCapsuleChangeFlag(ctx) tmpConf, err = d.RelaodCapsuleConfig(ctx, redisChangeFlag) if err != nil || tmpConf == nil || len(tmpConf) == 0 { log.Error("[dap.capsule | GetCapsuleConf] CapsuleCoinConf is empty") return nil, err } } now := time.Now().Unix() conf = make(map[int64]*CapsuleCoinConf) for coinId := range CoinIdIntMap { if _, ok := tmpConf[coinId]; ok { coinConf := tmpConf[coinId] if coinConf.AllPoolConf != nil && len(coinConf.AllPoolConf) > 0 { for _, poolConf := range coinConf.AllPoolConf { if poolConf.StartTime < now && poolConf.EndTime > now { if _, ok := conf[coinId]; !ok { conf[coinId] = &CapsuleCoinConf{ Id: coinConf.Id, Title: coinConf.Title, GiftType: coinConf.GiftType, ChangeNum: coinConf.ChangeNum, StartTime: coinConf.StartTime, EndTime: coinConf.EndTime, Status: coinConf.Status, GiftMap: coinConf.GiftMap, AreaMap: coinConf.AreaMap, PoolConf: poolConf, AllPoolConf: coinConf.AllPoolConf, } } else { if poolConf.IsBottom != IsBottomPool { conf[coinId] = &CapsuleCoinConf{ Id: coinConf.Id, Title: coinConf.Title, GiftType: coinConf.GiftType, ChangeNum: coinConf.ChangeNum, StartTime: coinConf.StartTime, EndTime: coinConf.EndTime, Status: coinConf.Status, GiftMap: coinConf.GiftMap, AreaMap: coinConf.AreaMap, PoolConf: poolConf, AllPoolConf: coinConf.AllPoolConf, } } } } } } } } return conf, nil } func getCapsuleTable(Uid int64) int64 { return Uid % 10 } func userKey(uid int64) string { return fmt.Sprintf(_userCapsuleCoinRedis, uid) } func userInfoKey(uid int64, coinId int64) string { return fmt.Sprintf(_userInfoRedis, uid, coinId) } func openHistoryKey(coinType string) string { return fmt.Sprintf(_openHistoryRedis, coinType) } func openTotalCountKey() string { return _historyOpenCount } func openGiftCountKey(giftId int64, day string) string { return fmt.Sprintf(_historyGiftCount, giftId, day) } func whiteUserPrizeKey(uid int64) string { return fmt.Sprintf(_whiteUserPrizeRedis, uid) } // GetUserCapsuleInfo 获取扭蛋币积分 func (d *Dao) GetUserCapsuleInfo(c context.Context, uid int64) (coinMap map[int64]int64, err error) { var ( isEmpty bool uKey string normalScore int64 colorfulScore int64 ) uKey = userKey(uid) conn := d.redis.Get(c) defer conn.Close() uInfo, err := redis.Int64Map(conn.Do("HGETALL", uKey)) if err != nil { if err == redis.ErrNil { isEmpty = true err = nil } else { log.Error("[dao.redis_lottery|setUserCapsuleInfoCache] getUserCapsuleInfoCache conn.HMGET(%s) error(%v)", uKey, err) return } } else if len(uInfo) == 0 { isEmpty = true } coinMap = make(map[int64]int64) if isEmpty { sqlStr := fmt.Sprintf(_userCapsuleCoinMysql, getCapsuleTable(uid)) row := d.db.QueryRow(c, sqlStr, uid) err = row.Scan(&normalScore, &colorfulScore) if err != nil && err != sql.ErrNoRows { return } if err == sql.ErrNoRows { sqlStr := fmt.Sprintf(_addCapsuleCoinMysql, getCapsuleTable(uid)) _, err = d.db.Exec(c, sqlStr, uid, 0, 0) if err != nil { log.Error("[dao.redis_lottery|GetUserCapsuleInfo] init sql(%s) uid(%d) error(%v)", sqlStr, uid, err) return } normalScore, colorfulScore = 0, 0 } _, err = conn.Do("HMSET", uKey, CoinIdIntMap[NormalCoinId], normalScore, CoinIdIntMap[ColorfulCoinId], colorfulScore) if err != nil { log.Error("[dao.redis_lottery|GetUserCapsuleInfo] setUserCapsuleInfoCache conn.HMSET(%s) error(%v)", uKey, err) } coinMap[NormalCoinId] = normalScore coinMap[ColorfulCoinId] = colorfulScore err = nil } else { if _, ok := uInfo[CoinIdIntMap[NormalCoinId]]; ok { coinMap[NormalCoinId] = uInfo[CoinIdIntMap[NormalCoinId]] } if _, ok := uInfo[CoinIdIntMap[ColorfulCoinId]]; ok { coinMap[ColorfulCoinId] = uInfo[CoinIdIntMap[ColorfulCoinId]] } } return } // GetUserInfo 获取扭蛋币详情 func (d *Dao) GetUserInfo(c context.Context, uid, coinId int64) (coinMap map[int64]int64, err error) { if coinId <= ColorfulCoinId { coinMap, err = d.GetUserCapsuleInfo(c, uid) return } var ( isEmpty bool uKey string score int64 ) uKey = userInfoKey(uid, coinId) conn := d.redis.Get(c) defer conn.Close() score, err = redis.Int64(conn.Do("GET", uKey)) if err != nil { if err == redis.ErrNil { isEmpty = true err = nil } else { log.Error("[dao.redis_lottery|GetUserInfo] getUserInfoCache conn.HMGET(%s) error(%v)", uKey, err) return } } coinMap = make(map[int64]int64) if isEmpty { sqlStr := fmt.Sprintf(_userInfoMysql, getCapsuleTable(uid)) row := d.db.QueryRow(c, sqlStr, uid, CoinIdIntMap[coinId]) err = row.Scan(&score) if err != nil && err != sql.ErrNoRows { log.Error("[dao.redis_lottery|GetUserInfo] getUserInfoFromDB uid(%d) error(%v)", uid, err) return } if err == sql.ErrNoRows { sqlStr := fmt.Sprintf(_addInfoMysql, getCapsuleTable(uid)) _, err = d.db.Exec(c, sqlStr, uid, CoinIdIntMap[coinId], 0) if err != nil { log.Error("[dao.redis_lottery|GetUserInfo] init sql(%s) uid(%d) error(%v)", sqlStr, uid, err) return nil, err } score = 0 } _, err = conn.Do("SET", uKey, score) if err != nil { log.Error("[dao.redis_lottery|GetUserInfo] setUserCapsuleInfoCache conn.HMSET(%s) error(%v)", uKey, err) } coinMap[coinId] = score err = nil } else { coinMap[coinId] = score } return } // GetOpenHistory 获取扭蛋币历史 func (d *Dao) GetOpenHistory(c context.Context, coinType int64) (ret []*HistoryOpenInfo, err error) { hkey := openHistoryKey(CoinIdIntMap[coinType]) conn := d.redis.Get(c) defer conn.Close() jsons, err := redis.Strings(conn.Do("LRANGE", hkey, 0, OpenHistoryNum-1)) if err != nil { return } length := len(jsons) if length == 0 { return } ret = make([]*HistoryOpenInfo, length) for ix, jsonStr := range jsons { var openInfo HistoryOpenInfo json.Unmarshal([]byte(jsonStr), &openInfo) ret[ix] = &openInfo } return } // GetCoin 获取扭蛋数量 func (d *Dao) GetCoin(score int64, coinConf *CapsuleCoinConf) (coinNum int64) { if coinConf == nil || coinConf.ChangeNum == 0 { return 0 } coinNum = score / coinConf.ChangeNum return } // GetProgress 获取扭蛋币进度 func (d *Dao) GetProgress(score int64, coinConf *CapsuleCoinConf) (process *v1.Progress) { process = &v1.Progress{} if coinConf == nil || coinConf.ChangeNum == 0 { return } process.Max = coinConf.ChangeNum process.Now = score % coinConf.ChangeNum return } // UpdateScore 更新扭蛋币积分 func (d *Dao) UpdateScore(ctx context.Context, uid, coinId, score int64, action, platform string, coinConf *CapsuleCoinConf) (affect int64, err error) { var ( sqlStr, uKey, iKey string ) if action == CapsuleActionTrans { sqlStr = fmt.Sprintf(_transUserCapsuleMysql, getCapsuleTable(uid)) } else { sqlStr = fmt.Sprintf(_updateUserCapsuleMysql, getCapsuleTable(uid), CoinIdIntMap[coinId], CoinIdIntMap[coinId]) } conn := d.redis.Get(ctx) defer conn.Close() uKey = userKey(uid) iKey = userInfoKey(uid, coinId) log.Info("trace UpdateScore start") affect, err = d.execSqlWithBindParams(ctx, &sqlStr, score, uid) log.Info("trace UpdateScore end") if err != nil { log.Error("[dao.mysql_lottery|updateScore] uid(%d) type(%d) score(%d) error(%v)", uid, coinId, score, err) _, e := conn.Do("DEL", uKey, iKey) if e != nil { log.Error("[dao.redis_lottery|updateScore] conn.DEL(%s, %s) error(%v)", uKey, iKey, e) } return } _, e := conn.Do("DEL", uKey, iKey) if e != nil { log.Error("[dao.redis_lottery|updateScore] conn.DEL(%s, %s) error(%v)", uKey, iKey, e) } return } // UpdateCapsule 更新扭蛋币积分 func (d *Dao) UpdateCapsule(ctx context.Context, uid, coinId, score int64, action, platform string, coinConf *CapsuleCoinConf) (affect int64, err error) { var ( sqlStr, uKey, iKey string ) sqlStr = fmt.Sprintf(_updateUserInfoMysql, getCapsuleTable(uid)) conn := d.redis.Get(ctx) defer conn.Close() uKey = userKey(uid) iKey = userInfoKey(uid, coinId) log.Info("trace UpdateCapsule start") affect, err = d.execSqlWithBindParams(ctx, &sqlStr, score, uid, CoinIdIntMap[coinId]) log.Info("trace UpdateCapsule end") if err != nil { log.Error("[dao.mysql_lottery|UpdateCapsule] uid(%d) type(%d) score(%d) error(%v)", uid, coinId, score, err) _, e := conn.Do("DEL", uKey, iKey) if e != nil { log.Error("[dao.redis_lottery|UpdateCapsule] conn.DEL(%s, %s) error(%v)", uKey, iKey, e) } return } _, e := conn.Do("DEL", uKey, iKey) if e != nil { log.Error("[dao.redis_lottery|UpdateCapsule] conn.DEL(%s, %s) error(%v)", uKey, iKey, e) } return } // ReportCapsuleChange 扭蛋流水 func (d *Dao) ReportCapsuleChange(ctx context.Context, coinId, uid, score int64, action, platform string, pInfo, cInfo map[int64]int64, coinConf *CapsuleCoinConf) bool { if _, ok := pInfo[coinId]; !ok { return false } if _, ok := cInfo[coinId]; !ok { return false } chnageType := coinId change := d.GetCoin(cInfo[coinId], coinConf) - d.GetCoin(pInfo[coinId], coinConf) if change > 0 { d.AddNotice(ctx, uid, coinId, change) } var normalPreScore, colorPreScore, normalNowScore, colorNowScore int64 if coinId <= ColorfulCoinId { if action == CapsuleActionTrans { chnageType = 5 } else { if score < 0 { chnageType += 2 score = -score } } normalPreScore, colorPreScore, normalNowScore, colorNowScore = pInfo[NormalCoinId], pInfo[ColorfulCoinId], cInfo[NormalCoinId], cInfo[ColorfulCoinId] } else { chnageType = coinId * 10 if score < 0 { chnageType += 1 } normalPreScore, colorPreScore, normalNowScore, colorNowScore = 0, pInfo[coinId], 0, cInfo[coinId] } date := time.Now().Format("200601") sqlStr := fmt.Sprintf(_reportCapsuleChangeMysql, date) affect, _ := d.execSqlWithBindParams(ctx, &sqlStr, uid, chnageType, score, action, platform, normalPreScore, colorPreScore, normalNowScore, colorNowScore) var rcontent string if _, ok := ReprotConfig[chnageType]; ok { rcontent = ReprotConfig[chnageType] } if rcontent == "" { if chnageType%10 == 0 { rcontent = "减少" + coinConf.Title } else if chnageType%10 == 1 { rcontent = "增加" + coinConf.Title } else if chnageType%10 == 2 { rcontent = "转化" + coinConf.Title } } report.User(&report.UserInfo{ Platform: platform, Business: 101, // 101 102 103 104 105 106 Type: int(chnageType), Oid: uid, Action: "capsule_change", Ctime: time.Now(), Index: []interface{}{ rcontent, score, normalNowScore, colorNowScore, action, }, }) return affect > 0 } // PayCoin 支付扭蛋币 func (d *Dao) PayCoin(ctx context.Context, uid int64, coinConf *CapsuleCoinConf, openCount int64, action, platform string) (status int64, pInfo map[int64]int64, err error) { lockKey := fmt.Sprintf(_openLockRedis, uid) isGetLock, lockString, err := d.Lock(ctx, lockKey, 10000, 0, 0) if err != nil || !isGetLock { return } conn := d.redis.Get(ctx) defer conn.Close() userData, err := d.GetUserInfo(ctx, uid, coinConf.Id) if err != nil { d.UnLock(ctx, lockKey, lockString) return } var score int64 if _, ok := userData[coinConf.Id]; ok { score = userData[coinConf.Id] } coinNum := d.GetCoin(score, coinConf) if coinNum < openCount { d.UnLock(ctx, lockKey, lockString) return 1, userData, nil } value := openCount * coinConf.ChangeNum _, err = d.UpdateScore(ctx, uid, coinConf.Id, -value, action, platform, coinConf) if err != nil { d.UnLock(ctx, lockKey, lockString) return } d.UnLock(ctx, lockKey, lockString) return 0, userData, nil } // PayCapsule 支付扭蛋币 func (d *Dao) PayCapsule(ctx context.Context, uid int64, coinConf *CapsuleCoinConf, openCount int64, action, platform string) (status int64, pInfo map[int64]int64, err error) { lockKey := fmt.Sprintf(_openLockRedis, uid) isGetLock, lockString, err := d.Lock(ctx, lockKey, 10000, 0, 0) if err != nil { return } if !isGetLock { return 1, nil, nil } conn := d.redis.Get(ctx) defer conn.Close() userData, err := d.GetUserInfo(ctx, uid, coinConf.Id) if err != nil { d.UnLock(ctx, lockKey, lockString) return } var score int64 if _, ok := userData[coinConf.Id]; ok { score = userData[coinConf.Id] } coinNum := d.GetCoin(score, coinConf) if coinNum < openCount { d.UnLock(ctx, lockKey, lockString) return 2, userData, nil } value := openCount * coinConf.ChangeNum _, err = d.UpdateCapsule(ctx, uid, coinConf.Id, -value, action, platform, coinConf) if err != nil { d.UnLock(ctx, lockKey, lockString) return } d.UnLock(ctx, lockKey, lockString) return 0, userData, nil } // IsPoolOpen 判断扭蛋池是否开启 func (d *Dao) IsPoolOpen(coinConf *CapsuleCoinConf, coinId int64) bool { if coinConf == nil { return false } if coinConf.PoolConf == nil || len(coinConf.PoolConf.PoolPrize) == 0 { return false } now := time.Now().Unix() if coinConf.PoolConf.StartTime < now && coinConf.PoolConf.EndTime > now { return true } return false } // GetGift 获取扭蛋奖池奖品 func (d *Dao) GetGift(ctx context.Context, coinId int64) (gift []*CapsulePoolPrize, err error) { coinConfMap, err := d.GetCapsuleConf(ctx) if err != nil || len(coinConfMap) == 0 { return } if _, ok := coinConfMap[coinId]; !ok { return } conf := coinConfMap[coinId] if conf.PoolConf == nil || len(conf.PoolConf.PoolPrize) == 0 { return } gift = conf.PoolConf.PoolPrize return } // IncrOpenCount 增加开奖次数 func (d *Dao) IncrOpenCount(ctx context.Context, coinId int64) (cnt int64) { hkey := openTotalCountKey() conn := d.redis.Get(ctx) defer conn.Close() cnt, err := redis.Int64(conn.Do("HINCRBY", hkey, CoinIdIntMap[coinId], 1)) if err != nil { return } if cnt > 0 { return cnt } return 0 } // GetOpenCount 获取开奖次数 func (d *Dao) GetOpenCount(ctx context.Context, coinId int64) (cnt int64) { hkey := openTotalCountKey() conn := d.redis.Get(ctx) defer conn.Close() cnt, err := redis.Int64(conn.Do("HGET", hkey, CoinIdIntMap[coinId])) if err != nil { return 0 } if cnt > 0 { return cnt } return 0 } func getWhiteGift(coinConf *CapsuleCoinConf) (fixPrize []*CapsulePoolPrize) { if coinConf == nil || coinConf.PoolConf == nil || len(coinConf.PoolConf.PoolPrize) == 0 { return } fLen := 0 for _, prize := range coinConf.PoolConf.PoolPrize { if prize.ProType == ProTypeWhite { fLen++ } } if fLen <= 0 { return } fixPrize = make([]*CapsulePoolPrize, fLen) fLen = 0 for _, prize := range coinConf.PoolConf.PoolPrize { if prize.ProType == ProTypeWhite { fixPrize[fLen] = prize fLen++ } } return } func getFixGift(coinConf *CapsuleCoinConf) (fixPrize []*CapsulePoolPrize) { if coinConf == nil || coinConf.PoolConf == nil || len(coinConf.PoolConf.PoolPrize) == 0 { return } fLen := 0 for _, prize := range coinConf.PoolConf.PoolPrize { if prize.ProType == ProTypeFixDay || prize.ProType == ProTypeFixWeek { fLen++ } } if fLen <= 0 { return } fixPrize = make([]*CapsulePoolPrize, fLen) fLen = 0 for _, prize := range coinConf.PoolConf.PoolPrize { if prize.ProType == ProTypeFixDay || prize.ProType == ProTypeFixWeek { fixPrize[fLen] = prize fLen++ } } return } func getRandomGift(coinConf *CapsuleCoinConf) (randomPrize []*CapsulePoolPrize) { if coinConf == nil || coinConf.PoolConf == nil || len(coinConf.PoolConf.PoolPrize) == 0 { return } rLen := 0 for _, prize := range coinConf.PoolConf.PoolPrize { if prize.ProType == ProTypeNormal { rLen++ } } if rLen <= 0 { return } randomPrize = make([]*CapsulePoolPrize, rLen) rLen = 0 for _, prize := range coinConf.PoolConf.PoolPrize { if prize.ProType == ProTypeNormal { randomPrize[rLen] = prize rLen++ } } return } func (d *Dao) checkWhiteLimit(ctx context.Context, uid int64, prize *CapsulePoolPrize) bool { if prize == nil || prize.ProType != ProTypeWhite || len(prize.WhiteUserMap) == 0 { return false } if _, ok := prize.WhiteUserMap[uid]; !ok { return false } conn := d.redis.Get(ctx) defer conn.Close() dtime := time.Now() day := dtime.Format("2006-01-02") uKey := whiteUserPrizeKey(uid) lastTime, err := redis.Int64(conn.Do("GET", uKey)) if err != nil { if err == redis.ErrNil { // 回源数据库 prizeLog, err := d.GetUserPrizeLog(ctx, prize.Id, uid) if err != nil { return false } if prizeLog != nil { lastTime = prizeLog.Timestamp } else { lastTime = 0 } conn.Do("SET", uKey, lastTime, "EX", 30*86400) } else { return false } } if dtime.Unix()-lastTime < 7*86400 { return false } gKey := openGiftCountKey(prize.Id, day) isGetLock, lockString, errLock := d.Lock(ctx, gKey, 1000000, 0, 0) if errLock != nil || !isGetLock { return false } mKey := day + strconv.FormatInt(prize.Id, 10) _, ok := whitePrizeMap.Load(mKey) if ok { d.UnLock(ctx, gKey, lockString) return false } // 回源数据库 prizeLog, errDb := d.GetPrizeDayLog(ctx, prize.Id, day) if errDb != nil { d.UnLock(ctx, gKey, lockString) return false } if prizeLog != nil { whitePrizeMap.Store(mKey, prizeLog.Uid) d.UnLock(ctx, gKey, lockString) return false } stutus, errAdd := d.AddPrizeData(ctx, prize.Id, uid, day, dtime.Unix()) if errAdd != nil || !stutus { d.UnLock(ctx, gKey, lockString) return false } whitePrizeMap.Store(mKey, uid) conn.Do("DEL", uKey) d.UnLock(ctx, gKey, lockString) return true } func (d *Dao) checkPrizeLimit(ctx context.Context, prize *CapsulePoolPrize) bool { if prize == nil { return false } if prize.ProType != ProTypeFixDay && prize.ProType != ProTypeFixWeek { return false } conn := d.redis.Get(ctx) defer conn.Close() var status bool switch prize.ProType { case ProTypeFixDay: day := time.Now().Format("2006-01-02") gKey := openGiftCountKey(prize.Id, day) cnt, err := redis.Int64(conn.Do("INCRBY", gKey, 1)) if err != nil { status = false break } status = cnt <= prize.LimitNum case ProTypeFixWeek: wDay := time.Now().Weekday() if wDay == 0 { wDay = 7 } diff := time.Duration(wDay) day := time.Now().Add(time.Second * 86400 * (diff - 1)).Format("2006-01-02") gKey := openGiftCountKey(prize.Id, day) cnt, err := redis.Int64(conn.Do("INCRBY", gKey, 1)) if err != nil { status = false break } status = cnt <= prize.LimitNum default: status = false } return status } func (d *Dao) getWhiteAward(ctx context.Context, uid int64, coinConf *CapsuleCoinConf) (prize *CapsulePoolPrize) { whitePrize := getWhiteGift(coinConf) if len(whitePrize) == 0 { return } openCount := d.GetOpenCount(ctx, coinConf.Id) if openCount == 0 { return } for _, wprize := range whitePrize { if !d.checkWhiteLimit(ctx, uid, wprize) { continue } return &CapsulePoolPrize{ Id: wprize.Id, PoolId: wprize.PoolId, Type: wprize.Type, Num: wprize.Num, Name: wprize.Name, WebImage: wprize.WebImage, MobileImage: wprize.MobileImage, Description: wprize.Description, ProType: wprize.ProType, JumpUrl: wprize.JumpUrl, ObjectId: wprize.ObjectId, Expire: wprize.Expire, Weight: wprize.Weight, } } return } func (d *Dao) getFixAward(ctx context.Context, uid, openCount int64, coinConf *CapsuleCoinConf) (prize *CapsulePoolPrize) { fixPrize := getFixGift(coinConf) if len(fixPrize) == 0 { return } if openCount == 0 { return } var loop int64 for _, fprize := range fixPrize { loop = fprize.LoopNum if openCount%loop != 0 { continue } if d.checkPrizeLimit(ctx, fprize) { return &CapsulePoolPrize{ Id: fprize.Id, PoolId: fprize.PoolId, Type: fprize.Type, Num: fprize.Num, Name: fprize.Name, WebImage: fprize.WebImage, MobileImage: fprize.MobileImage, Description: fprize.Description, ProType: fprize.ProType, JumpUrl: fprize.JumpUrl, ObjectId: fprize.ObjectId, Expire: fprize.Expire, Weight: fprize.Weight, } } } return } func (d *Dao) getRandomAward(ctx context.Context, uid int64, coinConf *CapsuleCoinConf) (prize *CapsulePoolPrize) { randomPrize := getRandomGift(coinConf) if len(randomPrize) == 0 { return } var start, random, total int64 for _, prize := range randomPrize { if prize == nil { rbyte, _ := json.Marshal(randomPrize) log.Error("[dao.capsule | getRandomAward] randomPrize error : %s", string(rbyte)) continue } total += prize.Chance } r := rand.New(rand.NewSource(time.Now().UnixNano())) random = r.Int63n(total - 1) for _, prize := range randomPrize { if random >= start && random < start+prize.Chance { return &CapsulePoolPrize{ Id: prize.Id, PoolId: prize.PoolId, Type: prize.Type, Num: prize.Num, Name: prize.Name, WebImage: prize.WebImage, MobileImage: prize.MobileImage, Description: prize.Description, ProType: prize.ProType, JumpUrl: prize.JumpUrl, ObjectId: prize.ObjectId, Expire: prize.Expire, Weight: prize.Weight, } } start += prize.Chance } return } // OpenCapsule 开启扭蛋 func (d *Dao) OpenCapsule(ctx context.Context, uid int64, coinConf *CapsuleCoinConf, iTime, openCount int64, isGetFixAward bool, entryMap map[int64]bool) (award *CapsulePoolPrize) { if iTime == 0 { award = d.getWhiteAward(ctx, uid, coinConf) if award != nil { return award } } if isGetFixAward { award = d.getFixAward(ctx, uid, openCount, coinConf) if award != nil { if _, ok := entryMap[award.Id]; !ok { return } } } award = d.getRandomAward(ctx, uid, coinConf) if award != nil { return } return } // LogAward 记录抽奖奖励 func (d *Dao) LogAward(ctx context.Context, uid int64, coinId int64, awards []*CapsulePoolPrize) { if len(awards) == 0 { return } hkey := openHistoryKey(CoinIdIntMap[coinId]) day := time.Now().Format("2006-01-02") logs := make([]interface{}, len(awards)+1) logs[0] = hkey ll := 1 for _, award := range awards { if award.Type == CapsulePrizeGift1Type { continue } info := HistoryOpenInfo{Uid: uid, Name: award.Name, Num: award.Num, Date: day} b, err := json.Marshal(info) if err == nil { logs[ll] = string(b) ll++ } } if ll == 1 { return } logs = logs[0:ll] conn := d.redis.Get(ctx) defer conn.Close() conn.Do("LPUSH", logs...) } // AddNotice 增加标记 func (d *Dao) AddNotice(ctx context.Context, uid, coinId, coinNum int64) { nKey := fmt.Sprintf(_capsuleNotice, CoinIdIntMap[coinId], uid) conn := d.redis.Get(ctx) defer conn.Close() conn.Do("SET", nKey, coinNum, 30*86400) } // ClearNotice 清除标记 func (d *Dao) ClearNotice(ctx context.Context, uid, coinId int64) { nKey := fmt.Sprintf(_capsuleNotice, CoinIdIntMap[coinId], uid) conn := d.redis.Get(ctx) defer conn.Close() conn.Do("DEL", nKey) } // ClearNoticeBoth 清除标记 func (d *Dao) ClearNoticeBoth(ctx context.Context, uid int64) { keys := make([]interface{}, len(CoinIdIntMap)) var i = 0 for _, coinType := range CoinIdIntMap { keys[i] = fmt.Sprintf(_capsuleNotice, coinType, uid) } conn := d.redis.Get(ctx) defer conn.Close() conn.Do("DEL", keys...) } // GetChangeNum 获取扭蛋变化数量 func (d *Dao) GetChangeNum(ctx context.Context, uid, coinId int64) int64 { nKey := fmt.Sprintf(_capsuleNotice, CoinIdIntMap[coinId], uid) conn := d.redis.Get(ctx) defer conn.Close() change, err := redis.Int64(conn.Do("GET", nKey)) if err != nil { return 0 } return change } // SetCapsuleChangeFlag 设置扭蛋配置变化标记 func (d *Dao) SetCapsuleChangeFlag(ctx context.Context) (status string, err error) { conn := d.redis.Get(ctx) defer conn.Close() status, err = redis.String(conn.Do("SET", _capsuleConfRand, time.Now().Unix())) if err != nil { log.Error("[dao.capsule | SetCapsuleChangeFlag] redis set error : %v", err) return } return } // GetCapsuleChangeFlag 获取扭蛋配置变化标记 func (d *Dao) GetCapsuleChangeFlag(ctx context.Context) (changeFlag int64, err error) { conn := d.redis.Get(ctx) defer conn.Close() changeFlag, err = redis.Int64(conn.Do("GET", _capsuleConfRand)) if err != nil { return } return } // GetCapsuleChangeInfo 获取扭蛋配置信息 func (d *Dao) GetCapsuleChangeInfo(ctx context.Context) (int64, int64) { capsuleConf.RwLock.RLock() CacheTime := capsuleConf.CacheTime ChangeFlag := capsuleConf.ChangeFlag capsuleConf.RwLock.RUnlock() return CacheTime, ChangeFlag } // RelaodCapsuleConfig 重新加载扭蛋配置 func (d *Dao) RelaodCapsuleConfig(ctx context.Context, changeFlag int64) (conf map[int64]*CapsuleCoinConf, err error) { coinMap, err := d.GetCoinMap(ctx) if err != nil || len(coinMap) == 0 { log.Error("[dao.capsule | RelaodCapsuleConfig] coinMap is empty") return } coinIds := make([]int64, len(coinMap)) ix := 0 for _, coinInfo := range coinMap { coinIds[ix] = coinInfo.Id ix++ } coinConfigMap, err := d.GetCoinConfigMap(ctx, coinIds) if err != nil || len(coinConfigMap) == 0 { log.Error("[dao.capsule | RelaodCapsuleConfig] CoinConfigMap is empty") return } poolMap, err := d.GetPoolMap(ctx, coinIds) if err != nil || len(poolMap) == 0 { log.Error("[dao.capsule | RelaodCapsuleConfig] PoolMap is empty") return } poolIds := make([]int64, 0) for _, pools := range poolMap { for _, pool := range pools { poolIds = append(poolIds, pool.Id) } } poolPrizeMap, err := d.GetPoolPrizeMap(ctx, poolIds) if err != nil || len(poolPrizeMap) == 0 { log.Error("[dao.capsule | RelaodCapsuleConfig] PoolPrizeMap is empty") return } coinConfMap := make(map[int64]*CapsuleCoinConf) ids := make([]int64, 0) prizeIds := make([]int64, 0) idMap := make(map[int64]struct{}) for _, prizeList := range poolPrizeMap { for _, prize := range prizeList { if prize.ObjectId != 0 && prize.Type == CapsulePrizeTitleType { if _, ok := idMap[prize.ObjectId]; !ok { ids = append(ids, prize.ObjectId) idMap[prize.ObjectId] = struct{}{} } } prizeIds = append(prizeIds, prize.Id) } } prizeWhiteMap, err1 := d.GetWhiteUserMap(ctx, prizeIds) if err1 != nil { log.Error("[dao.capsule | RelaodCapsuleConfig] GetWhiteUserMap error") } titleMap := make(map[int64]string) if len(ids) != 0 { TitleData, err1 := RcApi.V1UserTitle.GetTitleByIds(ctx, &v12.UserTitleGetTitleByIdsReq{Ids: ids}) if err1 != nil { log.Error("[dao.capsule | RelaodCapsuleConfig] GetTitleByIds error") } if TitleData != nil && TitleData.Data != nil { titleMap = TitleData.Data } } for coinId, coinConf := range coinMap { conf := &CapsuleCoinConf{} conf.Status = coinConf.Status conf.GiftType = coinConf.GiftType conf.Title = coinConf.Title conf.EndTime = coinConf.EndTime conf.StartTime = coinConf.StartTime conf.Id = coinConf.Id conf.ChangeNum = coinConf.ChangeNum if _, ok := coinConfigMap[coinId]; ok { coinConfig := coinConfigMap[coinId] gifts := make(map[int64]struct{}) areas := make(map[int64]struct{}) for _, config := range coinConfig { if config.GiftId > 0 { gifts[config.GiftId] = struct{}{} } if config.AreaV2ParentId > 0 { areas[config.AreaV2Id] = struct{}{} } } conf.AreaMap = areas conf.GiftMap = gifts } if _, ok := poolMap[coinId]; ok { for _, poolConf := range poolMap[coinId] { pool := &CapsulePoolConf{} pool.Id = poolConf.Id pool.StartTime = poolConf.StartTime pool.EndTime = poolConf.EndTime pool.Title = poolConf.Title pool.Status = poolConf.Status pool.Rule = poolConf.Description pool.CoinId = poolConf.CoinId pool.IsBottom = poolConf.IsBottom if _, ok := poolPrizeMap[pool.Id]; ok { prizeConfigs := poolPrizeMap[pool.Id] pool.PoolPrize = make([]*CapsulePoolPrize, len(poolPrizeMap[pool.Id])) for ix, prizeConfig := range prizeConfigs { name := PrizeNameMap[prizeConfig.Type] if prizeConfig.Type == CapsulePrizeTitleType && titleMap != nil { if _, ok := titleMap[prizeConfig.ObjectId]; ok { name = titleMap[prizeConfig.ObjectId] } } prize := &CapsulePoolPrize{ Id: prizeConfig.Id, PoolId: prizeConfig.PoolId, Type: prizeConfig.Type, Num: prizeConfig.Num, ObjectId: prizeConfig.ObjectId, Expire: prizeConfig.Expire, Name: name, WebImage: prizeConfig.WebUrl, MobileImage: prizeConfig.MobileUrl, Description: prizeConfig.Description, JumpUrl: prizeConfig.JumpUrl, ProType: prizeConfig.ProType, Chance: prizeConfig.Chance, LoopNum: prizeConfig.LoopNum, LimitNum: prizeConfig.LimitNum, Weight: prizeConfig.Weight, } prize.WhiteUserMap = make(map[int64]struct{}) if prize.ProType == ProTypeWhite { if _, ok := prizeWhiteMap[prize.Id]; ok { if len(prizeWhiteMap[prize.Id]) > 0 { for _, wuid := range prizeWhiteMap[prize.Id] { prize.WhiteUserMap[wuid] = struct{}{} } } } } pool.PoolPrize[ix] = prize } } if conf.AllPoolConf == nil { conf.AllPoolConf = make([]*CapsulePoolConf, 0) } conf.AllPoolConf = append(conf.AllPoolConf, pool) } } coinConfMap[coinId] = conf } cacheTime := time.Now().Unix() capsuleConf.RwLock.Lock() capsuleConf.CacheTime = cacheTime capsuleConf.ChangeFlag = changeFlag capsuleConf.CoinConfMap = coinConfMap capsuleConf.RwLock.Unlock() log.Info("[dao.capsule | RelaodCapsuleConfig] reload conf") return coinConfMap, nil } // GetBottomPrize 获取保底奖品 func (d *Dao) GetBottomPrize(ctx context.Context, coinConf *CapsuleCoinConf) (bottomPrize *CapsulePoolPrize) { if coinConf == nil || coinConf.PoolConf == nil || len(coinConf.PoolConf.PoolPrize) == 0 { return nil } for _, prize := range coinConf.PoolConf.PoolPrize { if bottomPrize == nil || bottomPrize.Weight > prize.Weight { bottomPrize = &CapsulePoolPrize{ Id: prize.Id, PoolId: prize.PoolId, Type: prize.Type, Name: prize.Name, WebImage: prize.WebImage, MobileImage: prize.MobileImage, Description: prize.Description, ProType: prize.ProType, JumpUrl: prize.JumpUrl, ObjectId: prize.ObjectId, Expire: prize.Expire, Weight: prize.Weight, Num: prize.Num, } } } return bottomPrize } //GetExpireTime 获取过期时间 func (d *Dao) GetExpireTime(expire int64) time.Time { var td time.Time if expire == CapsulePrizeExpire1Day { year, month, day := time.Now().Date() td = time.Date(year, month, day, 0, 0, 0, 0, time.Now().Location()).Add(86400 * time.Second).Add(86400 * time.Second) } else if expire == CapsulePrizeExpire3Day { year, month, day := time.Now().Date() td = time.Date(year, month, day, 0, 0, 0, 0, time.Now().Location()).Add(86400 * time.Second).Add(3 * 86400 * time.Second) } else if expire == CapsulePrizeExpire1Week { year, month, day := time.Now().Date() td = time.Date(year, month, day, 0, 0, 0, 0, time.Now().Location()).Add(86400 * time.Second).Add(6 * 86400 * time.Second) } else if expire == CapsulePrizeExpire3Month { year, month, day := time.Now().Date() td = time.Date(year, month, day, 0, 0, 0, 0, time.Now().Location()).Add(86400 * time.Second).Add(90 * 86400 * time.Second) } else if expire == CapsulePrizeExpireForever { td = time.Unix(0, 0) } else { td = time.Unix(0, 0) } return td } // IsAwardEntry 是否是实物奖励 func (d *Dao) IsAwardEntry(awardType int64) bool { if awardType == CapsulePrizeSmallTvType || awardType == CapsulePrizeLplProduct1 || awardType == CapsulePrizeLplProduct2 || awardType == CapsulePrizeLplProduct3 { return true } if awardType >= CapsulePrizeProduct1 && awardType < CapsulePrizeCoupon1 { return true } return false } // IsAwardCoupon 是否是会员券 func (d *Dao) IsAwardCoupon(awardType int64) bool { return awardType >= CapsulePrizeCoupon1 }