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,2 @@
### v1.0.0
1. 上线功能xxx

View File

@@ -0,0 +1,6 @@
# Owner
fuyu01
# Author
# Reviewer

View File

@@ -0,0 +1,10 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- fuyu01
labels:
- job
- job/live/xroom-feed
- live
options:
no_parent_owners: true

View File

@@ -0,0 +1,12 @@
# xroom-feed-job
## 项目简介
1.
## 编译环境
## 依赖包
## 编译执行

View File

@@ -0,0 +1,42 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "cmd",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
importpath = "go-common/app/job/live/xroom-feed/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/live/xroom-feed/internal/server/http:go_default_library",
"//app/job/live/xroom-feed/internal/service:go_default_library",
"//library/conf/paladin:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log: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"],
)

View File

@@ -0,0 +1,48 @@
package main
import (
"context"
"flag"
"os"
"os/signal"
"syscall"
"time"
"go-common/app/job/live/xroom-feed/internal/server/http"
"go-common/app/job/live/xroom-feed/internal/service"
"go-common/library/conf/paladin"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
)
func main() {
flag.Parse()
if err := paladin.Init(); err != nil {
panic(err)
}
log.Init(nil) // debug flag: log.dir={path}
defer log.Close()
log.Info("xroom-feed-job start")
ecode.Init(nil)
svc := service.New()
httpSrv := http.New(svc)
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
ctx, _ := context.WithTimeout(context.Background(), 35*time.Second)
httpSrv.Shutdown(ctx)
log.Info("xroom-feed-job exit")
svc.Close()
time.Sleep(time.Second)
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,8 @@
# This is a TOML document. Boom
JobTtl = 20
[roomClient]
AppID = "live.room"
ConnTimeout = "50ms"

View File

@@ -0,0 +1,4 @@
[server]
addr = "0.0.0.0:8000"
timeout = "1s"

View File

@@ -0,0 +1,13 @@
demoExpire = "24h"
[demo]
name = "xroom-feed"
proto = "tcp"
addr = "127.0.0.1:11211"
active = 50
idle = 10
dialTimeout = "100ms"
readTimeout = "200ms"
writeTimeout = "300ms"
idleTimeout = "80s"

View File

@@ -0,0 +1,10 @@
[liveApp]
Addr = "172.16.38.117:3312"
DSN = "live:oWni@ElNs0P0C(dphdj*F1y4@tcp(172.16.38.117:3312)/live-app?timeout=1s&readTimeout=1s&writeTimeout=1s&parseTime=true&loc=Local&charset=utf8mb4,utf8"
ReadDSN = ["live:oWni@ElNs0P0C(dphdj*F1y4@tcp(172.16.38.117:3312)/live-app?timeout=1s&readTimeout=1s&writeTimeout=1s&parseTime=true&loc=Local&charset=utf8mb4,utf8"]
Active = 20
Idle = 10
IdleTimeout ="4h"
QueryTimeout = "200ms"
ExecTimeout = "300ms"
TranTimeout = "400ms"

View File

@@ -0,0 +1,13 @@
recExpire = "24h"
[rec]
name = "xroom-feed"
proto = "tcp"
addr = "127.0.0.1:6379"
idle = 10
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"

View File

@@ -0,0 +1,42 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"cache.go",
"conf.go",
"dao.go",
"rediskey.go",
],
importpath = "go-common/app/job/live/xroom-feed/internal/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/live/xroom-feed/internal/model:go_default_library",
"//app/service/live/dao-anchor/api/grpc/v1:go_default_library",
"//library/cache/redis:go_default_library",
"//library/conf/paladin:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/time: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"],
)

View File

@@ -0,0 +1,45 @@
package dao
import (
"context"
daoAnchorV1 "go-common/app/service/live/dao-anchor/api/grpc/v1"
"go-common/library/log"
)
func (d *Dao) SetRecPoolCache(ctx context.Context, key string, list string, expire int) (err error) {
log.Info("[SetRecPoolCache]key: %s", key)
rconn := d.redis.Get(ctx)
defer rconn.Close()
if _, err = rconn.Do("SET", key, list, "EX", expire); err != nil {
log.Error("[SetRecPoolCache]redis.Set error(%v)", err)
}
return
}
func (d *Dao) SetRecInfoCache(ctx context.Context, list map[int64]*daoAnchorV1.RoomData) (err error) {
rconn := d.redis.Get(ctx)
defer rconn.Close()
//rconn.Send("MULTI")
for roomId, roomData := range list {
if roomData == nil {
continue
}
key, expire := d.getRecInfoKey(roomId)
rconn.Send("HMSET", key, "room_id", roomData.RoomId, "uid", roomData.Uid, "title", roomData.Title, "popularity_count", roomData.PopularityCount, "Keyframe", roomData.Keyframe, "cover", roomData.Cover, "parent_area_id", roomData.ParentAreaId, "parent_area_name", roomData.ParentAreaName, "area_id", roomData.AreaId, "area_name", roomData.AreaName)
rconn.Send("EXPIRE", key, expire)
}
if err = rconn.Flush(); err != nil {
log.Error("[SetRecInfoCache]redis.Set error(%v)", err)
}
for _, roomData := range list {
if roomData == nil {
continue
}
rconn.Receive()
rconn.Receive()
}
return
}

View File

@@ -0,0 +1,69 @@
package dao
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"go-common/app/job/live/xroom-feed/internal/model"
"go-common/library/database/sql"
"go-common/library/log"
)
const (
_sqlConf = "select id, name, type, rules, module_type, position, percent, priority from ap_rec_pool_conf where is_del = 0 and (status = 2 or (status = 1 and start_time < '%s' and end_time > '%s'))"
_sqlWhiteList = "select room_id from ap_rec_white_list where rec_id = %d and is_del = 0"
)
func (d *Dao) GetConfFromDb() (ruleConf []*model.RecPoolConf, err error) {
var rows *sql.Rows
ruleConf = make([]*model.RecPoolConf, 0)
timeNow := time.Now().Format("2006-01-02 15:04:05")
rows, err = d.liveAppDb.Query(context.TODO(), fmt.Sprintf(_sqlConf, timeNow, timeNow))
if err != nil {
log.Error("getConfFromDb_error:%+v", err)
return
}
for rows.Next() {
r := new(model.RecPoolConf)
if err = rows.Scan(&r.Id, &r.Name, &r.ConfType, &r.Rules, &r.ModuleType, &r.Position, &r.Percent, &r.Priority); err != nil {
log.Error("getConfFromDb_parseError:%+v", err)
continue
}
ruleConf = append(ruleConf, r)
}
return
}
func (d *Dao) GetWhiteList(ctx context.Context, id int) (whiteStr string, err error) {
//白名单获取
var whiteRows *sql.Rows
whiteRows, err = d.liveAppDb.Query(ctx, fmt.Sprintf(_sqlWhiteList, id))
if err != nil {
log.Error("getWhiteListFromDb_error:%+v", err)
return
}
whiteList := make([]string, 0)
for whiteRows.Next() {
r := new(model.RecWhiteList)
if err = whiteRows.Scan(&r.RoomId); err != nil {
log.Error("getWhiteListFromDb_parseError:%+v", err)
continue
}
if r.RoomId == 0 {
continue
}
whiteList = append(whiteList, strconv.Itoa(r.RoomId))
}
if len(whiteList) <= 0 {
return
}
whiteStr = strings.Join(whiteList, ",")
return
}

View File

@@ -0,0 +1,78 @@
package dao
import (
"context"
"fmt"
"time"
"go-common/library/cache/redis"
"go-common/library/conf/paladin"
"go-common/library/database/sql"
"go-common/library/log"
xtime "go-common/library/time"
)
const (
_recInfoExpireTtl = 86400
_recInfoKey = "rec_info_%d"
)
// Dao dao.
type Dao struct {
liveAppDb *sql.DB
redis *redis.Pool
redisExpire int32
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
// New new a dao and return.
func New() (dao *Dao) {
var (
dc struct {
LiveApp *sql.Config
}
rc struct {
Rec *redis.Config
DemoExpire xtime.Duration
}
)
checkErr(paladin.Get("mysql.toml").UnmarshalTOML(&dc))
checkErr(paladin.Get("redis.toml").UnmarshalTOML(&rc))
fmt.Printf("%+v", dc)
dao = &Dao{
// mysql
liveAppDb: sql.NewMySQL(dc.LiveApp),
// redis
redis: redis.NewPool(rc.Rec),
redisExpire: int32(time.Duration(rc.DemoExpire) / time.Second),
}
return
}
// Close close the resource.
func (d *Dao) Close() {
d.redis.Close()
d.liveAppDb.Close()
}
// Ping ping the resource.
func (d *Dao) Ping(ctx context.Context) (err error) {
if err = d.pingRedis(ctx); err != nil {
return
}
return d.liveAppDb.Ping(ctx)
}
func (d *Dao) pingRedis(ctx context.Context) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
if _, err = conn.Do("SET", "ping", "pong"); err != nil {
log.Error("conn.Set(PING) error(%v)", err)
}
return
}

View File

@@ -0,0 +1,9 @@
package dao
import "fmt"
func (d *Dao) getRecInfoKey(roomId int64) (key string, expire int){
key = fmt.Sprintf(_recInfoKey, roomId)
expire = _recInfoExpireTtl
return
}

View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["model.go"],
importpath = "go-common/app/job/live/xroom-feed/internal/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,39 @@
package model
type RuleProtocol struct {
Cond string `json:"cond"`
Key string `json:"key"`
ConfType string `json:"type"`
Max int64 `json:"max"`
Min int64 `json:"min"`
TopV int64 `json:"top_v"`
StringV string `json:"string_v"`
Condition []*RuleProtocol `json:"conditions"`
}
type RecPoolConf struct {
Id int `json:"id"`
Name string `json:"name"`
ConfType int `json:"type"`
Rules string `json:"rules"`
Priority int64 `json:"priority"`
Percent int64 `json:"percent"`
ModuleType int64 `json:"module_type"`
Position int64 `json:"position"`
}
type RecWhiteList struct {
RoomId int `json:"room_id"`
}
type RoomData = struct {
RoomId int `json:"room_id"`
Title string `json:"title"`
PopularityCount int `json:"popularity_count"`
Keyframe string `json:"Keyframe"`
Cover string `json:"cover"`
ParentAreaId int `json:"parent_area_id"`
ParenAreaName string `json:"parent_area_name"`
AreaId int `json:"area_id"`
AreaName string `json:"area_name"`
}

View File

@@ -0,0 +1,35 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["http.go"],
importpath = "go-common/app/job/live/xroom-feed/internal/server/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/live/xroom-feed/internal/service:go_default_library",
"//library/conf/paladin:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/verify: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"],
)

View File

@@ -0,0 +1,61 @@
package http
import (
"net/http"
"go-common/app/job/live/xroom-feed/internal/service"
"go-common/library/conf/paladin"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/verify"
)
var (
svc *service.Service
)
// New new a bm server.
func New(s *service.Service) (engine *bm.Engine) {
var(
hc struct {
Server *bm.ServerConfig
}
)
if err := paladin.Get("http.toml").UnmarshalTOML(&hc); err != nil {
if err != paladin.ErrNotExist {
panic(err)
}
}
svc = s
engine = bm.DefaultServer(hc.Server)
initRouter(engine, verify.New(nil))
if err := engine.Start(); err != nil {
panic(err)
}
return
}
func initRouter(e *bm.Engine, v *verify.Verify) {
e.Ping(ping)
e.Register(register)
g := e.Group("/x/xroom-feed")
{
g.GET("/start", v.Verify, howToStart)
}
}
func ping(ctx *bm.Context) {
if err := svc.Ping(ctx); err != nil {
log.Error("ping error(%v)", err)
ctx.AbortWithStatus(http.StatusServiceUnavailable)
}
}
func register(c *bm.Context) {
c.JSON(map[string]interface{}{}, nil)
}
// example for http request handler.
func howToStart(c *bm.Context) {
c.String(0, "Golang 大法好 !!!")
}

View File

@@ -0,0 +1,53 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"condrule.go",
"is_black.go",
"onlinelist.go",
"reccache.go",
"rediskey.go",
"rule.go",
"service.go",
"sort.go",
],
importpath = "go-common/app/job/live/xroom-feed/internal/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/live/xroom-feed/internal/dao:go_default_library",
"//app/job/live/xroom-feed/internal/model:go_default_library",
"//app/service/live/dao-anchor/api/grpc/v1:go_default_library",
"//app/service/live/room/api/liverpc:go_default_library",
"//app/service/live/room/api/liverpc/v1:go_default_library",
"//library/conf/env:go_default_library",
"//library/conf/paladin:go_default_library",
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/net/rpc/liverpc:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/time:go_default_library",
"//library/xstr: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"],
)

View File

@@ -0,0 +1,402 @@
package service
import (
"context"
"encoding/json"
"go-common/app/job/live/xroom-feed/internal/model"
daoAnchorV1 "go-common/app/service/live/dao-anchor/api/grpc/v1"
"go-common/library/log"
)
const (
_isAll = -1
_roomStatusTagId = 3
_onlineCurrent = 3
)
type attrFilter struct {
attrId int64
attrSubId int64
max int64
min int64
top int64
}
var attrType = map[string]int64{
_onlineType: 1,
_incomeType: 2,
_dmsType: 3,
_hourRankType: 4,
_liveDaysType: 5,
}
var tagType = map[string]bool{
_areaType: true,
_roomStatusType: true,
_anchorCateType: true,
}
type filterItem struct {
isTagHit bool
item *daoAnchorV1.AttrResp
}
// ParentAreaIds ...
var ParentAreaIds []int64
// AreaIds ...
var AreaIds [][]int64
// getConditionTypeIndex ...
func (s *Service) getConditionTypeIndex(conds []*model.RuleProtocol, sType string) (i int64) {
i = -1
for index, cond := range conds {
if cond == nil {
continue
}
if cond.ConfType == sType {
i = int64(index)
break
}
}
return
}
// genCondConfRoomList ... recId for log
func (s *Service) genCondConfRoomList(ctx context.Context, Condition []*model.RuleProtocol, Cond string, recId int) (roomIds []int64) {
ParentAreaIds := make([]int64, 0)
AreaIds := make([][]int64, 0)
areaFilter := make(map[int64]map[int64]bool)
roomStatus := make(map[int64]bool)
anchorCate := make(map[int64]bool)
attrs := make([]*daoAnchorV1.AttrReq, 0)
attrsFilter := make(map[int64]map[int64]*attrFilter)
for _, cond := range Condition {
// 统计型标签
if attrTypeV, ok := attrType[cond.Key]; ok {
isAppend := true
param := &daoAnchorV1.AttrReq{
AttrId: attrTypeV,
}
if len(cond.Condition) <= 0 || cond.Condition[0] == nil {
continue
}
if cond.Key == _onlineType {
switch cond.Condition[0].StringV {
case "current":
param.AttrSubId = 1
case "last7day":
param.AttrSubId = 2
case "last30day":
param.AttrSubId = 3
}
}
if cond.Key == _incomeType || cond.Key == _dmsType {
switch cond.Condition[0].StringV {
case "range15min":
param.AttrSubId = 1
case "range30min":
param.AttrSubId = 2
case "range45min":
param.AttrSubId = 3
case "range60min":
param.AttrSubId = 4
}
}
if cond.Key == _liveDaysType {
switch cond.Condition[0].StringV {
case "last7day":
param.AttrSubId = 3
case "last30day":
param.AttrSubId = 4
}
}
if cond.Key == _hourRankType {
if cond.Condition[0].TopV <= 0 {
isAppend = false
}
param.AttrSubId = 1 // 目前小时榜固定是1 @orca
}
// 排除后台选择了小时榜但是top写了0导致dao anchor不返回这个attr导致下面and or 过滤出错
if isAppend {
attrsFilter[attrTypeV] = make(map[int64]*attrFilter)
attrsFilter[attrTypeV][param.AttrSubId] = &attrFilter{}
if i := s.getConditionTypeIndex(cond.Condition, _confTypeRange); i >= 0 && cond.Condition[i] != nil {
attrsFilter[attrTypeV][param.AttrSubId].max = cond.Condition[i].Max
attrsFilter[attrTypeV][param.AttrSubId].min = cond.Condition[i].Min
}
if i := s.getConditionTypeIndex(cond.Condition, _confTypeTop); i >= 0 && cond.Condition[i] != nil {
attrsFilter[attrTypeV][param.AttrSubId].top = cond.Condition[i].TopV
}
attrs = append(attrs, param)
}
}
// 展示型标签
if _, ok := tagType[cond.Key]; ok {
if cond.Key == _areaType {
for index, areaCond := range cond.Condition {
// parent area id dimension
if index == 0 {
err := json.Unmarshal([]byte(areaCond.StringV), &ParentAreaIds)
if err != nil {
log.Error("[genCondConfRoomList]recId:%d, unmarshalParentAreaIdErr:%+v", recId, err)
continue
}
if len(ParentAreaIds) == 1 && ParentAreaIds[0] == _isAll {
areaFilter[_isAll] = make(map[int64]bool)
break
}
for _, pId := range ParentAreaIds {
if _, ok := areaFilter[pId]; !ok {
areaFilter[pId] = make(map[int64]bool)
}
}
}
// area id dimension
if index == 1 {
err := json.Unmarshal([]byte(areaCond.StringV), &AreaIds)
if err != nil {
log.Error("[genCondConfRoomList]recId:%d, unmarshalAreaIdErr:%+v", recId, err)
continue
}
for pIdIndex, ids := range AreaIds {
// 后台保证长度对应 ParentAreaId[pIdIndex]
if len(ParentAreaIds) <= pIdIndex {
continue
}
if _, ok := areaFilter[ParentAreaIds[pIdIndex]]; !ok {
continue
}
for _, id := range ids {
if _, ok := areaFilter[ParentAreaIds[pIdIndex]][id]; !ok {
areaFilter[ParentAreaIds[pIdIndex]][id] = true
}
}
}
}
}
}
if cond.Key == _roomStatusType && len(cond.Condition) >= 0 && cond.Condition[0] != nil {
switch cond.Condition[0].StringV {
case "lottery":
roomStatus[2] = true
case "pk":
roomStatus[1] = true
}
}
if cond.Key == _anchorCateType && len(cond.Condition) >= 0 && cond.Condition[0] != nil {
switch cond.Condition[0].StringV {
case "normal":
anchorCate[0] = true
case "sign":
anchorCate[1] = true
case "union":
anchorCate[2] = true
}
}
}
}
log.Info("[genCondConfRoomList]recId:%d, attrs: %+v", recId, attrs)
roomList, err := s.getOnlineListByAttrs(ctx, attrs)
if err != nil {
log.Error("[getOnlineListByAttrs]recId:%d, getOnlineListByAttrsErr:%+v, resp:%+v", recId, err, roomList)
return
}
filterRoomData := make([]*filterItem, 0)
for _, roomData := range roomList {
isHit := false
isTagHit := false
if Cond == _condAnd {
isHit = s.andFilter(attrsFilter, areaFilter, roomStatus, anchorCate, roomData, recId)
}
if Cond == _condOr {
isHit, isTagHit = s.orFilter(attrsFilter, areaFilter, roomStatus, anchorCate, roomData, recId)
}
if !isHit {
continue
}
// 命中逻辑
filterRoomData = append(filterRoomData, &filterItem{
isTagHit: isTagHit,
item: roomData,
})
}
return s.sort(attrsFilter, filterRoomData, Cond, recId)
}
func (s *Service) andFilter(attrsFilters map[int64]map[int64]*attrFilter, areaFilter map[int64]map[int64]bool, roomStatus, anchorCate map[int64]bool, roomData *daoAnchorV1.AttrResp, recId int) (isHit bool) {
// area filter
if len(areaFilter) > 0 {
_, isParentAll := areaFilter[_isAll]
// 不是全选继续判断
if !isParentAll {
if _, ok := areaFilter[roomData.ParentAreaId]; !ok {
log.Info("[andFilter]recId:%d, ParentAreaIdNotMatch:areaFilter:%+v, parentId:%d, roomId:%d", recId, areaFilter, roomData.ParentAreaId, roomData.RoomId)
return false
}
_, isAreaAll := areaFilter[roomData.ParentAreaId][_isAll]
_, isAreaIdExist := areaFilter[roomData.ParentAreaId][roomData.AreaId]
if !isAreaAll && !isAreaIdExist {
log.Info("[andFilter]recId:%d, AreaIdNotMatch:areaFilter:%+v, Id:%d, roomId:%d", recId, areaFilter, roomData.AreaId, roomData.RoomId)
return false
}
}
}
//如果配置了房间状态筛选
if len(roomStatus) > 0 {
roomStatusTag := &daoAnchorV1.TagData{}
for _, tag := range roomData.TagList {
if tag.TagId == _roomStatusTagId {
roomStatusTag = tag
break
}
}
if _, ok := roomStatus[roomStatusTag.TagSubId]; !ok {
log.Info("[andFilter]recId:%d, roomStatusNotMatch:roomStatus:%+v, tagSubId:%d, roomId:%d", recId, roomStatus, roomStatusTag.TagSubId, roomData.RoomId)
return false
}
}
//如果配置了主播类型筛选
if len(anchorCate) > 0 {
if _, ok := anchorCate[roomData.AnchorProfileType]; !ok {
log.Info("[andFilter]recId:%d, anchorCateNotMatch:anchorCate:%+v, anchorProfileType:%d, roomId:%d", recId, anchorCate, roomData.AnchorProfileType, roomData.RoomId)
return false
}
}
// attr(统计类) filter
attrsMap := make(map[int64]*daoAnchorV1.AttrData)
for _, attr := range roomData.AttrList {
attrsMap[attr.AttrId] = attr
}
for attrId, attrFilter := range attrsFilters {
if _, ok := attrsMap[attrId]; !ok {
log.Info("[andFilter]recId:%d, attrNotExist:attrId:%d, attrFilter:%d, roomID:%d", recId, attrId, attrsMap, roomData.RoomId)
return false
}
for attrSubId, attrInfo := range attrFilter {
if attrInfo == nil {
continue
}
if attrsMap[attrId].AttrSubId == attrSubId {
if attrInfo.min > 0 && attrsMap[attrId].AttrValue < attrInfo.min {
log.Info("[andFilter]recId:%d, attrMinNotMatch:attrId:%d, value:%d, max:%d, min:%d, roomID:%d", recId, attrId, attrsMap[attrId].AttrValue, attrInfo.max, attrInfo.min, roomData.RoomId)
return false
}
if attrInfo.max > 0 && attrsMap[attrId].AttrValue > attrInfo.max {
log.Info("[andFilter]recId:%d, attrMaxNotMatch:attrId:%d, value:%d, max:%d, min:%d, roomID:%d", recId, attrId, attrsMap[attrId].AttrValue, attrInfo.max, attrInfo.min, roomData.RoomId)
return false
}
}
}
}
log.Info("[andFilter]recId:%d, successMatch:roomData:%+v,attrs:%+v, areaFilter:%+v, roomStatus:%+v, anchorState:%+v, roomID:%d", recId, roomData, attrsFilters, areaFilter, roomStatus, anchorCate, roomData.RoomId)
return true
}
func (s *Service) orFilter(attrsFilters map[int64]map[int64]*attrFilter, areaFilter map[int64]map[int64]bool, roomStatus, anchorCate map[int64]bool, roomData *daoAnchorV1.AttrResp, recId int) (isHit bool, isTagHit bool) {
// area filter
isTagHit = false
if len(areaFilter) > 0 {
if _, ok := areaFilter[_isAll]; ok {
log.Info("[orFilter]recId:%d, ParentAreaIdAllMatch:areaFilter:%+v, parentId:%d, roomID:%d", recId, areaFilter, roomData.ParentAreaId, roomData.RoomId)
isTagHit = true
return true, isTagHit
}
if _, ok := areaFilter[roomData.ParentAreaId]; ok {
log.Info("[orFilter]recId:%d, ParentAreaIdMatch:areaFilter:%+v, parentId:%d, roomID:%d", recId, areaFilter, roomData.ParentAreaId, roomData.RoomId)
isTagHit = true
return true, isTagHit
}
_, isAll := areaFilter[roomData.ParentAreaId][_isAll]
_, isAreaIdExist := areaFilter[roomData.ParentAreaId][roomData.AreaId]
if isAll || isAreaIdExist {
log.Info("[orFilter]recId:%d, AreaIdMatch:areaFilter:%+v, Id:%d, roomID:%d", recId, areaFilter, roomData.AreaId, roomData.RoomId)
isTagHit = true
return true, isTagHit
}
}
//如果配置了房间状态筛选
if len(roomStatus) > 0 {
roomStatusTag := &daoAnchorV1.TagData{}
for _, tag := range roomData.TagList {
if tag.TagId == _roomStatusTagId {
roomStatusTag = tag
break
}
}
if _, ok := roomStatus[roomStatusTag.TagSubId]; ok {
log.Info("[orFilter]recId:%d, roomStatusMatch:roomStatus:%+v, tagSubId:%d, roomID:%d", recId, roomStatus, roomStatusTag.TagSubId, roomData.RoomId)
isTagHit = true
return true, isTagHit
}
}
//如果配置了主播类型筛选
if len(anchorCate) > 0 {
if _, ok := anchorCate[roomData.AnchorProfileType]; ok {
log.Info("[orFilter]recId:%d, anchorCateMatch:anchorCate:%+v, anchorProfileType:%d, roomID:%d", recId, anchorCate, roomData.AnchorProfileType, roomData.RoomId)
isTagHit = true
return true, isTagHit
}
}
// attr filter
attrDataMap := make(map[int64]*daoAnchorV1.AttrData)
for _, attr := range roomData.AttrList {
attrDataMap[attr.AttrId] = attr
}
for attrId, attrFilter := range attrsFilters {
if _, ok := attrDataMap[attrId]; !ok {
continue
}
//小时榜不需要判断max min
if attrId == attrType[_hourRankType] {
return true, isTagHit
}
for attrSubId, attrFilterInfo := range attrFilter {
attrData, ok := attrDataMap[attrId]
if !ok {
continue
}
if attrData.AttrSubId == attrSubId {
if attrFilterInfo.min > 0 && attrData.AttrValue < attrFilterInfo.min {
continue
}
if attrFilterInfo.max > 0 && attrData.AttrValue > attrFilterInfo.max {
continue
}
log.Info("[orFilter]recId:%d, attrMaxMinMatch:attrId:%d, value:%d, max:%d, min:%d, roomID:%d", recId, attrId, attrDataMap[attrId].AttrValue, attrFilterInfo.max, attrFilterInfo.min, roomData.RoomId)
return true, isTagHit
}
}
}
log.Info("[orFilter]recId:%d, failMatch:roomData:%+v,attrs:%+v, areaFilter:%+v, roomStatus:%+v, anchorState:%+v, roomId:%d", recId, roomData, attrsFilters, areaFilter, roomStatus, anchorCate, roomData.RoomId)
return false, isTagHit
}

View File

@@ -0,0 +1,58 @@
package service
import (
"context"
"time"
roomServeice "go-common/app/service/live/room/api/liverpc/v1"
"go-common/library/conf/env"
"go-common/library/log"
"go-common/library/net/metadata"
)
func (s *Service) isBlackRoomID(roomID int64) bool {
bl := s.indexBlackList.Load()
backList, ok := bl.(map[int64]bool)
if !ok {
log.Warn("[isBlackRoomID] cache err: %+v", bl)
backList = s.getBlackList()
}
if _, ok := backList[roomID]; !ok {
return false
}
return true
}
func (s *Service) getBlackList() (res map[int64]bool) {
cCtx := metadata.NewContext(context.TODO(), metadata.MD{metadata.Color: env.Color})
ctx, _ := context.WithTimeout(cCtx, time.Duration(200*time.Millisecond))
res = make(map[int64]bool)
req := &roomServeice.RoomMngIsBlackReq{}
resp, err := s.roomService.V1RoomMng.IsBlack(ctx, req)
if err != nil {
log.Error("[getBlackList] rpc V1RoomMng.IsBlack err:%v", err)
return
}
if resp.Code != 0 {
log.Error("[getBlackList] rpc V1RoomMng.IsBlack err code:%d", resp.Code)
return
}
for roomID := range resp.Data {
res[roomID] = true
}
return
}
func (s *Service) loadBlackList() {
m := s.getBlackList()
s.indexBlackList.Store(m)
}
func (s *Service) blackListProc() {
time.Sleep(time.Second * 60)
s.loadBlackList()
}

View File

@@ -0,0 +1,27 @@
package service
import (
"context"
daoAnchorV1 "go-common/app/service/live/dao-anchor/api/grpc/v1"
"go-common/library/log"
)
func (s *Service) getOnlineListByAttrs(ctx context.Context, attrs []*daoAnchorV1.AttrReq) (roomList map[int64]*daoAnchorV1.AttrResp, err error){
roomList = make(map[int64]*daoAnchorV1.AttrResp)
RoomOnlineListByAttrsResp, err := s.daoAnchor.RoomOnlineListByAttrs(ctx, &daoAnchorV1.RoomOnlineListByAttrsReq{
Attrs: attrs,
})
if err != nil {
log.Error("[getOnlineListByAttrs]rpc_error:%+v", err)
return
}
if RoomOnlineListByAttrsResp == nil || len(RoomOnlineListByAttrsResp.Attrs) <= 0 {
log.Error("[getOnlineListByAttrs]RoomList_empty")
return
}
roomList = RoomOnlineListByAttrsResp.Attrs
return
}

View File

@@ -0,0 +1,98 @@
package service
import (
"context"
"math"
daoAnchorV1 "go-common/app/service/live/dao-anchor/api/grpc/v1"
"go-common/library/log"
"go-common/library/sync/errgroup"
)
const (
_video = 33
_music = 34
)
var _fields = []string{
"room_id",
"uid",
"title",
"popularity_count",
"keyframe",
"cover",
"parent_area_id",
"parent_area_name",
"area_id",
"area_name",
}
func (s *Service) setRecInfoCache(ctx context.Context, roomIds []int64) (filterIds []int64) {
filterIds = make([]int64, 0)
chunkSize := 100
// 批次
chunkNum := int(math.Ceil(float64(len(roomIds)) / float64(chunkSize)))
chunkIds := make([][]int64, chunkNum)
wg := errgroup.Group{}
for i := 1; i <= chunkNum; i++ {
x := i
wg.Go(func() error {
chunkRoomIds := make([]int64, 10)
if x == chunkNum {
chunkRoomIds = roomIds[(x-1)*chunkSize:]
} else {
chunkRoomIds = roomIds[(x-1)*chunkSize : x*chunkSize]
}
resp, err := s.daoAnchor.FetchRoomByIDs(ctx, &daoAnchorV1.RoomByIDsReq{
RoomIds: chunkRoomIds,
Fields: _fields,
})
if err != nil {
log.Error("[setRecInfoCache]FetchRoomByIDs_error:%+v", err)
return nil
}
if resp == nil || len(resp.RoomDataSet) == 0 {
log.Info("[setRecInfoCache]FetchRoomByIDs_empty")
return nil
}
filterRoomData := make(map[int64]*daoAnchorV1.RoomData)
for roomId, data := range resp.RoomDataSet {
if data == nil {
continue
}
if data.Cover == "" && data.Keyframe == "" {
log.Info("[setRecInfoCache]emptyCoverOrKeyFrame, roomId:%d ,cover:%s, keyframe:%s", data.RoomId, data.Cover, data.Keyframe)
continue
}
if data.AreaName == "" || data.ParentAreaName == "" {
log.Info("[setRecInfoCache]emptyAreaName, roomId:%d ,areaName:%s, parentAreaName:%s", data.RoomId, data.AreaName, data.ParentAreaName)
continue
}
if data.AreaId == _video || data.AreaId == _music {
log.Info("[setRecInfoCache]musicOrVideoArea, roomId:%d ,area:%d", data.RoomId, data.AreaId)
continue
}
if s.isBlackRoomID(data.RoomId) {
log.Info("[setRecInfoCache]is IndexBlackRoomID, roomId:%d ", data.RoomId)
continue
}
chunkIds[x-1] = append(chunkIds[x-1], roomId)
filterRoomData[roomId] = data
}
s.dao.SetRecInfoCache(ctx, filterRoomData)
return nil
})
}
err := wg.Wait()
if err != nil {
log.Error("[setRecInfoCache]waitError:%+v", err)
}
for _, ids := range chunkIds {
for _, id := range ids {
filterIds = append(filterIds, id)
}
}
return
}

View File

@@ -0,0 +1,28 @@
package service
import "fmt"
const (
_recPoolTtl = 60
_recPoolKey = "rec_pool_%d"
_recInfoExpireTtl = 86400
_recInfoKey = "rec_info_%d"
_recConfExpireTtl = 86400
_recConfKey = "rec_conf"
)
func (s *Service) getRecPoolKey(id int) (key string, expire int) {
key = fmt.Sprintf(_recPoolKey, id)
expire = _recPoolTtl
return
}
func (s *Service) getRecInfoKey(roomId int) (key string, expire int) {
key = fmt.Sprintf(_recInfoKey, roomId)
expire = _recInfoExpireTtl
return
}
func (s *Service) getRecConfKey() (key string, expire int) {
return _recConfKey, _recConfExpireTtl
}

View File

@@ -0,0 +1,191 @@
package service
import (
"context"
"encoding/json"
"strconv"
"strings"
"time"
"go-common/library/conf/env"
"go-common/library/net/metadata"
"go-common/app/job/live/xroom-feed/internal/model"
"go-common/library/log"
"go-common/library/sync/errgroup"
"go-common/library/xstr"
)
const (
_whiteListType = 1
_condType = 2
_fileListType = 3
_areaType = "area"
_onlineType = "online"
_incomeType = "income"
_dmsType = "dms"
_liveDaysType = "live_days"
_hourRankType = "hour_rank"
_anchorCateType = "anchor_cate"
_roomStatusType = "room_status"
_condAnd = "and"
_condOr = "or"
_confTypeString = "string"
_confTypeRange = "range"
_confTypeTop = "top"
)
// JobTTl ...
type JobTTl struct {
ttl int
}
func (s *Service) reloadConfFromDb() {
t1 := time.NewTicker(time.Second * 5)
defer t1.Stop()
for {
select {
case <-t1.C:
confList, err := s.dao.GetConfFromDb()
if err != nil {
log.Error("[reloadConfFromDb]getConfFromDb_error:%+v", err)
continue
}
key, expire := s.getRecConfKey()
listStr, err := json.Marshal(confList)
if err == nil {
s.dao.SetRecPoolCache(context.TODO(), key, string(listStr), expire)
}
s.ruleConf.Store(confList)
}
}
}
func (s *Service) loadConfFromDb() {
confList, err := s.dao.GetConfFromDb()
if err != nil {
log.Error("[loadConfFromDb]getConfFromDb_error:%+v", err)
return
}
s.ruleConf.Store(confList)
}
func (s *Service) parseCondConf(cond string) (condConf *model.RuleProtocol, err error) {
condConf = new(model.RuleProtocol)
err = json.Unmarshal([]byte(cond), &condConf)
if err != nil {
log.Error("[parseCondConf]Unmarshal err: %+v", err)
return
}
return
}
func (s *Service) reloadRecList() {
t1 := time.NewTicker(time.Second * 20)
defer t1.Stop()
for {
select {
case <-t1.C:
s.genRecListJob()
}
}
}
// 对所有配置的规则取数据的主程序
func (s *Service) genRecListJob() {
log.Info("[genRecListJob]genRecListJob start")
ttl := 20
ttl, err := s.ac.Get("JobTtl").Int()
if err != nil {
log.Error("[genRecListJob]getJobTtlConfFromSven_error:%+v", err)
}
confList := s.ruleConf.Load()
// assert
res, ok := confList.([]*model.RecPoolConf)
if !ok {
log.Error("[genRecListJob]conf assert error! %+v", confList)
return
}
if len(res) <= 0 {
log.Warn("[genRecListJob]confList_empty")
return
}
//可配置时间结束子任务 内部要有阻塞操作
cCtx := metadata.NewContext(context.TODO(), metadata.MD{metadata.Color: env.Color})
ctx, cancel := context.WithTimeout(cCtx, time.Duration(ttl)*time.Second)
wg := errgroup.Group{}
// 每条规则之间无关联可以不互相cancel只看超时ctx (可在内部对每个操作设置)
// 规则获取loop
for _, rule := range res {
// 白名单规则
if rule.ConfType == _whiteListType || rule.ConfType == _fileListType {
ruleId := rule.Id
wg.Go(func() error {
listStr, err := s.dao.GetWhiteList(ctx, ruleId)
if err != nil {
log.Error("[genRecListJob]GetWhiteList_err:%+v", err)
return nil
}
listStrArr := strings.Split(listStr, ",")
listIntArr := make([]int64, 0)
for _, roomIdStr := range listStrArr {
roomIdInt, _ := strconv.ParseInt(roomIdStr, 10, 64)
listIntArr = append(listIntArr, roomIdInt)
}
if len(listIntArr) > 0 {
ids := s.setRecInfoCache(ctx, listIntArr)
key, expire := s.getRecPoolKey(ruleId)
s.dao.SetRecPoolCache(ctx, key, xstr.JoinInts(ids), expire)
}
return nil
})
}
// 条件规则
if rule.ConfType == _condType {
ruleId := rule.Id
ruleStr := rule.Rules
wg.Go(func() error {
condConf, err := s.parseCondConf(ruleStr)
if err != nil {
log.Error("[genRecListJob]parseCondConf_err:%+v", err)
return nil
}
if len(condConf.Condition) == 0 {
log.Error("[genRecListJob]parseCondConf_empty")
return nil
}
roomIds := s.genCondConfRoomList(ctx, condConf.Condition, condConf.Cond, ruleId)
if len(roomIds) > 0 {
ids := s.setRecInfoCache(ctx, roomIds)
key, expire := s.getRecPoolKey(ruleId)
s.dao.SetRecPoolCache(ctx, key, xstr.JoinInts(ids), expire)
}
return nil
})
}
}
// 超时检测
select {
case <-ctx.Done():
{
log.Info("[genRecListJob]job time out: %d", ttl)
cancel()
}
}
err = wg.Wait()
if err != nil {
log.Error("[genRecListJob]rule loop wait err:%+v", err)
}
log.Info("[genRecListJob]genRecListJob done")
}

View File

@@ -0,0 +1,80 @@
package service
import (
"context"
"sync/atomic"
"time"
"go-common/library/log"
"go-common/library/net/rpc/liverpc"
"go-common/library/net/rpc/warden"
xtime "go-common/library/time"
"go-common/app/job/live/xroom-feed/internal/dao"
daoAnchor "go-common/app/service/live/dao-anchor/api/grpc/v1"
roomClient "go-common/app/service/live/room/api/liverpc"
"go-common/library/conf/paladin"
)
// Service service.
type Service struct {
ac *paladin.Map
dao *dao.Dao
daoAnchor daoAnchor.DaoAnchorClient
roomService *roomClient.Client
ruleConf atomic.Value
indexBlackList atomic.Value
}
// New new a service and return.
func New() (s *Service) {
var ac = new(paladin.TOML)
if err := paladin.Watch("application.toml", ac); err != nil {
panic(err)
}
s = &Service{
ac: ac,
dao: dao.New(),
}
wdConf := new(warden.ClientConfig)
wdConf.Timeout = xtime.Duration(time.Second * 10)
err := s.ac.Get("daoAnchorClient").UnmarshalTOML(wdConf)
if err != nil {
log.Error("[service]get daoAnchorClient warden error:%+v", err)
wdConf.Dial = xtime.Duration(time.Millisecond * 100)
wdConf.Timeout = xtime.Duration(time.Second * 10)
}
conn, err := daoAnchor.NewClient(wdConf) // 目前传空,如果需要配置
if err != nil {
panic(err)
}
s.daoAnchor = conn
roomClientConf := new(liverpc.ClientConfig)
rerr := s.ac.Get("roomClient").UnmarshalTOML(roomClientConf)
if rerr != nil {
log.Error("[service]get roomClient conf error:%+v", rerr)
roomClientConf.ConnTimeout = xtime.Duration(time.Millisecond * 50)
roomClientConf.AppID = "live.room"
}
s.roomService = roomClient.New(roomClientConf)
s.loadConfFromDb()
s.loadBlackList()
go s.reloadConfFromDb()
go s.reloadRecList()
go s.blackListProc()
return s
}
// Ping ping the resource.
func (s *Service) Ping(ctx context.Context) (err error) {
return s.dao.Ping(ctx)
}
// Close close the resource.
func (s *Service) Close() {
s.dao.Close()
}

View File

@@ -0,0 +1,190 @@
package service
import (
"go-common/library/log"
"sort"
)
type roomInfo struct {
roomId int64
value int64
}
type attrSortStruct struct {
roomIdInfos []roomInfo
value int64
}
type RoomInfoSlice []roomInfo
func (a RoomInfoSlice) Len() int {
return len(a)
}
func (a RoomInfoSlice) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a RoomInfoSlice) Less(i, j int) bool {
return a[j].value < a[i].value
}
func (s *Service) sort(attrsFilter map[int64]map[int64]*attrFilter, filterRoomData []*filterItem, Cond string, recId int) (roomIds []int64) {
roomIds = make([]int64, 0)
noSort := make([]int64, 0)
attrSort := make(map[int64]*attrSortStruct)
attrTag := make([]int64, 0)
if len(attrsFilter) <= 0 {
for _, attrResp := range filterRoomData {
if attrResp == nil {
continue
}
noSort = append(noSort, attrResp.item.RoomId)
}
} else {
//需要排序的
for _, attrResp := range filterRoomData {
if attrResp == nil {
continue
}
for _, attr := range attrResp.item.AttrList {
if _, ok := attrsFilter[attr.AttrId][attr.AttrSubId]; ok {
if _, ok := attrSort[attr.AttrId]; !ok {
attrSort[attr.AttrId] = &attrSortStruct{}
}
// 如果有top
// 小时榜特殊处理
if attr.AttrId == attrType[_hourRankType] {
attr.AttrValue = 10 - attr.AttrValue
}
attrSort[attr.AttrId].value = attrsFilter[attr.AttrId][attr.AttrSubId].top
attrSort[attr.AttrId].roomIdInfos = append(attrSort[attr.AttrId].roomIdInfos, roomInfo{
roomId: attrResp.item.RoomId,
value: attr.AttrValue,
})
}
}
//or 逻辑需要非attr类型合并
if attrResp.isTagHit {
attrTag = append(attrTag, attrResp.item.RoomId)
}
}
}
log.Info("[sort]recId:%d, attrSort:%+v, Cond:%s, attrsFilter:%+v, filterRoomData:%+v", recId, attrSort, Cond, attrsFilter, filterRoomData)
log.Info("[sort]recId:%d, attrTag:%+v, Cond:%s, attrsFilter:%+v, filterRoomData:%+v", recId, attrTag, Cond, attrsFilter, filterRoomData)
log.Info("[sort]recId:%d, noSort:%+v, Cond:%s, attrsFilter:%+v, filterRoomData:%+v", recId, noSort, Cond, attrsFilter, filterRoomData)
sortedList := make([][]int64, 0)
//有top 需要排序的
for _, obj := range attrSort {
sortedList = append(sortedList, s.sortRoomList(obj.roomIdInfos, obj.value))
}
if len(sortedList) <= 0 {
sortedList = append(sortedList, noSort)
}
// and 求交集
if Cond == _condAnd {
// 如果不填top
roomIds = s.sliceIntersect(sortedList)
}
if Cond == _condOr {
// or 条件时有些roomData可能没有attr需要合并没有attr的
sortedList = append(sortedList, attrTag)
roomIds = s.sliceMerge(sortedList)
}
log.Info("[sort]recId:%d, roomIds:%+v, Cond:%s, attrsFilter:%+v, filterRoomData:%+v", recId, roomIds, Cond, attrsFilter, filterRoomData)
return
}
func (s *Service) sortRoomList(roomIdInfos []roomInfo, value int64) (roomList []int64) {
//value <= 0 不排序,直接返回
if value <= 0 {
for _, info := range roomIdInfos {
roomList = append(roomList, info.roomId)
}
return
}
sort.Sort(RoomInfoSlice(roomIdInfos))
// top
if len(roomIdInfos) >= int(value) {
roomIdInfos = roomIdInfos[:value]
}
for _, info := range roomIdInfos {
roomList = append(roomList, info.roomId)
}
return
}
// 带去重 slice合并
func (s *Service) sliceMerge(roomList [][]int64) (mergedSlice []int64) {
duplicateMap := make(map[int64]bool)
mergedSlice = make([]int64, 0)
for _, list := range roomList {
for _, roomId := range list {
if _, ok := duplicateMap[roomId]; !ok {
mergedSlice = append(mergedSlice, roomId)
duplicateMap[roomId] = true
}
}
}
return
}
func (s *Service) sliceIntersect(roomList [][]int64) (mergedSlice []int64) {
mergedSlice = make([]int64, 0)
if len(roomList) == 0 {
return
}
if len(roomList) == 1 {
mergedSlice = roomList[0]
return
}
if len(roomList) >= 2 {
i := 3
mergedSlice = s.intersect(roomList[0], roomList[1])
for {
if len(roomList) < i {
break
}
mergedSlice = s.intersect(mergedSlice, roomList[int64(i-1)])
i++
}
}
return
}
// 带去重 slice交集 slice已排序
func (s *Service) intersect(nums1 []int64, nums2 []int64) (IntersectSlice []int64) {
IntersectSlice = make([]int64, 0)
x := 0
y := 0
for {
if x < len(nums1) && y < len(nums2) {
if nums1[x] == nums2[y] {
IntersectSlice = append(IntersectSlice, nums1[x])
x++
y++
} else if nums1[x] > nums2[y] {
y++
} else {
x++
}
} else {
break
}
}
return
}