Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"mysql.go",
"redis.go",
"redisKey.go",
],
importpath = "go-common/app/service/live/dao-anchor/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/common/live/library/lrucache:go_default_library",
"//app/service/live/dao-anchor/api/grpc/v1:go_default_library",
"//app/service/live/dao-anchor/conf:go_default_library",
"//app/service/live/dao-anchor/model:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
"//library/sync/errgroup:go_default_library",
"//vendor/github.com/json-iterator/go:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = [
"dao_test.go",
"mysql_test.go",
],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/service/live/dao-anchor/api/grpc/v1:go_default_library",
"//app/service/live/dao-anchor/conf:go_default_library",
"//app/service/live/dao-anchor/model:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,874 @@
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
}

View File

@@ -0,0 +1,61 @@
package dao
import (
"context"
"encoding/json"
"flag"
//"fmt"
"os"
"testing"
. "github.com/smartystreets/goconvey/convey"
"go-common/app/service/live/dao-anchor/conf"
"go-common/library/log"
"go-common/library/queue/databus"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
// TODO: other environments?
flag.Set("conf", "../cmd/test.toml")
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
m.Run()
os.Exit(0)
}
func TestCanConsume(t *testing.T) {
flag.Set("conf", "../cmd/test.toml")
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
log.Init(conf.Conf.Log)
Convey("", t, func(c C) {
ctx := context.TODO()
d := New(conf.Conf)
msg := &databus.Message{
Topic: "test-topic",
Value: json.RawMessage(`{"msg_id":"test-msg-id", "other_key":"value"}`),
}
d.clearConsumed(ctx, msg)
can := d.CanConsume(ctx, msg)
So(can, ShouldBeTrue)
can = d.CanConsume(ctx, msg)
So(can, ShouldBeFalse)
d.clearConsumed(ctx, msg)
can = d.CanConsume(ctx, msg)
So(can, ShouldBeTrue)
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
"go-common/app/service/live/dao-anchor/api/grpc/v1"
"go-common/app/service/live/dao-anchor/model"
)
func TestDaoNormalizeRoomIDs(t *testing.T) {
var (
c = context.TODO()
inputIDs = []int64{5910, 5901, 63, 53, 5010, 115, 666}
turnedFlags = []bool{false, false, true, true, false, true, true}
)
convey.Convey("When normalize a given list of ids", t, func(ctx convey.C) {
normalized, err := d.dbNormalizeRoomIDs(c, inputIDs)
ctx.Convey("Then short-id is turned into room-id while room-id keeps untouched", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(len(normalized), convey.ShouldEqual, len(inputIDs))
for i, turned := range turnedFlags {
if turned {
ctx.So(normalized[i] > inputIDs[i], convey.ShouldBeTrue)
} else {
ctx.So(normalized[i] == inputIDs[i], convey.ShouldBeTrue)
}
}
// ctx.So(d.shortIDMapping.caches[0].Len(), convey.ShouldEqual, len(inputIDs))
})
ctx.Convey("Then order of result will be preserved", func(ctx convey.C) {
roomIds := []int64{5901, 57796, 5010, 1011}
results, err := d.dbNormalizeRoomIDs(c, roomIds)
ctx.So(err, convey.ShouldBeNil)
for i := range results {
ctx.So(roomIds[i], convey.ShouldEqual, results[i])
}
})
})
}
func TestDaoFetchAreas(t *testing.T) {
var (
c = context.TODO()
)
convey.Convey("When given a valid main area id", t, func(ctx convey.C) {
req := &v1.FetchAreasReq{
AreaId: 3,
}
resp, err := d.fetchAreas(c, req)
ctx.Convey("Then we will get a list of its subarea's info along with its info", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(resp.Info.AreaId, convey.ShouldEqual, req.AreaId)
ctx.So(resp.Info.AreaName, convey.ShouldNotBeEmpty)
ctx.So(resp.Areas, convey.ShouldNotBeEmpty)
})
})
convey.Convey("When given a non-existed main area id", t, func(ctx convey.C) {
req := &v1.FetchAreasReq{
AreaId: 999,
}
_, err := d.fetchAreas(c, req)
ctx.Convey("Then we will get nothing", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
})
}
func TestDaoFetchAnchorInfo(t *testing.T) {
var (
c = context.TODO()
)
convey.Convey("When to fetch anchor info for a given uid", t, func(ctx convey.C) {
uid := []int64{2}
const RoomID = 1024
resp := make(map[int64]*v1.RoomData)
err := d.dbFetchAnchorInfo(c, uid, resp, false)
ctx.Convey("Then we will get room data for the anchor", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(resp, convey.ShouldNotBeEmpty)
data, ok := resp[RoomID]
ctx.So(ok, convey.ShouldBeTrue)
alv := data.AnchorLevel
ctx.So(alv.MaxLevel, convey.ShouldEqual, model.MaxAnchorLevel)
ctx.So(alv.Level, convey.ShouldEqual, 1)
ctx.So(alv.Color, convey.ShouldEqual, 0)
ctx.So(alv.Left, convey.ShouldEqual, 0)
ctx.So(alv.Right, convey.ShouldEqual, 49)
})
})
}

View File

@@ -0,0 +1,754 @@
package dao
import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
v1pb "go-common/app/service/live/dao-anchor/api/grpc/v1"
"go-common/library/cache/redis"
"go-common/library/ecode"
"go-common/library/log"
)
//实时消费缓存设计,异步落地
const VALUE = "value"
const DATE = "date" //是否最新消息是为1(需要刷新到DB) 否为0(不需要刷新到DB)
const DATE_1 = "1"
type ListIntValueInfo struct {
Value int64 `json:"value"`
Time int64 `json:"time"`
}
//Set 设置实时数据
func (d *Dao) Set(ctx context.Context, redisKey string, value string, timeOut int) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
if _, err = conn.Do("HMSET", redisKey, VALUE, value, DATE, DATE_1); err != nil {
log.Error("redis_set_err:key=%s;value=%s;err=%v", redisKey, value, err)
return
}
conn.Do("EXPIRE", redisKey, timeOut)
return
}
//Incr 设置增加数据
func (d *Dao) Incr(ctx context.Context, redisKey string, value int64, timeOut int) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
if err = conn.Send("HINCRBY", redisKey, VALUE, value); err != nil {
log.Error("redis_incr_err:key=%s;value=%d;err=%v", redisKey, value, err)
return
}
if err = conn.Send("HSET", redisKey, DATE, DATE_1); err != nil {
log.Error("redis_hset_err:key=%s;date_value=%s;err=%v", redisKey, DATE_1, err)
return
}
conn.Send("EXPIRE", redisKey, timeOut)
if err = conn.Flush(); err != nil {
log.Error("redisIncreRoomInfo conn.Flush error(%v)", err)
return
}
_, err = conn.Receive()
_, err = conn.Receive()
_, err = conn.Receive()
return
}
func (d *Dao) HGet(ctx context.Context, redisKey string) (resp int64, err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
resp, err = redis.Int64(conn.Do("HGET", redisKey, VALUE))
if err != nil {
log.Error("redis_incr_err:key=%s;reply=%d;err=%v", redisKey, resp, err)
return
}
return
}
func (d *Dao) SetList(ctx context.Context, redisKey string, value string, timeOut int) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
if _, err = conn.Do("LPUSH", redisKey, value); err != nil {
log.Error("redis_setList_error:key=%s;value=%s;err=%v", redisKey, value, err)
return
}
conn.Do("EXPIRE", redisKey, timeOut)
return
}
func (d *Dao) GetList(ctx context.Context, redisKey string, start int, end int) (resp []*ListIntValueInfo, err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
res, err := redis.Values(conn.Do("LRANGE", redisKey, start, end))
if err != nil {
log.Error("redis_getList_error:key=%s;err=%v", redisKey, err)
return
}
for _, sList := range res {
list := &ListIntValueInfo{}
if err = json.Unmarshal(sList.([]byte), &list); err != nil {
log.Error("GetList_json_error")
continue
}
resp = append(resp, list)
}
return
}
// GetRoomRecordsCurrent return a list of records corresponding to `content`.
func (d *Dao) GetRoomRecordsCurrent(ctx context.Context, content string, roomIds []int64) (list []int64, err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
for _, roomId := range roomIds {
key := d.SRoomRecordCurrent(content, roomId)
if err = conn.Send("HGET", key.RedisKey, VALUE); err != nil {
log.Error("GetRoomRecordsCurrent conn.Send(HGET, %s, %s) error(%v)", key.RedisKey, VALUE, err)
return nil, err
}
}
if err = conn.Flush(); err != nil {
log.Error("GetRoomRecordsCurrent conn.Flush error(%v)", err)
return nil, err
}
for i := 0; i < len(roomIds); i++ {
var data int64
if data, err = redis.Int64(conn.Receive()); err != nil {
if err != redis.ErrNil {
log.Error("GetRoomRecordsCurrent conn.Receive() %d error(%v)", i, err)
return nil, err
}
}
list = append(list, data)
}
return
}
func (d *Dao) DelRoomRecordsCurrent(ctx context.Context, content string, roomIds []int64) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
args := make([]interface{}, len(roomIds))
for i, roomId := range roomIds {
args[i] = d.SRoomRecordCurrent(content, roomId)
}
if _, err = conn.Do("DEL", args...); err != nil {
log.Error("DelRoomRecordsCurrent_del_error:%v;roomIds=%v", err, roomIds)
return
}
return
}
func (d *Dao) SetRoomRecordsList(ctx context.Context, roomIds []int64, keys map[int64]interface{}, values map[int64]string) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
for _, roomId := range roomIds {
keyInfo := keys[roomId].(*redisKeyResp)
value := values[roomId]
if err = conn.Send("LPUSH", keyInfo.RedisKey, value); err != nil {
log.Error("SetRoomRecordsList conn.Send(LPUSH, %s, %s) error(%v)", keyInfo.RedisKey, value, err)
return err
}
if err = conn.Send("EXPIRE", keyInfo.RedisKey, keyInfo.TimeOut); err != nil {
log.Error("SetRoomRecordsList conn.Send(EXPIRE, %s, %d) error(%v)", keyInfo.RedisKey, keyInfo.TimeOut, err)
return err
}
}
if err = conn.Flush(); err != nil {
log.Error("SetRoomRecordsList conn.Flush() error(%v)", err)
return err
}
for range roomIds {
conn.Receive()
conn.Receive()
}
return
}
// GetRoomLiveRecordsRange can partially succeed, and in this case, err is still nil.
func (d *Dao) GetRoomLiveRecordsRange(ctx context.Context, content string, roomIds []int64, liveTime int64, start, end int) (resp map[int64][]*ListIntValueInfo, err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
okRoomIds := make([]int64, 0, len(roomIds))
for _, roomId := range roomIds {
keyInfo := d.LRoomLiveRecordList(content, roomId, liveTime)
if err = conn.Send("LRANGE", keyInfo.RedisKey, start, end); err != nil {
log.Error("GetRoomLiveRecordsRange conn.Send(LRANGE, %s, %d, %d) error(%v)", keyInfo.RedisKey, start, end, err)
continue
}
okRoomIds = append(okRoomIds, roomId)
}
if err = conn.Flush(); err != nil {
log.Error("GetRoomLiveRecordsRange conn.Flush() error(%v)", err)
return nil, err
}
resp = make(map[int64][]*ListIntValueInfo)
for i := 0; i < len(okRoomIds); i++ {
values, err := redis.Values(conn.Receive())
if err != nil {
log.Error("GetRoomLiveRecordsRange redis.Values(conn.Receive()) error(%v)", err)
continue
}
roomId := okRoomIds[i]
for _, info := range values {
valueInfo := &ListIntValueInfo{}
if err = json.Unmarshal(info.([]byte), &valueInfo); err != nil {
log.Error("GetRoomLiveRecordsRange json unmarshall error(%v)", err)
continue
}
resp[roomId] = append(resp[roomId], valueInfo)
}
}
return resp, nil
}
const (
_roomInfoKey = "room_info_v3:%d"
_anchorInfoKey = "anchor_info:%d"
_onlineListKey = "online_list_v3:%d"
_tagListKey = "tag_list_v3:%d"
_onlineListAllArea = 0
)
var (
_allRoomInfoFields = []string{"uid", "title", "cover", "tags", "background", "description", "live_start_time", "live_status", "live_screen_type", "live_type", "lock_status", "lock_time", "hidden_time", "hidden_status", "area_id", "parent_area_id", "anchor_profile_type", "anchor_round_switch", "anchor_record_switch", "anchor_exp", "popularity_count", "keyframe"}
)
func (d *Dao) filterOutTagList(fields []string) (resp []string, needTagList bool) {
resp = make([]string, 0, len(fields))
for _, f := range fields {
if f == "tag_list" {
needTagList = true
} else {
resp = append(resp, f)
}
}
return
}
func (d *Dao) redisGetRoomInfo(ctx context.Context, roomID int64, fields []string) (data *v1pb.RoomData, err error) {
if len(fields) <= 0 {
return
}
conn := d.redis.Get(ctx)
defer conn.Close()
roomKey := fmt.Sprintf(_roomInfoKey, roomID)
ok, err := redis.Bool(conn.Do("EXPIRE", roomKey, d.c.Common.ExpireTime))
if err != nil && err != redis.ErrNil {
log.Error("redisGetRoomInfo conn.Do(EXPIRE, %s) error(%v)", roomKey, err)
return
}
if !ok {
err = ecode.RoomNotFound
return
}
for _, f := range fields {
if f == "room_id" {
continue
}
if err = conn.Send("HGET", roomKey, f); err != nil {
log.Error("redisGetRoomInfo conn.Send(HGET, %s, %s) error(%v)", roomKey, f, err)
return
}
}
if err = conn.Flush(); err != nil {
log.Error("redisGetRoomInfo conn.Flush error(%v)", err)
return
}
data = &v1pb.RoomData{
RoomId: roomID,
AnchorLevel: new(v1pb.AnchorLevel),
}
for _, f := range fields {
if f == "room_id" {
continue
}
reply, err := conn.Receive()
if e, ok := err.(*redis.Error); ok && strings.Index(e.Error(), "WRONGTYPE") != -1 {
return data, err
}
switch f {
case "uid":
data.Uid, err = redis.Int64(reply, err)
case "title":
data.Title, err = redis.String(reply, err)
case "cover":
data.Cover, err = redis.String(reply, err)
case "tags":
data.Tags, err = redis.String(reply, err)
case "background":
data.Background, err = redis.String(reply, err)
case "description":
data.Description, err = redis.String(reply, err)
case "live_start_time":
data.LiveStartTime, err = redis.Int64(reply, err)
case "live_status":
data.LiveStatus, err = redis.Int64(reply, err)
case "live_screen_type":
data.LiveScreenType, err = redis.Int64(reply, err)
case "live_type":
data.LiveType, err = redis.Int64(reply, err)
case "lock_status":
data.LockStatus, err = redis.Int64(reply, err)
case "lock_time":
data.LockTime, err = redis.Int64(reply, err)
case "hidden_time":
data.HiddenTime, err = redis.Int64(reply, err)
case "hidden_status":
data.HiddenStatus, err = redis.Int64(reply, err)
case "area_id":
data.AreaId, err = redis.Int64(reply, err)
case "parent_area_id":
data.ParentAreaId, err = redis.Int64(reply, err)
case "anchor_san":
data.AnchorSan, err = redis.Int64(reply, err)
case "anchor_profile_type":
data.AnchorProfileType, err = redis.Int64(reply, err)
case "anchor_round_switch":
data.AnchorRoundSwitch, err = redis.Int64(reply, err)
case "anchor_record_switch":
data.AnchorRecordSwitch, err = redis.Int64(reply, err)
case "anchor_exp":
data.AnchorLevel.Score, err = redis.Int64(reply, err)
case "popularity_count":
data.PopularityCount, err = redis.Int64(reply, err)
case "keyframe":
data.Keyframe, err = redis.String(reply, err)
default:
log.Error("redisGetRoomInfo unsupported field(%v), roomID(%d)", f, roomID)
err = ecode.InvalidParam
return nil, err
}
if err != nil {
log.Warn("redisGetRoomInfo conn.Receive() field(%v), error(%v)", f, err)
return nil, err
}
}
return
}
func (d *Dao) redisSetRoomInfo(ctx context.Context, roomID int64, fields []string, data *v1pb.RoomData, fastfail bool) (err error) {
if len(fields) <= 0 {
return
}
conn := d.redis.Get(ctx)
defer conn.Close()
roomKey := fmt.Sprintf(_roomInfoKey, roomID)
ok, err := redis.Bool(conn.Do("EXPIRE", roomKey, d.c.Common.ExpireTime))
if err != nil && err != redis.ErrNil {
log.Error("redisSetRoomInfo conn.Do(EXPIRE, %s) error(%v)", roomKey, err)
return
}
if !ok && fastfail {
return
}
args := make([]interface{}, len(fields)*2+1)
args[0] = roomKey
for i, f := range fields {
var v interface{}
switch f {
case "roomid":
v = data.RoomId
case "uid":
v = data.Uid
case "title":
v = data.Title
case "cover":
v = data.Cover
case "tags":
v = data.Tags
case "background":
v = data.Background
case "description":
v = data.Description
case "live_start_time":
v = data.LiveStartTime
case "live_status":
v = data.LiveStatus
case "live_screen_type":
v = data.LiveScreenType
case "live_type":
v = data.LiveType
case "lock_status":
v = data.LockStatus
case "lock_time":
v = data.LockTime
case "hidden_time":
v = data.HiddenTime
case "hidden_status":
v = data.HiddenStatus
case "area_id":
v = data.AreaId
case "parent_area_id":
v = data.ParentAreaId
case "anchor_san":
v = data.AnchorSan
case "anchor_profile_type":
v = data.AnchorProfileType
case "anchor_round_switch":
v = data.AnchorRoundSwitch
case "anchor_record_switch":
v = data.AnchorRecordSwitch
case "anchor_exp":
v = data.AnchorRecordSwitch
case "popularity_count":
v = data.PopularityCount
case "keyframe":
v = data.Keyframe
default:
log.Error("redisSetRoomInfo unsupported field(%v), roomID(%d)", f, roomID)
return ecode.InvalidParam
}
args[i*2+1] = f
args[i*2+2] = v
}
if _, err = conn.Do("HMSET", args...); err != nil {
log.Error("redisSetRoomInfo conn.Do(HMSET, %v) error(%v)", args, err)
return
}
return
}
func (d *Dao) redisIncreRoomInfo(ctx context.Context, roomID int64, fields []string, data *v1pb.RoomData) (err error) {
if len(fields) <= 0 {
return
}
conn := d.redis.Get(ctx)
defer conn.Close()
roomKey := fmt.Sprintf(_roomInfoKey, roomID)
ok, err := redis.Bool(conn.Do("EXPIRE", roomKey, d.c.Common.ExpireTime))
if err != nil && err != redis.ErrNil {
log.Error("redisIncreRoomInfo conn.Do(EXPIRE, %s) error(%v)", roomKey, err)
return
}
// fast fail if key not exists
if !ok {
return
}
for _, f := range fields {
var v int64
switch f {
case "anchor_san":
v = data.AnchorSan
case "anchor_exp":
v = data.AnchorRecordSwitch
case "popularity_count":
v = data.PopularityCount
default:
log.Error("redisIncreRoomInfo unsupported field(%v), roomID(%d)", f, roomID)
return ecode.InvalidParam
}
if err = conn.Send("HINCRBY", roomKey, f, v); err != nil {
log.Error("redisIncreRoomInfo conn.Send(HINCRBY, %s, %s, %d) error(%v)", roomKey, f, v, err)
return
}
}
if err = conn.Flush(); err != nil {
log.Error("redisIncreRoomInfo conn.Flush error(%v)", err)
return
}
for _, f := range fields {
_, err = conn.Receive()
if err != nil && err != redis.ErrNil {
log.Error("redisIncreRoomInfo conn.Receive() field(%v), error(%v)", f, err)
return
}
}
return
}
func (d *Dao) redisGetOnlineList(ctx context.Context, areaID int64) (list []int64, err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := fmt.Sprintf(_onlineListKey, areaID)
ok, err := redis.Bool(conn.Do("EXPIRE", key, d.c.Common.ExpireTime))
if err != nil && err != redis.ErrNil {
log.Error("redisGetOnlineList conn.Do(EXPIRE, %s) error(%v)", key, err)
return
}
if !ok {
// 不存在或者在播列表为空都会重新去DB获取
return
}
list = make([]int64, 0)
roomids, err := redis.Strings(conn.Do("SMEMBERS", key))
if err != nil && err != redis.ErrNil {
if e, ok := err.(*redis.Error); ok && strings.Index(e.Error(), "WRONGTYPE") == -1 {
log.Error("redisGetOnlineList conn.Do(SMEMBERS, %s) error(%v)", key, err)
return
}
return list, nil
}
for _, id := range roomids {
roomid, err := strconv.ParseInt(id, 10, 64)
if err != nil {
log.Warn("redisGetOnlineList ParseInt(%d) error(%v)", roomid, err)
return nil, err
}
list = append(list, roomid)
}
return
}
func (d *Dao) redisSetOnlineList(ctx context.Context, areaID int64, list []int64) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := fmt.Sprintf(_onlineListKey, areaID)
if len(list) <= 0 {
// 设置哨兵
if _, err = conn.Do("SETEX", key, d.c.Common.ExpireTime, "emptylist"); err != nil {
log.Error("redisSetOnlineList conn.Do(SETEX, %s, %v) error(%v)", key, list, err)
}
return
}
if _, err = conn.Do("DEL", key); err != nil && err != redis.ErrNil {
log.Error("redisSetOnlineList conn.Do(DEL, %s) error(%v)", key, err)
return
}
args := make([]interface{}, len(list)+1)
args[0] = key
for i, id := range list {
args[i+1] = id
}
if _, err = conn.Do("SADD", args...); err != nil {
log.Error("redisSetOnlineList conn.Do(SADD, %s, %v) error(%v)", key, list, err)
return
}
return
}
func (d *Dao) redisAddOnlineList(ctx context.Context, areaID int64, roomID int64) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := fmt.Sprintf(_onlineListKey, areaID)
typ, err := redis.String(conn.Do("TYPE", key))
if err != nil && err != redis.ErrNil {
log.Error("redisAddOnlineList conn.Do(TYPE, %s) error(%v)", key, err)
return
}
if strings.ToLower(typ) == "none" {
// 不存在就不处理
return
} else if strings.ToLower(typ) != "set" {
if _, err = conn.Do("DEL", key); err != nil && err != redis.ErrNil {
log.Error("redisAddOnlineList conn.Do(DEL, %s) error(%v)", key, err)
return
}
}
if _, err = conn.Do("SADD", key, roomID); err != nil {
log.Error("redisAddOnlineList conn.Do(SADD, %s, %d) error(%v)", key, roomID, err)
return
}
return
}
func (d *Dao) redisDelOnlineList(ctx context.Context, areaID int64, roomID int64) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := fmt.Sprintf(_onlineListKey, areaID)
typ, err := redis.String(conn.Do("TYPE", key))
if err != nil && err != redis.ErrNil {
log.Error("redisDelOnlineList conn.Do(TYPE, %s) error(%v)", key, err)
return
}
if strings.ToLower(typ) != "set" {
// 不存在就不处理
return
}
if _, err = conn.Do("SREM", key, roomID); err != nil {
log.Error("redisDelOnlineList conn.Do(SADD, %s, %d) error(%v)", key, roomID, err)
return
}
return
}
func (d *Dao) redisGetTagList(ctx context.Context, roomID int64) (list []*v1pb.TagData, err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := fmt.Sprintf(_tagListKey, roomID)
ok, err := redis.Bool(conn.Do("EXPIRE", key, d.c.Common.ExpireTime))
if err != nil && err != redis.ErrNil {
log.Error("redisGetTagList conn.Do(EXPIRE, %s) error(%v)", key, err)
return nil, err
}
if !ok {
err = ecode.RoomNotFound
return
}
list = make([]*v1pb.TagData, 0)
tags, err := redis.Strings(conn.Do("SMEMBERS", key))
if err != nil && err != redis.ErrNil {
if e, ok := err.(*redis.Error); ok && strings.Index(e.Error(), "WRONGTYPE") == -1 {
log.Error("redisGetTagList conn.Do(SMEMBERS, %s) error(%v)", key, err)
return
}
return list, nil
}
for _, tag := range tags {
seg := strings.Split(tag, ":")
if len(seg) < 5 {
log.Error("redisGetTagList Split(%s) error(%v)", tag, err)
return nil, err
}
data := &v1pb.TagData{
TagExt: strings.Join(seg[4:], ":"),
}
data.TagExpireAt, _ = strconv.ParseInt(seg[3], 10, 64)
if data.TagExpireAt > time.Now().Unix() {
data.TagId, _ = strconv.ParseInt(seg[0], 10, 64)
data.TagSubId, _ = strconv.ParseInt(seg[1], 10, 64)
data.TagValue, _ = strconv.ParseInt(seg[2], 10, 64)
list = append(list, data)
}
}
return
}
func (d *Dao) redisAddTag(ctx context.Context, roomID int64, tag *v1pb.TagData) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := fmt.Sprintf(_tagListKey, roomID)
typ, err := redis.String(conn.Do("TYPE", key))
if err != nil && err != redis.ErrNil {
log.Error("redisAddTag conn.Do(TYPE, %s) error(%v)", key, err)
return
}
if strings.ToLower(typ) == "none" {
// 不存在就不处理
return
} else if strings.ToLower(typ) != "set" {
if _, err = conn.Do("DEL", key); err != nil && err != redis.ErrNil {
log.Error("redisAddTag conn.Do(DEL, %s) error(%v)", key, err)
return
}
}
tagVal := fmt.Sprintf("%d:%d:%d:%d:%s", tag.TagId, tag.TagSubId, tag.TagValue, tag.TagExpireAt, tag.TagExt)
if _, err = conn.Do("SADD", key, tagVal); err != nil {
log.Error("redisAddTag conn.Do(SADD, %s, %s) error(%v)", key, tagVal, err)
return
}
return
}
func (d *Dao) redisSetTagList(ctx context.Context, roomID int64, list []*v1pb.TagData) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := fmt.Sprintf(_tagListKey, roomID)
if len(list) <= 0 {
// 设置哨兵
if _, err = conn.Do("SETEX", key, d.c.Common.ExpireTime, "emptylist"); err != nil {
log.Error("redisSetTagList conn.Do(SETEX, %s, %v) error(%v)", key, list, err)
}
return
}
if _, err = conn.Do("DEL", key); err != nil && err != redis.ErrNil {
log.Error("redisSetTagList conn.Do(DEL, %s) error(%v)", key, err)
return
}
args := make([]interface{}, len(list)+1)
args[0] = key
for i, tag := range list {
args[i+1] = fmt.Sprintf("%d:%d:%d:%d:%s", tag.TagId, tag.TagSubId, tag.TagValue, tag.TagExpireAt, tag.TagExt)
}
if _, err = conn.Do("SADD", args...); err != nil {
log.Error("redisSetTagList conn.Do(SADD, %s, %v) error(%v)", key, list, err)
return
}
return
}

View File

@@ -0,0 +1,80 @@
package dao
import (
"context"
"fmt"
"strconv"
"go-common/app/service/live/dao-anchor/api/grpc/v1"
"go-common/library/log"
)
type redisKeyResp struct {
RedisKey string `json:"redis_key"`
TimeOut int `json:"time_out"`
}
//RoomRecordListForLive 房间侧开播记录相关历史数据存3天
func (d *Dao) LRoomLiveRecordList(content string, roomId int64, liveTime int64) (resp *redisKeyResp) {
resp = &redisKeyResp{}
if liveTime <= 0 {
roomInfo, err := d.FetchRoomByIDs(context.TODO(), &v1.RoomByIDsReq{RoomIds: []int64{roomId}, Fields: []string{"live_start_time"}})
if err != nil || roomInfo.RoomDataSet == nil {
log.Error("LRoomLiveRecordList_err:err=%v;info=%v", err, roomInfo)
return
}
liveTime = roomInfo.RoomDataSet[roomId].LiveStartTime
}
contentType := content + "_" + strconv.Itoa(int(liveTime))
resp.RedisKey = fmt.Sprintf(contentType+"_list_%d", roomId)
resp.TimeOut = 24 * 60 * 60 * 3
return
}
//RoomRecordList 房间侧记录相关历史数据存1天
func (d *Dao) LRoomRecordList(contentType string, roomId int64) (resp *redisKeyResp) {
resp = &redisKeyResp{}
resp.RedisKey = fmt.Sprintf(contentType+"_list_%d", roomId)
resp.TimeOut = 24 * 60 * 60
return
}
//RoomRecordCurrent 房间侧实时数据记录,存一个小时
func (d *Dao) SRoomRecordCurrent(content string, roomId int64) (resp *redisKeyResp) {
resp = &redisKeyResp{}
resp.RedisKey = fmt.Sprintf(content+"_key_%d", roomId)
resp.TimeOut = 60 * 60
return
}
//主播侧开播记录相关历史数据存3天
func (d *Dao) LUserLiveRecordList(content string, uid int64, liveTime int64) (resp *redisKeyResp) {
resp = &redisKeyResp{}
if liveTime <= 0 {
roomInfo, err := d.FetchRoomByIDs(context.TODO(), &v1.RoomByIDsReq{Uids: []int64{uid}, Fields: []string{"live_start_time"}})
if err != nil {
log.Error("LRoomLiveRecordList_err:err=%v;info=%v", err, roomInfo)
return
}
}
contentType := content + "_" + strconv.Itoa(int(liveTime))
resp.RedisKey = fmt.Sprintf(contentType+"_list_%d", uid)
resp.TimeOut = 24 * 60 * 60 * 3
return
}
//主播侧记录相关历史数据存1天
func (d *Dao) LUserRecordList(contentType string, uid int64) (resp *redisKeyResp) {
resp = &redisKeyResp{}
resp.RedisKey = fmt.Sprintf(contentType+"_list_%d", uid)
resp.TimeOut = 24 * 60 * 60
return
}
//UserRecordCurrent 主播侧实时数据记录,存一个小时
func (d *Dao) SUserRecordCurrent(content string, uid int64) (resp *redisKeyResp) {
resp = &redisKeyResp{}
resp.RedisKey = fmt.Sprintf(content+"_key_%d", uid)
resp.TimeOut = 60 * 60
return
}