package dao import ( "context" "fmt" "strconv" "strings" "sync" "time" "github.com/pkg/errors" v12 "go-common/app/service/live/rc/api/liverpc/v1" "go-common/library/cache/redis" "go-common/app/job/live/xlottery/internal/model" "go-common/library/database/sql" "go-common/library/log" "go-common/library/queue/databus/report" ) // NormalCoinId 普通扭蛋id const NormalCoinId = 1 // ColorfulCoinId 梦幻扭蛋id const ColorfulCoinId = 2 // WeekCoinId 周星扭蛋id const WeekCoinId = 3 // LplCoinId 周星扭蛋id const LplCoinId = 4 // BlessCoinId 祈福抽奖券 const BlessCoinId = 5 // IsBottomPool 是否为保底奖池 const IsBottomPool = 1 // CapsuleActionTrans 转换扭蛋币 const CapsuleActionTrans = "trans" const ( // NormalCoinString 普通扭蛋字符串标识,数据库和redis NormalCoinString = "normal" // ColorfulCoinString 梦幻扭蛋字符串标识,数据库和redis ColorfulCoinString = "colorful" // WeekCoinString 周星扭蛋字符串标识,数据库和redis WeekCoinString = "week" // LplCoinString 周星扭蛋字符串标识,数据库和redis LplCoinString = "lpl" // BlessCoinString 祈福扭蛋字符串标识,数据库和redis BlessCoinString = "bless" ) const ( _capsuleNotice = "capsule:notice:%s:%d" _userCapsuleCoinRedis = "hash:capsule:user:%d" _userInfoRedis = "capsule:user:info:%d:%d" _capsuleConfRand = "capsule:rand" _lplSendGiftRedis = "capsule:lpl:send:gift:%s:%d" ) const ( _ = iota // CapsulePrizeGift1Type 辣条 CapsulePrizeGift1Type // 辣条 // CapsulePrizeTitleType 头衔 CapsulePrizeTitleType // CapsulePrizeStuff1Type 经验原石 CapsulePrizeStuff1Type // CapsulePrizeStuff2Type 经验曜石 CapsulePrizeStuff2Type // CapsulePrizeStuff3Type 贤者之石 CapsulePrizeStuff3Type // CapsulePrizeSmallTvType 小电视 CapsulePrizeSmallTvType // CapsulePrizeGuard3Type 舰长体验 CapsulePrizeGuard3Type // CapsulePrizeGuard2Type 提督体验 CapsulePrizeGuard2Type // CapsulePrizeGuard1Type 总督体验 CapsulePrizeGuard1Type ) const ( _ = iota // CapsuleGiftTypeAll gift_type 为全部道具 CapsuleGiftTypeAll // CapsuleGiftTypeGold gift_type 为金瓜子道具 CapsuleGiftTypeGold // CapsuleGiftTypeSelected gift_type 为指定道具 CapsuleGiftTypeSelected ) const ( _getActiveColorPool = "SELECT id, coin_id, title, rule, start_time, end_time, status FROM capsule_pool WHERE status = 1 AND coin_id = ?" _getCapsuleMaxId = "SELECT id FROM capsule_%s order by id desc limit 1" _transCapsule = "UPDATE capsule_%s SET normal_score = normal_score + ?,colorful_score = 0 WHERE uid = ?" _getChangeNum = "SELECT change_num FROM capsule_coin WHERE id = ?" _getUserInfoById = "SELECT uid,normal_score,colorful_score FROM capsule_%s WHERE id >= ? AND id < ? and colorful_score > 0" _getUserInfoByUids = "SELECT uid,normal_score,colorful_score FROM capsule_%s WHERE uid in(%s)" _getOnCoin = "SELECT id, title, gift_type, change_num, start_time, end_time, status FROM capsule_coin WHERE status = 1" _getCoinConfigMap = "SELECT coin_id, type, area_v2_parent_id, area_v2_id, gift_id FROM capsule_coin_config WHERE coin_id in (%v) AND status = 1" _getPoolMap = "SELECT id, coin_id, title, rule, start_time, end_time, status, is_bottom FROM capsule_pool WHERE status = 1 and coin_id in (%v)" _getPoolPrizeMap = "SELECT id, pool_id, type, num, object_id,expire, web_url, mobile_url, description, jump_url, pro_type, chance, loop_num, limit_num, weight FROM capsule_pool_prize WHERE pool_id in (%s) and status = 1 order by ctime" _reportCapsuleChangeMysql = "insert into capsule_log_%s(uid, type, score, action, platform, pre_normal_score, pre_colorful_score, cur_normal_score, cur_colorful_score) values(?,?,?,?,?,?,?,?,?)" _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(?, ?, ?)" _transUserCapsuleMysql = "update capsule_%d set colorful_score = 0, normal_score = normal_score + ? where uid = ?" _updateUserCapsuleMysql = "update capsule_%d set %s_score = %s_score + ? where uid = ?" _updateUserInfoMysql = "update capsule_info_%d set score = score + ? where uid = ? and type = ?" _getExtraDataMysql = "select item_value, item_extra from capsule_extra_data where uid = ? and type = ?" _addExtraDataMysql = "insert into capsule_extra_data(uid,type,item_value,item_extra) values(?,?,?,?)" _getExtraDataByTimeMysql = "select id, uid, type, item_value, item_extra from capsule_extra_data where mtime >= ? and mtime < ?" _updateExtraValueMysql = "update capsule_extra_data set item_value = ? where id = ?" _updateExtraMtimeMysql = "update capsule_extra_data set mtime = ? where id = ?" _updateExtraMysql = "update capsule_extra_data set item_value = ?, item_extra = ? where id = ?" _getExtraDataMaxIdMysql = "select id from capsule_extra_data order by id desc limit 1" _getExtraDataByIdMysql = "select id, uid, type, item_value, item_extra from capsule_extra_data where id >= ? and id < ?" ) var ( capsuleConf CapsuleConf // ReprotConfig map ReprotConfig map[int64]string // CoinIdIntMap map CoinIdIntMap map[int64]string // PrizeNameMap map PrizeNameMap map[int64]string ) var ( // UnLockGetWrong flag UnLockGetWrong = "UnLockGetWrong" // ErrUnLockGet error ErrUnLockGet = errors.New(UnLockGetWrong) ) // 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 } func init() { CoinIdIntMap = make(map[int64]string) CoinIdIntMap[NormalCoinId] = NormalCoinString CoinIdIntMap[ColorfulCoinId] = ColorfulCoinString CoinIdIntMap[WeekCoinId] = WeekCoinString CoinIdIntMap[LplCoinId] = LplCoinString CoinIdIntMap[BlessCoinId] = BlessCoinString PrizeNameMap = make(map[int64]string) PrizeNameMap[CapsulePrizeGift1Type] = "辣条" PrizeNameMap[CapsulePrizeTitleType] = "头衔" PrizeNameMap[CapsulePrizeStuff1Type] = "经验原石" PrizeNameMap[CapsulePrizeStuff2Type] = "经验曜石" PrizeNameMap[CapsulePrizeStuff3Type] = "贤者之石" PrizeNameMap[CapsulePrizeSmallTvType] = "小电视抱枕" PrizeNameMap[CapsulePrizeGuard3Type] = "舰长体验" PrizeNameMap[CapsulePrizeGuard2Type] = "提督体验" ReprotConfig = make(map[int64]string) ReprotConfig[0] = "未知" ReprotConfig[1] = "增加普通扭蛋" ReprotConfig[2] = "增加梦幻扭蛋" ReprotConfig[3] = "减少普通扭蛋" ReprotConfig[4] = "减少梦幻扭蛋" ReprotConfig[5] = "梦幻转化普通" } // GetActiveColorPool 获取奖池 func (d *Dao) GetActiveColorPool(ctx context.Context) (pool []*model.Pool, err error) { var rows *sql.Rows if rows, err = d.db.Query(ctx, _getActiveColorPool, ColorfulCoinId); err != nil { log.Error("[dao.capsule | GetActiveColorPool]query(%s) error(%v)", _getActiveColorPool, err) return } defer rows.Close() for rows.Next() { p := &model.Pool{} if err = rows.Scan(&p.Id, &p.CoinId, &p.Title, &p.Description, &p.StartTime, &p.EndTime, &p.Status); err != nil { log.Error("[dao.capsule | GetActiveColorPool] scan error, err %v", err) return } pool = append(pool, p) } return } // GetUserInfoById 获取扭蛋币信息 func (d *Dao) GetUserInfoById(ctx context.Context, table string, start int64, end int64) (infos [][4]int64, err error) { var rows *sql.Rows sqlStr := fmt.Sprintf(_getUserInfoById, table) if rows, err = d.db.Query(ctx, sqlStr, start, end); err != nil { log.Error("[dao.capsule | GetUserInfos]query(%s) error(%v)", _getUserInfoById, err) return } defer rows.Close() infos = make([][4]int64, 0) for rows.Next() { p := &model.UserInfo{} if err = rows.Scan(&p.Uid, &p.NormalScore, &p.ColorfulScore); err != nil { log.Error("[dao.capsule | GetUserInfos] scan error, err %v", err) return } infos = append(infos, [4]int64{p.Uid, p.NormalScore, p.ColorfulScore, 0}) } return } // GetUserInfoByUids 获取扭蛋币信息 func (d *Dao) GetUserInfoByUids(ctx context.Context, table string, uids []int64) (infos map[int64][2]int64, err error) { var rows *sql.Rows uidStrArray := make([]string, len(uids)) for ix, uid := range uids { uidStrArray[ix] = strconv.FormatInt(uid, 10) } uidStr := strings.Join(uidStrArray, ",") sqlStr := fmt.Sprintf(_getUserInfoByUids, table, uidStr) if rows, err = d.db.Query(ctx, sqlStr); err != nil { log.Error("[dao.capsule | GetUserInfoByUids]query(%s) error(%v)", _getUserInfoByUids, err) return } defer rows.Close() infos = make(map[int64][2]int64) for rows.Next() { p := &model.UserInfo{} if err = rows.Scan(&p.Uid, &p.NormalScore, &p.ColorfulScore); err != nil { log.Error("[dao.capsule | GetUserInfoByUids] scan error, err %v", err) return } infos[p.Uid] = [2]int64{p.NormalScore, p.ColorfulScore} } return } // GetTransNum 获取扭蛋币数量 func (d *Dao) GetTransNum(ctx context.Context, coinId int64) (changeNum int64, err error) { var rows *sql.Rows if rows, err = d.db.Query(ctx, _getChangeNum, coinId); err != nil { log.Error("[dao.capsule | GetTransNum]query(%s) error(%v)", _getChangeNum, err) return } defer rows.Close() for rows.Next() { p := &model.Coin{} if err = rows.Scan(&p.ChangeNum); err != nil { log.Error("[dao.capsule | GetTransNum]scan error, err %v", err) return } changeNum = p.ChangeNum } return } // TransCapsule 转换扭蛋币 func (d *Dao) TransCapsule(ctx context.Context, table string, colorChangeNum int64, normalChangeNum int64) (err error) { var maxId int64 row := d.db.QueryRow(ctx, fmt.Sprintf(_getCapsuleMaxId, table)) if err = row.Scan(&maxId); err != nil { log.Error("[dao.capsule | TransCapsule] query(%s),err(%v)", _getCapsuleMaxId, err) return } log.Info("[dao.capsule | TransCapsule] table: %s, max: %d", table, maxId) conn := d.redis.Get(ctx) defer conn.Close() var i int64 for i = 0; i <= maxId; i = i + 1000 { var userInfos [][4]int64 userInfos, err = d.GetUserInfoById(ctx, table, i, i+1000) if err != nil { log.Error("[dao.capsule | TransCapsule] GetUserInfos error %v", err) return err } ulen := len(userInfos) if ulen < 1 { continue } uids := make([]int64, ulen) for ix, userInfo := range userInfos { uid := userInfo[0] changeScore := userInfo[2] - userInfo[2]%normalChangeNum _, err = d.db.Exec(ctx, fmt.Sprintf(_transCapsule, table), changeScore, uid) if err != nil { log.Error("[dao.capsule | TransCapsule]query(%s) error(%v)", _transCapsule, err) return } userInfos[ix][3] = changeScore uids[ix] = uid uKey := userKey(uid) _, e := conn.Do("DEL", uKey) if e != nil { log.Error("redis_lottery|delete score error,%v, uid %v", e, uid) } } var userMap map[int64][2]int64 userMap, err = d.GetUserInfoByUids(ctx, table, uids) if err != nil { log.Error("[dao.capsule | TransCapsule] GetUserInfoByUids error %v", err) return err } if userMap == nil { continue } for _, userInfo := range userInfos { changeScore := userInfo[3] uid := userInfo[0] if _, ok := userMap[uid]; !ok { continue } report.User(&report.UserInfo{ Business: 101, Type: int(1), Oid: uid, Action: "capsule_change", Ctime: time.Now(), Index: []interface{}{ "梦幻转化普通", changeScore, userMap[uid][0], userMap[uid][1], "trans", }, }) date := time.Now().Format("200601") sqlStr := fmt.Sprintf(_reportCapsuleChangeMysql, date) _, err := d.execSqlWithBindParams(ctx, &sqlStr, uid, 1, changeScore, "trans", "", userInfo[1], userInfo[2], userMap[uid][0], userMap[uid][1]) if err != nil { log.Error("[dao.capsule | TransCapsule] AddCapsuleLog error %v", err) continue } } } return } func userKey(uid int64) string { return fmt.Sprintf(_userCapsuleCoinRedis, uid) } func userInfoKey(uid int64, coinId int64) string { return fmt.Sprintf(_userInfoRedis, uid, coinId) } // GetCapsuleChangeFlag 获取扭蛋配置变化标记 func (d *Dao) GetCapsuleChangeFlag(ctx context.Context) (randNum int64, err error) { conn := d.redis.Get(ctx) defer conn.Close() randNum, err = redis.Int64(conn.Do("GET", _capsuleConfRand)) if err != nil { log.Error("[dao.redis_lottery|GetCapsuleChangeFlag] conn.GET(%s) error(%v)", _capsuleConfRand, err) return } return } // 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("[dao.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 } //GetCoinMap 批量获取扭蛋币 func (d *Dao) GetCoinMap(ctx context.Context) (coinMap map[int64]*model.Coin, err error) { var rows *sql.Rows if rows, err = d.db.Query(ctx, _getOnCoin); err != nil { log.Error("[dao.coin | GetCoinMap] query(%s) error(%v)", _getOnCoin, err) return } defer rows.Close() coinMap = make(map[int64]*model.Coin) for rows.Next() { p := &model.Coin{} if err = rows.Scan(&p.Id, &p.Title, &p.GiftType, &p.ChangeNum, &p.StartTime, &p.EndTime, &p.Status); err != nil { log.Error("[dao.coin | GetCoinMap] scan error, err %v", err) return } coinMap[p.Id] = p } return } //GetCoinConfigMap 批量获取扭蛋币 func (d *Dao) GetCoinConfigMap(ctx context.Context, coinIds []int64) (configMap map[int64][]*model.CoinConfig, err error) { var rows *sql.Rows stringCoinIds := make([]string, 0) for _, coinId := range coinIds { stringCoinIds = append(stringCoinIds, strconv.FormatInt(coinId, 10)) } coinString := strings.Join(stringCoinIds, ",") if rows, err = d.db.Query(ctx, fmt.Sprintf(_getCoinConfigMap, coinString)); err != nil { log.Error("[dao.coin_config | GetCoinConfigMap]query(%s) error(%v)", _getCoinConfigMap, err) return } defer rows.Close() configMap = make(map[int64][]*model.CoinConfig) for rows.Next() { d := &model.CoinConfig{} if err = rows.Scan(&d.CoinId, &d.Type, &d.AreaV2ParentId, &d.AreaV2Id, &d.GiftId); err != nil { log.Error("[dao.coin_config | GetCoinConfigMap] scan error, err %v", err) return } configMap[d.CoinId] = append(configMap[d.CoinId], d) } return } //GetPoolMap 批量奖池信息 func (d *Dao) GetPoolMap(ctx context.Context, coinIds []int64) (poolMap map[int64][]*model.Pool, err error) { var rows *sql.Rows stringCoinIds := make([]string, 0) for _, coinId := range coinIds { stringCoinIds = append(stringCoinIds, strconv.FormatInt(coinId, 10)) } coinString := strings.Join(stringCoinIds, ",") if rows, err = d.db.Query(ctx, fmt.Sprintf(_getPoolMap, coinString)); err != nil { log.Error("[dao.pool | GetPoolMap]query(%s) error(%v)", _getPoolMap, err) return } defer rows.Close() poolMap = make(map[int64][]*model.Pool) for rows.Next() { d := &model.Pool{} if err = rows.Scan(&d.Id, &d.CoinId, &d.Title, &d.Description, &d.StartTime, &d.EndTime, &d.Status, &d.IsBottom); err != nil { log.Error("[dao.pool |GetPoolMap] scan error, err %v", err) return } if _, ok := poolMap[d.CoinId]; !ok { poolMap[d.CoinId] = make([]*model.Pool, 0) } poolMap[d.CoinId] = append(poolMap[d.CoinId], d) } return } // GetPoolPrizeMap 批量奖池奖品 func (d *Dao) GetPoolPrizeMap(ctx context.Context, poolIds []int64) (poolPrizeMap map[int64][]*model.PoolPrize, err error) { var rows *sql.Rows stringPoolIds := make([]string, 0) for _, poolId := range poolIds { stringPoolIds = append(stringPoolIds, strconv.FormatInt(poolId, 10)) } poolString := strings.Join(stringPoolIds, ",") if rows, err = d.db.Query(ctx, fmt.Sprintf(_getPoolPrizeMap, poolString)); err != nil { log.Error("[dao.pool_prize | GetPoolPrizeMap] query(%s) error(%v)", _getPoolPrizeMap, err) return } defer rows.Close() poolPrizeMap = make(map[int64][]*model.PoolPrize) for rows.Next() { d := &model.PoolPrize{} if err = rows.Scan(&d.Id, &d.PoolId, &d.Type, &d.Num, &d.ObjectId, &d.Expire, &d.WebUrl, &d.MobileUrl, &d.Description, &d.JumpUrl, &d.ProType, &d.Chance, &d.LoopNum, &d.LimitNum, &d.Weight); err != nil { log.Error("[dao.pool_prize | GetPoolPrizeMap] scan error, err %v", err) return } if _, ok := PrizeNameMap[d.Type]; !ok { continue } poolPrizeMap[d.PoolId] = append(poolPrizeMap[d.PoolId], d) } return } // 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) 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{}{} } } } } 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, } 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 } func getCapsuleTable(Uid int64) int64 { return Uid % 10 } // UpdateScore 更新扭蛋币积分 func (d *Dao) UpdateScore(ctx context.Context, uid, coinId, score int64, action, platform string, userInfo map[int64]int64, coinConf *CapsuleCoinConf) (affect int64, err error) { var ( sqlStr, uKey, iKey string ) if _, ok := userInfo[coinId]; !ok { userInfo, _ = d.GetUserCapsuleInfo(ctx, uid) } 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) affect, err = d.execSqlWithBindParams(ctx, &sqlStr, score, uid) 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) } d.ReportCapsuleChange(ctx, coinId, uid, score, action, platform, userInfo, coinConf) 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 ) userInfo, _ := d.GetUserInfo(ctx, uid, coinId) sqlStr = fmt.Sprintf(_updateUserInfoMysql, getCapsuleTable(uid)) conn := d.redis.Get(ctx) defer conn.Close() uKey = userKey(uid) iKey = userInfoKey(uid, coinId) affect, err = d.execSqlWithBindParams(ctx, &sqlStr, score, uid, CoinIdIntMap[coinId]) 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) } d.ReportCapsuleChange(ctx, coinId, uid, score, action, platform, userInfo, coinConf) return } // 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 { return d.GetUserCapsuleInfo(c, uid) } var ( isEmpty bool uKey string ) 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|setUserCapsuleInfoCache] 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 { return } if err == sql.ErrNoRows { sqlStr := fmt.Sprintf(_addInfoMysql, getCapsuleTable(uid)) _, err = d.db.Exec(c, sqlStr, uid, CoinIdIntMap[coinId], 0) if err != nil { return } 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 } // ReportCapsuleChange 通知扭蛋积分变换 func (d *Dao) ReportCapsuleChange(ctx context.Context, coinId, uid, score int64, action, platform string, pInfo map[int64]int64, coinConf *CapsuleCoinConf) bool { if _, ok := pInfo[coinId]; !ok { return false } chnageType := coinId cInfo, err := d.GetUserInfo(ctx, uid, coinId) if err != nil { return false } if _, ok := cInfo[coinId]; !ok { return false } 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 } // GetCoin 获取扭蛋数量 func (d *Dao) GetCoin(score int64, coinConf *CapsuleCoinConf) (coinNum int64) { if coinConf == nil || coinConf.ChangeNum == 0 { return 0 } coinNum = score / coinConf.ChangeNum return } // 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() _, err := conn.Do("SET", nKey, coinNum, "EX", 30*86400) if err != nil { log.Error("[dao.redis_lottery|AddNotice] conn.SET(%s) error(%v)", nKey, err) } } // 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 } // CheckLplFirstGift 检测是否首次送礼 func (d *Dao) CheckLplFirstGift(ctx context.Context, uid, giftId int64) bool { var ( value int64 extra string day string cType string ) day = time.Now().Format("2006-01-02") cType = "lpl" + day nKey := fmt.Sprintf(_lplSendGiftRedis, day, uid) conn := d.redis.Get(ctx) defer conn.Close() _, err := redis.Int64(conn.Do("GET", nKey)) log.Info("[dao.redis_lottery|CheckLplFirstGift] conn.GET(%s) error(%v)", nKey, err) if err == redis.ErrNil { row := d.db.QueryRow(ctx, _getExtraDataMysql, uid, cType) err = row.Scan(&value, &extra) if err == sql.ErrNoRows { _, err = d.db.Exec(ctx, _addExtraDataMysql, uid, cType, time.Now().Unix(), strconv.FormatInt(giftId, 10)) if err != nil { log.Error("[dao.redis_lottery|CheckLplFirstGift] conn.addExtraData(%s) error(%v)", nKey, err) return false } return true } if err != nil { log.Error("[dao.redis_lottery|CheckLplFirstGift] conn.getExtraData(%s) error(%v)", nKey, err) return false } _, err = conn.Do("SET", nKey, 1, "EX", 86400) if err != nil { log.Error("[dao.redis_lottery|CheckLplFirstGift] conn.SET(%s) error(%v)", nKey, err) } return false } if err != nil { log.Error("[dao.redis_lottery|CheckLplFirstGift] conn.GET(%s) error(%v)", nKey, err) } return false } // GetExtraDataByTime 获取数据 func (d *Dao) GetExtraDataByTime(ctx context.Context, startTime, endTime string) (extraData []*model.ExtraData, err error) { var rows *sql.Rows if rows, err = d.db.Query(ctx, _getExtraDataByTimeMysql, startTime, endTime); err != nil { log.Error("[dao.extra | GetExtraDataByType] query(%s) error (%v)", _getExtraDataByTimeMysql, err) return } log.Info("[dao.extra | GetExtraDataByType] start(%d) end(%s)", startTime, endTime) defer rows.Close() extraData = make([]*model.ExtraData, 0) for rows.Next() { p := &model.ExtraData{} if err = rows.Scan(&p.Id, &p.Uid, &p.Type, &p.ItemValue, &p.ItemExtra); err != nil { log.Error("[dao.extra | GetExtraDataByType] scan error, err %v", err) return } extraData = append(extraData, p) } return } // UpdateExtraValueById 更新数据 func (d *Dao) UpdateExtraValueById(ctx context.Context, id int64, itemValue int64) (status bool, err error) { res, err := d.db.Exec(ctx, _updateExtraValueMysql, itemValue, id) if err != nil { log.Error("[dao.extra | UpdateExtraValue] update(%s) error(%v)", _updateExtraValueMysql, err) return false, err } var rows int64 rows, err = res.RowsAffected() if err != nil { log.Error("[dao.extra | UpdateExtraValue] err %v", err) return false, err } return rows > 0, nil } // UpdateExtraMtimeById 更新时间数据 func (d *Dao) UpdateExtraMtimeById(ctx context.Context, id int64, mtime string) (status bool, err error) { res, err := d.db.Exec(ctx, _updateExtraMtimeMysql, mtime, id) if err != nil { log.Error("[dao.extra | UpdateExtraMtimeById] update(%s) error(%v)", _updateExtraMtimeMysql, err) return false, err } var rows int64 rows, err = res.RowsAffected() if err != nil { log.Error("[dao.extra | UpdateExtraMtimeById] err %v", err) return false, err } return rows > 0, nil } // UpdateExtraById 更新数据 func (d *Dao) UpdateExtraById(ctx context.Context, id int64, itemValue int64, itemExtra string) (status bool, err error) { res, err := d.db.Exec(ctx, _updateExtraMysql, itemValue, itemExtra, id) if err != nil { log.Error("[dao.extra | UpdateExtraById] update(%s) error(%v)", _updateExtraMysql, err) return false, err } var rows int64 rows, err = res.RowsAffected() if err != nil { log.Error("[dao.extra | UpdateExtraById] err %v", err) return false, err } return rows > 0, nil } // GetExtraDataByIds 获取数据 func (d *Dao) GetExtraDataByIds(ctx context.Context, start, end int64) (extraData []*model.ExtraData, err error) { var rows *sql.Rows if rows, err = d.db.Query(ctx, _getExtraDataByIdMysql, start, end); err != nil { log.Error("[dao.capsule | GetExtraDataByIds]query(%s) error(%v)", _getUserInfoById, err) return } defer rows.Close() extraData = make([]*model.ExtraData, 0) for rows.Next() { p := &model.ExtraData{} if err = rows.Scan(&p.Id, &p.Uid, &p.Type, &p.ItemValue, &p.ItemExtra); err != nil { log.Error("[dao.capsule | GetExtraDataByIds] scan error, err %v", err) return } extraData = append(extraData, p) } return } // GetCouponData 添加数据 func (d *Dao) GetCouponData(ctx context.Context) (extraData []*model.ExtraData, err error) { var i, maxId int64 row := d.db.QueryRow(ctx, _getExtraDataMaxIdMysql) if err = row.Scan(&maxId); err != nil { log.Error("[dao.capsule | GetCouponData] query(%s),err(%v)", _getExtraDataMaxIdMysql, err) return } extraData = make([]*model.ExtraData, 0) for i = 0; i < maxId; i = i + 10000 { var curExtraData []*model.ExtraData curExtraData, err = d.GetExtraDataByIds(ctx, i, i+10000) if err != nil { return } for _, extra := range curExtraData { if extra.ItemValue != 0 { continue } if !strings.HasPrefix(extra.Type, "CouponRetry") { continue } extraData = append(extraData, extra) } } return }