package dao import ( "context" "fmt" "go-common/app/common/live/library/lrucache" jsonitor "github.com/json-iterator/go" v1pb "go-common/app/service/live/dao-anchor/api/grpc/v1" "go-common/app/service/live/dao-anchor/conf" "go-common/library/cache/redis" xsql "go-common/library/database/sql" "go-common/library/ecode" "go-common/library/log" "go-common/library/queue/databus" "go-common/library/sync/errgroup" ) const ( INFO_ROOM = 1 << iota INFO_ROOM_EXT INFO_TAG INFO_ANCHOR INFO_SHORT_ID INFO_AREA_INFO ) const ( INFO_ALL = (INFO_ROOM | INFO_ROOM_EXT | INFO_TAG | INFO_ANCHOR | INFO_SHORT_ID | INFO_AREA_INFO) ) const ( FETCH_PAGE_SIZE = 100 ) //消费类型常量 定义 const ( //弹幕 //DANMU_NUM 当前弹幕累计数量 DANMU_NUM = "danmu_num" //DANMU_MINUTE_NUM_15 最近15分钟弹幕数量 DANMU_MINUTE_NUM_15 = "danmu_minute_num_15" //DANMU_MINUTE_NUM_30 ... DANMU_MINUTE_NUM_30 = "danmu_minute_num_30" //DANMU_MINUTE_NUM_45 ... DANMU_MINUTE_NUM_45 = "danmu_minute_num_45" //DANMU_MINUTE_NUM_60 ... DANMU_MINUTE_NUM_60 = "danmu_minute_num_60" //人气 //POPULARITY 当前实时人气 POPULARITY = "popularity" //POPULARITY_MAX_TO_ARG_7 7日峰值人气的均值 POPULARITY_MAX_TO_ARG_7 = "popularity_max_to_avg_7" //POPULARITY_MAX_TO_ARG_30 30日人气峰值的均值 POPULARITY_MAX_TO_ARG_30 = "popularity_max_to_avg_30" //送礼 //GIFT_NUM 实时送礼数 GIFT_NUM = "gift_num_current_total" //GIFT_GOLD_AMOUNT 实时消费金瓜子数 GIFT_GOLD_NUM = "gift_gold_num" //GIFT_GOLD_AMOUNT 实时消费金瓜子金额 GIFT_GOLD_AMOUNT = "gift_gold_amount" //GIFT_GOLD_AMOUNT_MINUTE_15 最近15分钟金瓜子金额 GIFT_GOLD_AMOUNT_MINUTE_15 = "gift_gold_num_minute_15" //GIFT_GOLD_AMOUNT_MINUTE_30 最近30分钟金瓜子金额 GIFT_GOLD_AMOUNT_MINUTE_30 = "gift_gold_num_minute_30" //GIFT_GOLD_AMOUNT_MINUTE_45 ... GIFT_GOLD_AMOUNT_MINUTE_45 = "gift_gold_num_minute_45" //GIFT_GOLD_AMOUNT_MINUTE_60 ... GIFT_GOLD_AMOUNT_MINUTE_60 = "gift_gold_num_minute_60" //有效开播天数 //VALID_LIVE_DAYS_TYPE_1_DAY_7 7日内有效开播天数;有效开播:一次开播大于5分钟 VALID_LIVE_DAYS_TYPE_1_DAY_7 = "valid_days_type_1_day_7" //VALID_LIVE_DAYS_TYPE_1_DAY_14 14日内有效开播天数;有效开播:一次开播大于5分钟 VALID_LIVE_DAYS_TYPE_1_DAY_14 = "valid_days_type_1_day_14" //VALID_LIVE_DAYS_TYPE_2_DAY_7 7日内有效开播天数;有效开播:大于等于120分钟 VALID_LIVE_DAYS_TYPE_2_DAY_7 = "valid_days_type_2_day_7" //VALID_LIVE_DAYS_TYPE_2_DAY_30 14日内有效开播天数;有效开播:大于等于120分钟 VALID_LIVE_DAYS_TYPE_2_DAY_30 = "valid_days_type_2_day_30" //房间状态 //ROOM_TAG_CURRENT 房间实时标签 ROOM_TAG_CURRENT = "room_tag_current" //榜单 //RANK_LIST_CURRENT 排行榜相关数据 RANK_LIST_CURRENT = "rank_list_current" //DAU DAU = "dau" ) const ( _RoomIdMappingCacheCapacity = 1024 ) // Dao dao type Dao struct { c *conf.Config redis *redis.Pool db *xsql.DB dbLiveApp *xsql.DB shortIDMapping *lrucache.SyncCache areaInfoMapping *lrucache.SyncCache } // New init mysql db func New(c *conf.Config) (dao *Dao) { dao = &Dao{ c: c, redis: redis.NewPool(c.Redis), db: xsql.NewMySQL(c.MySQL), dbLiveApp: xsql.NewMySQL(c.LiveAppMySQL), shortIDMapping: lrucache.NewSyncCache(c.LRUCache.Bucket, c.LRUCache.Capacity, c.LRUCache.Timeout), areaInfoMapping: lrucache.NewSyncCache(c.LRUCache.Bucket, c.LRUCache.Capacity, c.LRUCache.Timeout), } return } // Close close the resource. func (d *Dao) Close() { d.redis.Close() d.db.Close() return } // Ping dao ping func (d *Dao) Ping(c context.Context) error { // TODO: if you need use mc,redis, please add return d.db.Ping(c) } // FetchRoomByIDs implementation // FetchRoomByIDs 查询房间信息 func (d *Dao) FetchRoomByIDs(ctx context.Context, req *v1pb.RoomByIDsReq) (resp *v1pb.RoomByIDsResp, err error) { if len(req.RoomIds) > 0 { req.RoomIds, err = d.dbNormalizeRoomIDs(ctx, req.RoomIds) if err != nil { log.Error("[dao.dao-anchor.mysql|dbFetchRoomByIDs] normalize ids error(%v), req(%v)", err, req) return nil, err } } // TODO: 处理部分fields的情况,需要考虑特殊status的依赖问题 if len(req.RoomIds) > 0 { resp = &v1pb.RoomByIDsResp{ RoomDataSet: make(map[int64]*v1pb.RoomData), } idsDB := make([]int64, 0, len(req.RoomIds)) // 从redis获取房间所有信息 for _, id := range req.RoomIds { data, err := d.redisGetRoomInfo(ctx, id, _allRoomInfoFields) if err != nil { idsDB = append(idsDB, id) } else { d.dbDealWithStatus(ctx, data) resp.RoomDataSet[id] = data } } // 需要回源DB取数据 if len(idsDB) > 0 { // 分段处理 for start := 0; start < len(idsDB); start += FETCH_PAGE_SIZE { end := start + FETCH_PAGE_SIZE if end > len(idsDB) { end = len(idsDB) } reqRoom := &v1pb.RoomByIDsReq{ RoomIds: idsDB[start:end], Fields: _allRoomInfoFields, } respRoom, err := d.dbFetchRoomByIDs(ctx, reqRoom) if err != nil { log.Error("[RoomOnlineList] dbFetchRoomByIDs error(%v), reqRoom(%v)", err, reqRoom) return nil, err } // 回写房间信息到redis for _, id := range idsDB[start:end] { resp.RoomDataSet[id] = respRoom.RoomDataSet[id] d.redisSetRoomInfo(ctx, id, _allRoomInfoFields, respRoom.RoomDataSet[id], false) } } } } else if len(req.Uids) > 0 { // TODO 根据主播ID查询房间号的场景较少,暂不优化,后续先转房间号 resp, err = d.dbFetchRoomByIDs(ctx, req) } return } // RoomOnlineList implementation // RoomOnlineList 在线房间列表 func (d *Dao) RoomOnlineList(ctx context.Context, req *v1pb.RoomOnlineListReq) (resp *v1pb.RoomOnlineListResp, err error) { log.Info("[dao|RoomOnlineList] req(%v)", err, req) ids, err := d.redisGetOnlineList(ctx, _onlineListAllArea) if err != nil || len(ids) <= 0 { ids, err = d.dbOnlineListByArea(ctx, _onlineListAllArea) if err != nil { log.Error("[RoomOnlineListByAttrs] dbOnlineListByArea error(%v), req(%v)", err, req) return nil, err } d.redisSetOnlineList(ctx, _onlineListAllArea, ids) } resp = &v1pb.RoomOnlineListResp{ RoomDataList: make(map[int64]*v1pb.RoomData), } // 分页逻辑 start := int(req.Page * req.PageSize) size := len(ids) if start >= size { return } end := start + int(req.PageSize) if end > size { end = size } ids = ids[start:end] idsDB := make([]int64, 0, len(ids)) // 从redis获取房间信息 for _, id := range ids { data, err := d.redisGetRoomInfo(ctx, id, req.Fields) if err != nil { idsDB = append(idsDB, id) } else { d.dbDealWithStatus(ctx, data) resp.RoomDataList[id] = data } } // 需要回源DB取数据 if len(idsDB) > 0 { reqRoom := &v1pb.RoomByIDsReq{ RoomIds: idsDB, Fields: _allRoomInfoFields, } respRoom, err := d.dbFetchRoomByIDs(ctx, reqRoom) if err != nil { log.Error("[RoomOnlineList] dbFetchRoomByIDs error(%v), reqRoom(%v)", err, reqRoom) return nil, err } // 回写房间信息到redis for _, id := range idsDB { resp.RoomDataList[id] = respRoom.RoomDataSet[id] d.redisSetRoomInfo(ctx, id, _allRoomInfoFields, respRoom.RoomDataSet[id], false) } } return } // RoomOnlineListByArea implementation // RoomOnlineListByArea 分区在线房间列表 func (d *Dao) RoomOnlineListByArea(ctx context.Context, req *v1pb.RoomOnlineListByAreaReq) (resp *v1pb.RoomOnlineListByAreaResp, err error) { idSet := make(map[int64]bool) idsDB := make([]int64, 0) if len(req.AreaIds) <= 0 { req.AreaIds = []int64{0} } for _, areaID := range req.AreaIds { ids, err := d.redisGetOnlineList(ctx, areaID) if err != nil { idsDB = append(idsDB, areaID) } else { for _, id := range ids { idSet[id] = true } } } // 需要回源DB取数据 if len(idsDB) > 0 { for _, areaID := range idsDB { roomIds, err := d.dbOnlineListByArea(ctx, areaID) if err != nil { log.Error("[RoomOnlineListByArea] dbOnlineListByArea error(%v), areaID(%v)", err, areaID) return nil, err } d.redisSetOnlineList(ctx, areaID, roomIds) for _, id := range roomIds { idSet[id] = true } } } resp = &v1pb.RoomOnlineListByAreaResp{ RoomIds: make([]int64, 0, len(idSet)), } for id := range idSet { resp.RoomIds = append(resp.RoomIds, id) } return } var ( _fields = []string{"uid", "area_id", "parent_area_id", "popularity_count", "anchor_profile_type"} ) // RoomOnlineListByAttrs implementation // RoomOnlineListByAttrs 在线房间维度信息(不传attrs,不查询attr) func (d *Dao) RoomOnlineListByAttrs(ctx context.Context, req *v1pb.RoomOnlineListByAttrsReq) (resp *v1pb.RoomOnlineListByAttrsResp, err error) { ids, err := d.redisGetOnlineList(ctx, _onlineListAllArea) if err != nil || len(ids) <= 0 { ids, err = d.dbOnlineListByArea(ctx, _onlineListAllArea) if err != nil { log.Error("[RoomOnlineListByAttrs] dbOnlineListByArea error(%v), req(%v)", err, req) return nil, err } d.redisSetOnlineList(ctx, _onlineListAllArea, ids) } resp = &v1pb.RoomOnlineListByAttrsResp{ Attrs: make(map[int64]*v1pb.AttrResp), } idsDB := make([]int64, 0, len(ids)) for _, id := range ids { // 从redis获取房间基础信息 data, err := d.redisGetRoomInfo(ctx, id, _fields) if err != nil { idsDB = append(idsDB, id) } else { resp.Attrs[id] = &v1pb.AttrResp{ Uid: data.Uid, RoomId: id, AreaId: data.AreaId, ParentAreaId: data.ParentAreaId, PopularityCount: data.PopularityCount, AnchorProfileType: data.AnchorProfileType, } } } // 需要回源DB取数据 if len(idsDB) > 0 { eg := errgroup.Group{} // 分段处理 for start := 0; start < len(idsDB); start += FETCH_PAGE_SIZE { end := start + FETCH_PAGE_SIZE if end > len(idsDB) { end = len(idsDB) } eg.Go(func(idsDB []int64, start, end int) func() error { return func() (err error) { reqRoom := &v1pb.RoomByIDsReq{ RoomIds: idsDB[start:end], Fields: _allRoomInfoFields, } respRoom, err := d.dbFetchRoomByIDs(ctx, reqRoom) if err != nil { log.Error("[RoomOnlineList] dbFetchRoomByIDs error(%v), reqRoom(%v)", err, reqRoom) return err } // 回写房间信息到redis for _, id := range idsDB[start:end] { resp.Attrs[id] = &v1pb.AttrResp{ Uid: respRoom.RoomDataSet[id].Uid, RoomId: id, AreaId: respRoom.RoomDataSet[id].AreaId, ParentAreaId: respRoom.RoomDataSet[id].ParentAreaId, PopularityCount: respRoom.RoomDataSet[id].PopularityCount, AnchorProfileType: respRoom.RoomDataSet[id].AnchorProfileType, TagList: respRoom.RoomDataSet[id].TagList, AttrList: make([]*v1pb.AttrData, 0, len(req.Attrs)), } d.redisSetRoomInfo(ctx, id, _allRoomInfoFields, respRoom.RoomDataSet[id], false) d.redisSetTagList(ctx, id, respRoom.RoomDataSet[id].TagList) } return } }(idsDB, start, end)) } eg.Wait() } // 重置回源数组 idsDB = make([]int64, 0, len(ids)) for _, id := range ids { if resp.Attrs[id].TagList == nil { // 从redis获取房间Tag信息 data, err := d.redisGetTagList(ctx, id) if err != nil { idsDB = append(idsDB, id) } else { resp.Attrs[id].TagList = data } } } // 需要回源DB取数据 if len(idsDB) > 0 { eg := errgroup.Group{} // 分段处理 for start := 0; start < len(idsDB); start += FETCH_PAGE_SIZE { end := start + FETCH_PAGE_SIZE if end > len(idsDB) { end = len(idsDB) } eg.Go(func(idsDB []int64, start, end int) func() error { return func() (err error) { respRoom := make(map[int64]*v1pb.RoomData) err = d.dbFetchTagInfo(ctx, idsDB[start:end], respRoom) if err != nil { log.Error("[RoomOnlineList] dbFetchTagInfo error(%v), idsDB[start:end](%v)", err, idsDB[start:end]) return err } // 回写房间Tag信息到redis for _, id := range idsDB[start:end] { if tag, ok := respRoom[id]; ok { resp.Attrs[id].TagList = tag.TagList } else { resp.Attrs[id].TagList = make([]*v1pb.TagData, 0, len(req.Attrs)) } d.redisSetTagList(ctx, id, resp.Attrs[id].TagList) } return } }(idsDB, start, end)) } eg.Wait() } // TODO 从redis获取attr列表 // TODO 批量从db获取attr列表 if len(req.Attrs) > 0 { eg := errgroup.Group{} for _, attr := range req.Attrs { // 实时人气值特殊处理 if attr.AttrId == ATTRID_POPULARITY && attr.AttrSubId == ATTRSUBID_POPULARITY_REALTIME { for _, attrResp := range resp.Attrs { resp.Attrs[attrResp.RoomId].AttrList = append(resp.Attrs[attrResp.RoomId].AttrList, &v1pb.AttrData{ RoomId: attrResp.RoomId, AttrId: attr.AttrId, AttrSubId: attr.AttrSubId, AttrValue: attrResp.PopularityCount, }) } continue } eg.Go(func(attr *v1pb.AttrReq) func() error { return func() (err error) { reqAttr := &v1pb.FetchAttrByIDsReq{ AttrId: attr.AttrId, AttrSubId: attr.AttrSubId, RoomIds: ids, } respAttr, err := d.FetchAttrByIDs(ctx, reqAttr) if err != nil { log.Error("[RoomOnlineListByAttrs] FetchAttrByIDs from db error(%v), reqAttr(%v)", err, reqAttr) return err } for _, attr := range respAttr.Attrs { resp.Attrs[attr.RoomId].AttrList = append(resp.Attrs[attr.RoomId].AttrList, attr) } return } }(attr)) } eg.Wait() } return } // RoomCreate implementation // RoomCreate 房间创建 func (d *Dao) RoomCreate(ctx context.Context, req *v1pb.RoomCreateReq) (resp *v1pb.RoomCreateResp, err error) { return d.roomCreate(ctx, req) } // RoomUpdate implementation // RoomUpdate 房间更新 func (d *Dao) RoomUpdate(ctx context.Context, req *v1pb.RoomUpdateReq) (resp *v1pb.UpdateResp, err error) { resp, err = d.roomUpdate(ctx, req) if err == nil { fields := make([]string, 0, len(req.Fields)) data := &v1pb.RoomData{ RoomId: req.RoomId, AnchorLevel: new(v1pb.AnchorLevel), } for _, f := range req.Fields { switch f { case "title": data.Title = req.Title case "cover": data.Cover = req.Cover case "tags": data.Tags = req.Tags case "background": data.Background = req.Background case "description": data.Description = req.Description case "live_start_time": data.LiveStartTime = req.LiveStartTime // 更新在播列表 var areaID int64 reqRoom := &v1pb.RoomByIDsReq{ RoomIds: []int64{req.RoomId}, Fields: _allRoomInfoFields, } respRoom, err := d.FetchRoomByIDs(ctx, reqRoom) if err != nil { log.Error("[RoomOnlineList] dbFetchRoomByIDs error(%v), reqRoom(%v)", err, reqRoom) } else { if respRoom.RoomDataSet[req.RoomId] != nil { areaID = respRoom.RoomDataSet[req.RoomId].AreaId } } if req.LiveStartTime > 0 { d.redisAddOnlineList(ctx, _onlineListAllArea, req.RoomId) d.redisAddOnlineList(ctx, areaID, req.RoomId) } else { d.redisDelOnlineList(ctx, _onlineListAllArea, req.RoomId) d.redisDelOnlineList(ctx, areaID, req.RoomId) } // TODO 更新开播状态 case "live_screen_type": data.LiveScreenType = req.LiveScreenType case "live_type": data.LiveType = req.LiveType case "lock_status": data.LockStatus = req.LockStatus case "lock_time": data.LockTime = req.LockTime case "hidden_time": data.HiddenTime = req.HiddenTime // TODO 更新隐藏状态 case "area_id": data.AreaId = req.AreaId if req.AreaId > 0 { areaInfo, err := d.dbFetchAreaInfo(ctx, req.AreaId) if err != nil { log.Error("[dao.dao-anchor.mysql|roomUpdate] fetch area info error(%v), req(%v)", err, req) err = ecode.InvalidParam return nil, err } data.ParentAreaId = areaInfo.ParentAreaID fields = append(fields, "parent_area_id") } default: continue } fields = append(fields, f) } d.redisSetRoomInfo(ctx, data.RoomId, fields, data, true) } return } // RoomBatchUpdate implementation // RoomBatchUpdate 房间更新 func (d *Dao) RoomBatchUpdate(ctx context.Context, req *v1pb.RoomBatchUpdateReq) (resp *v1pb.UpdateResp, err error) { resp = &v1pb.UpdateResp{} for _, r := range req.Reqs { res, err := d.RoomUpdate(ctx, r) if err != nil { log.Error("[dao.dao-anchor.mysql|RoomBatchUpdate] update room record error(%v), req(%v)", err, r) return nil, err } resp.AffectedRows += res.AffectedRows } return } // RoomExtendUpdate implementation // RoomExtendUpdate 房间更新 func (d *Dao) RoomExtendUpdate(ctx context.Context, req *v1pb.RoomExtendUpdateReq) (resp *v1pb.UpdateResp, err error) { resp, err = d.roomExtendUpdate(ctx, req) if err == nil { fields := make([]string, 0, len(req.Fields)) data := &v1pb.RoomData{ RoomId: req.RoomId, AnchorLevel: new(v1pb.AnchorLevel), } for _, f := range req.Fields { switch f { case "keyframe": data.Keyframe = req.Keyframe case "popularity_count": data.PopularityCount = req.PopularityCount default: continue } fields = append(fields, f) } d.redisSetRoomInfo(ctx, data.RoomId, fields, data, true) } return } // RoomExtendBatchUpdate implementation // RoomExtendBatchUpdate 房间更新 func (d *Dao) RoomExtendBatchUpdate(ctx context.Context, req *v1pb.RoomExtendBatchUpdateReq) (resp *v1pb.UpdateResp, err error) { resp = &v1pb.UpdateResp{} for _, r := range req.Reqs { res, err := d.RoomExtendUpdate(ctx, r) if err != nil { log.Error("[dao.dao-anchor.mysql|RoomExtendBatchUpdate] update room extend record error(%v), req(%v)", err, r) return nil, err } resp.AffectedRows += res.AffectedRows } return } // RoomExtendIncre implementation // RoomExtendIncre 房间增量更新 func (d *Dao) RoomExtendIncre(ctx context.Context, req *v1pb.RoomExtendIncreReq) (resp *v1pb.UpdateResp, err error) { resp, err = d.roomExtendIncre(ctx, req) if err == nil { fields := make([]string, 0, len(req.Fields)) data := &v1pb.RoomData{ RoomId: req.RoomId, AnchorLevel: new(v1pb.AnchorLevel), } for _, f := range req.Fields { switch f { case "popularity_count": data.PopularityCount = req.PopularityCount default: continue } fields = append(fields, f) } if len(fields) > 0 { d.redisIncreRoomInfo(ctx, data.RoomId, fields, data) } } return } // RoomExtendBatchIncre implementation // RoomExtendBatchIncre 房间增量更新 func (d *Dao) RoomExtendBatchIncre(ctx context.Context, req *v1pb.RoomExtendBatchIncreReq) (resp *v1pb.UpdateResp, err error) { resp = &v1pb.UpdateResp{} for _, r := range req.Reqs { res, err := d.RoomExtendIncre(ctx, r) if err != nil { log.Error("[dao.dao-anchor.mysql|RoomExtendBatchIncre] update room extend increment record error(%v), req(%v)", err, r) return nil, err } resp.AffectedRows += res.AffectedRows } return } // RoomTagCreate implementation // RoomTagCreate 房间Tag创建 func (d *Dao) RoomTagCreate(ctx context.Context, req *v1pb.RoomTagCreateReq) (resp *v1pb.UpdateResp, err error) { resp, err = d.roomTagCreate(ctx, req) if err == nil { tag := &v1pb.TagData{ TagId: req.TagId, TagSubId: req.TagSubId, TagValue: req.TagValue, TagExt: req.TagExt, TagExpireAt: req.TagExpireAt, } d.redisAddTag(ctx, req.RoomId, tag) } return } // RoomAttrCreate implementation // RoomAttrCreate 房间Attr创建 func (d *Dao) RoomAttrCreate(ctx context.Context, req *v1pb.RoomAttrCreateReq) (resp *v1pb.UpdateResp, err error) { return d.roomAttrCreate(ctx, req) } // RoomAttrSetEx implementation // RoomAttrSetEx 房间Attr更新 func (d *Dao) RoomAttrSetEx(ctx context.Context, req *v1pb.RoomAttrSetExReq) (resp *v1pb.UpdateResp, err error) { return d.roomAttrSetEx(ctx, req) } // AnchorUpdate implementation // AnchorUpdate 主播更新 func (d *Dao) AnchorUpdate(ctx context.Context, req *v1pb.AnchorUpdateReq) (resp *v1pb.UpdateResp, err error) { resp, err = d.anchorUpdate(ctx, req) if err == nil { roomID := d.dbFetchRoomIDByUID(ctx, req.Uid) if roomID == 0 { return } fields := make([]string, 0, len(req.Fields)) data := &v1pb.RoomData{ RoomId: roomID, AnchorLevel: new(v1pb.AnchorLevel), } for _, f := range req.Fields { switch f { case "profile_type": f = "anchor_profile_type" data.AnchorProfileType = req.ProfileType case "san_score": f = "anchor_san" data.AnchorSan = req.SanScore case "round_status": f = "anchor_round_switch" data.AnchorRoundSwitch = req.RoundStatus case "record_status": f = "anchor_record_switch" data.AnchorRecordSwitch = req.RecordStatus case "exp": f = "anchor_exp" data.AnchorLevel.Score = req.Exp default: log.Error("[dao.dao-anchor.mysql|anchorUpdate] unsupported field(%v), req(%s)", f, req) err = ecode.InvalidParam return } fields = append(fields, f) } d.redisSetRoomInfo(ctx, data.RoomId, fields, data, true) } return } // AnchorBatchUpdate implementation // AnchorBatchUpdate 主播更新 func (d *Dao) AnchorBatchUpdate(ctx context.Context, req *v1pb.AnchorBatchUpdateReq) (resp *v1pb.UpdateResp, err error) { resp = &v1pb.UpdateResp{} for _, r := range req.Reqs { res, err := d.AnchorUpdate(ctx, r) if err != nil { log.Error("[dao.dao-anchor.mysql|AnchorBatchUpdate] update anchor record error(%v), req(%v)", err, r) return nil, err } resp.AffectedRows += res.AffectedRows } return } // AnchorIncre implementation // AnchorIncre 主播增量更新 func (d *Dao) AnchorIncre(ctx context.Context, req *v1pb.AnchorIncreReq) (resp *v1pb.UpdateResp, err error) { resp, err = d.anchorIncre(ctx, req) if err == nil { roomID := d.dbFetchRoomIDByUID(ctx, req.Uid) if roomID == 0 { return } fields := make([]string, 0, len(req.Fields)) data := &v1pb.RoomData{ RoomId: roomID, AnchorLevel: new(v1pb.AnchorLevel), } for _, f := range req.Fields { switch f { case "san_score": f = "anchor_san" data.AnchorSan = req.SanScore case "exp": f = "anchor_exp" data.AnchorLevel.Score = req.Exp default: continue } fields = append(fields, f) } if len(fields) > 0 { d.redisIncreRoomInfo(ctx, data.RoomId, fields, data) } } return } // AnchorBatchIncre implementation // AnchorBatchIncre 主播增量更新 func (d *Dao) AnchorBatchIncre(ctx context.Context, req *v1pb.AnchorBatchIncreReq) (resp *v1pb.UpdateResp, err error) { resp = &v1pb.UpdateResp{} for _, r := range req.Reqs { res, err := d.AnchorIncre(ctx, r) if err != nil { log.Error("[dao.dao-anchor.mysql|AnchorBatchIncre] update anchor increment record error(%v), req(%v)", err, r) return nil, err } resp.AffectedRows += res.AffectedRows } return } // FetchAreas implementation // FetchAreas 根据父分区号查询子分区 func (d *Dao) FetchAreas(ctx context.Context, req *v1pb.FetchAreasReq) (resp *v1pb.FetchAreasResp, err error) { return d.fetchAreas(ctx, req) } // FetchAttrByIDs implementation // FetchAttrByIDs 批量根据房间号查询指标 func (d *Dao) FetchAttrByIDs(ctx context.Context, req *v1pb.FetchAttrByIDsReq) (resp *v1pb.FetchAttrByIDsResp, err error) { return d.fetchAttrByIDs(ctx, req) } // DeleteAttr implementation // DeleteAttr 删除一个指标 func (d *Dao) DeleteAttr(ctx context.Context, req *v1pb.DeleteAttrReq) (resp *v1pb.UpdateResp, err error) { return d.deleteAttr(ctx, req) } type msgVal struct { MsgID string `json:"msg_id"` } func getConsumedKey(topic string, msgID string) string { return fmt.Sprintf("consumed:%s:%s", topic, msgID) } // 清除消费过的记录,主要用于测试 func (d *Dao) clearConsumed(ctx context.Context, msg *databus.Message) { val := &msgVal{} err := jsonitor.Unmarshal(msg.Value, val) if err != nil { return } conn := d.redis.Get(ctx) defer conn.Close() conn.Do("DEL", getConsumedKey(msg.Topic, val.MsgID)) } // CanConsume 是否可以消费 func (d *Dao) CanConsume(ctx context.Context, msg *databus.Message) bool { val := &msgVal{} err := jsonitor.Unmarshal(msg.Value, val) if err != nil { log.Error("unmarshal msg value error %+v, value: %s", err, string(msg.Value)) return true } if val.MsgID == "" { log.Warn("msg_id is empty ; value: %s", string(msg.Value)) return true } conn := d.redis.Get(ctx) defer conn.Close() var key = getConsumedKey(msg.Topic, val.MsgID) reply, err := conn.Do("SET", key, "1", "NX", "EX", 86400) // 24 hours if err == nil { if reply == nil { log.Info("Already consumed key:%s", key) return false } else { return true } } if err == redis.ErrNil { //already consumed log.Info("Already consumed key:%s", key) return false } // other redis error happenned, let it pass log.Error("redis error when resolve CanConsume %+v", err) return true }