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,89 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"backup-stream.go",
"cache.go",
"dao.cache.go",
"dao.go",
"http.go",
"localcache.go",
"main-stream.go",
"mysql.go",
"notify.go",
"official-stream.go",
"redis.go",
"stream-change-log.go",
"upstream-summary.go",
],
importpath = "go-common/app/service/video/stream-mng/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/video/stream-mng/common:go_default_library",
"//app/service/video/stream-mng/conf:go_default_library",
"//app/service/video/stream-mng/model:go_default_library",
"//library/cache:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/conf/env:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/metadata:go_default_library",
"//library/stat/prom:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/sync/pipeline/fanout:go_default_library",
"//vendor/github.com/bluele/gcache:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/golang.org/x/sync/singleflight: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 = [
"backup-stream_test.go",
"cache_test.go",
"dao.cache_test.go",
"dao_test.go",
"http_test.go",
"localcache_test.go",
"main-stream_test.go",
"mysql_test.go",
"notify_test.go",
"official-stream_test.go",
"redis_test.go",
"stream-change-log_test.go",
"upstream-summary_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/video/stream-mng/conf:go_default_library",
"//app/service/video/stream-mng/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,290 @@
package dao
import (
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"go-common/app/service/video/stream-mng/model"
"go-common/library/log"
"math/rand"
"strings"
"time"
"go-common/library/database/sql"
"github.com/pkg/errors"
"go-common/app/service/video/stream-mng/common"
)
const (
_maxRetryTimes = 5
_vendorBVC = 1
_vendorKS = 2
_vendorQN = 4
_vendorTC = 8
_vendorWS = 16
_getBackupStreamByRoomID = "SELECT `room_id`,`stream_name`, `key`,`default_vendor`, `origin_upstream`,`streaming`,`last_stream_time`, `expires_at`, `options` from `backup_stream` WHERE `room_id` = ? and status = 1"
_getBackupStreamByStreamName = "SELECT `room_id`,`stream_name`, `key`, `default_vendor`, `origin_upstream`, `streaming`, `last_stream_time`, `expires_at`, `options` from `backup_stream` WHERE `stream_name` = ? and status = 1;"
_getMultiBackupStreamByRID = "SELECT `room_id`,`stream_name`, `key`, `default_vendor`, `origin_upstream`, `streaming`, `last_stream_time`, `expires_at`, `options` from `backup_stream` WHERE `room_id` = %d and status = 1"
_getBackupRoom = "SELECT distinct room_id from `backup_stream`"
_insertBackupStream = "INSERT INTO `backup_stream` (room_id, stream_name, `key`, default_vendor, expires_at, options) VALUES (?, ?, ?, ?, ?, ?);"
)
// GetBackupStreamByRoomID 根据roomid获取备用流信息
func (d *Dao) GetBackupStreamByRoomID(ctx context.Context, rid int64) (infos []*model.BackupStream, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(ctx, _getBackupStreamByRoomID, rid); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
bs := new(model.BackupStream)
if err = rows.Scan(&bs.RoomID, &bs.StreamName, &bs.Key, &bs.DefaultVendor, &bs.OriginUpstream, &bs.Streaming, &bs.LastStreamTime, &bs.ExpiresAt, &bs.Options); err != nil {
err = errors.WithStack(err)
return
}
infos = append(infos, bs)
}
err = rows.Err()
return
}
// GetMultiBackupStreamByRID 批量查询备用流
func (d *Dao) GetMultiBackupStreamByRID(c context.Context, rids []int64) (infos []*model.BackupStream, err error) {
len := len(rids)
muSql := ""
for i := 0; i < len; i++ {
ss := fmt.Sprintf(_getMultiBackupStreamByRID, rids[i])
if i == 0 {
muSql = fmt.Sprintf("%s%s", muSql, ss)
} else {
muSql = fmt.Sprintf("%s UNION %s", muSql, ss)
}
}
var rows *sql.Rows
if rows, err = d.db.Query(c, muSql); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
bs := new(model.BackupStream)
if err = rows.Scan(&bs.RoomID, &bs.StreamName, &bs.Key, &bs.DefaultVendor, &bs.OriginUpstream, &bs.Streaming, &bs.LastStreamTime, &bs.ExpiresAt, &bs.Options); err != nil {
if err == sql.ErrNoRows {
continue
}
err = errors.WithStack(err)
return
}
infos = append(infos, bs)
}
err = rows.Err()
return
}
// GetBackupRoom 临时获取所有的房间号
func (d *Dao) GetBackupRoom(ctx context.Context) (res map[int64]int64, err error) {
res = map[int64]int64{}
var rows *sql.Rows
if rows, err = d.db.Query(ctx, _getBackupRoom); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
bs := new(model.BackupStream)
if err = rows.Scan(&bs.RoomID); err != nil {
err = errors.WithStack(err)
return
}
res[bs.RoomID] = bs.RoomID
}
err = rows.Err()
return
}
// CreateBackupStream 创建备用流
func (d *Dao) CreateBackupStream(ctx context.Context, bs *model.BackupStream) (*model.BackupStream, error) {
if bs.StreamName == "" {
bs.StreamName = fmt.Sprintf("live_%d_bs_%d", bs.RoomID, rand.Intn(9899999)+100000)
}
if bs.Key == "" {
h := md5.New()
h.Write([]byte(fmt.Sprintf("%s%d", bs.StreamName, time.Now().Nanosecond())))
bs.Key = hex.EncodeToString(h.Sum(nil))
}
if bs.DefaultVendor == 0 {
bs.DefaultVendor = 1
}
// 当传入的默认上行不是五家cdn
if _, ok := common.BitwiseMapSrc[bs.DefaultVendor]; !ok {
bs.DefaultVendor = 1
}
if bs.ExpiresAt.Before(time.Now()) {
bs.ExpiresAt = time.Now().Add(time.Hour * 336) // 14 * 24
}
res, err := d.stmtBackupStreamCreate.Exec(ctx, bs.RoomID, bs.StreamName, bs.Key, bs.DefaultVendor, bs.ExpiresAt.Format("2006-01-02 15:04:05"), bs.Options)
if err != nil {
return bs, err
}
bs.ID, err = res.LastInsertId()
return bs, err
}
// GetBackupStreamByStreamName 根据流名查询备用流
func (d *Dao) GetBackupStreamByStreamName(c context.Context, sn string) (*model.BackupStream, error) {
row := d.db.QueryRow(c, _getBackupStreamByStreamName, sn)
bs := &model.BackupStream{}
err := row.Scan(&bs.RoomID, &bs.StreamName, &bs.Key,
&bs.DefaultVendor, &bs.OriginUpstream, &bs.Streaming,
&bs.LastStreamTime, &bs.ExpiresAt, &bs.Options)
if err != nil {
return nil, err
}
return bs, nil
}
const (
_setOriginUpstream = "UPDATE `backup_stream` SET `origin_upstream` = ?, `streaming` = ? WHERE `stream_name` = ? and `origin_upstream` = 0;"
_setForwardUpstream = "UPDATE `backup_stream` SET `streaming` = ? WHERE `stream_name` = ? and `streaming` = ?;"
_setOriginUpstreamOnClose = "UPDATE `backup_stream` SET `origin_upstream` = 0, `streaming` = 0, `last_stream_time` = CURRENT_TIMESTAMP WHERE `stream_name` = ? and `origin_upstream` != 0;"
_setForwardUpstreamOnClose = "UPDATE `backup_stream` SET `streaming` = ? WHERE `stream_name` = ? and `streaming` = ?;"
)
var cdnBitwiseMap = map[string]int64{
"bvc": _vendorBVC,
"ks": _vendorKS,
"js": _vendorKS, // alias
"qn": _vendorQN,
"tc": _vendorTC,
"tx": _vendorTC, // alias
"txy": _vendorTC, // alias
"ws": _vendorWS,
}
// SetBackupStreamStreamingStatus
func (d *Dao) SetBackupStreamStreamingStatus(c context.Context, p *model.StreamingNotifyParam, bs *model.BackupStream, open bool) (*model.BackupStream, error) {
bitwise, ok := cdnBitwiseMap[strings.ToLower(p.SRC)]
if !ok {
return nil, errors.New("unknown src:" + p.SRC)
}
for i := 1; i <= _maxRetryTimes; i++ {
if open { // 开播
if bs.Streaming&bitwise == bitwise {
return bs, nil
}
if p.Type.String() == "0" { // 主推
if bs.OriginUpstream == 0 { // 只有当前没有原始上行时才去尝试更新主推记录
res, err := d.db.Exec(c, _setOriginUpstream, bitwise, bitwise, bs.StreamName)
if err != nil {
log.Errorw(c, "backup_stream_update_origin_record", err)
} else {
ra, err := res.RowsAffected()
if err != nil {
log.Errorw(c, "backup_stream_update_origin_record_rows_affected", err)
}
if ra == 1 { // 成功
bs.Streaming = bitwise
bs.OriginUpstream = bitwise
return bs, nil
}
// 影响行数为 0可能是发生了错误
// 也可能是因为原始数据已经发生变更等待后面重新读取DB中的数据。
}
} else { // 目前已经有上行了。在这里处理。
if bitwise != bs.OriginUpstream {
return bs, errors.New("origin upstream already exists")
}
}
} else { // 转推
if bs.OriginUpstream == 0 {
return bs, errors.New("origin upstream not exists")
}
res, err := d.db.Exec(c, _setForwardUpstream, bs.Streaming|bitwise, bs.StreamName, bs.Streaming)
if err != nil {
log.Errorw(c, "backup_stream_update_forward_record", err)
} else {
ra, err := res.RowsAffected()
if err != nil {
log.Errorw(c, "backup_stream_update_forward_record_rows_affected", err)
}
if ra == 1 { // 成功
bs.Streaming = bs.Streaming | bitwise
return bs, nil
}
}
}
} else { // 关播
if p.Type.String() == "0" { // 主推
if bs.OriginUpstream != bitwise { // 如果不是当前主推,直接拒绝
return bs, errors.New("permission denied")
}
res, err := d.db.Exec(c, _setOriginUpstreamOnClose, bs.StreamName)
if err != nil {
log.Errorw(c, "backup_stream_update_onclose_origin_record", err)
} else {
ra, err := res.RowsAffected()
if err != nil {
log.Errorw(c, "backup_stream_update_origin_onclose_record_rows_affected", err)
}
if ra == 1 { // 成功
bs.Streaming = 0
bs.OriginUpstream = 0
return bs, nil
}
}
// 影响行数为 0可能是发生了错误
// 也可能是因为原始数据已经发生变更等待后面重新读取DB中的数据。
} else { // 转推
if bs.OriginUpstream == bitwise {
return bs, errors.New("invalid params. you are origin upstream.")
}
res, err := d.db.Exec(c, _setForwardUpstreamOnClose, bs.Streaming&^bitwise, bs.StreamName, bs.Streaming)
if err != nil {
log.Errorw(c, "backup_stream_update_onclose_forward_record", err)
} else {
ra, err := res.RowsAffected()
if err != nil {
log.Errorw(c, "backup_stream_update_forward_onclose_record_rows_affected", err)
}
if ra == 1 { // 成功
bs.Streaming = bs.Streaming &^ bitwise
return bs, nil
}
}
}
}
time.Sleep(time.Millisecond * 100)
bs, err := d.GetBackupStreamByStreamName(c, bs.StreamName)
if err != nil {
log.Errorw(c, "backup_stream_refresh_record_row", err)
return bs, errors.New("system busy")
}
}
return bs, errors.New("update backup stream failed")
}

View File

@@ -0,0 +1,84 @@
package dao
import (
"context"
"go-common/app/service/video/stream-mng/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoGetBackupStreamByRoomID(t *testing.T) {
convey.Convey("GetBackupStreamByRoomID", t, func(ctx convey.C) {
var (
ctx2 = context.Background()
rid = int64(11891462)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
infos, err := d.GetBackupStreamByRoomID(ctx2, rid)
ctx.Convey("Then err should be nil.infos should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(infos, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoCreateBackupStream(t *testing.T) {
convey.Convey("CreateBackupStream", t, func(ctx convey.C) {
var (
ctx2 = context.Background()
bs = &model.BackupStream{
RoomID: 66666,
}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1, err := d.CreateBackupStream(ctx2, bs)
ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoGetBackupStreamByStreamName(t *testing.T) {
convey.Convey("GetBackupStreamByStreamName", t, func(ctx convey.C) {
var (
c = context.Background()
sn = "live_1511284_bs_7317941"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1, err := d.GetBackupStreamByStreamName(c, sn)
ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoSetBackupStreamStreamingStatus(t *testing.T) {
convey.Convey("SetBackupStreamStreamingStatus", t, func(ctx convey.C) {
var (
c = context.Background()
p = &model.StreamingNotifyParam{
SRC: "bvc",
Type: "0",
StreamName: "live_1511284_bs_7317941",
}
bs = &model.BackupStream{
OriginUpstream: 1,
StreamName: "live_1511284_bs_7317941",
}
open bool
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1, err := d.SetBackupStreamStreamingStatus(c, p, bs, open)
ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,143 @@
package dao
import (
"context"
"fmt"
"go-common/app/service/video/stream-mng/model"
)
//go:generate $GOPATH/src/go-common/app/tool/cache/gen
type _cache interface {
// 获取流完整信息
// cache: -singleflight=true -ignores=||id,sname -nullcache=&model.StreamFullInfo{RoomID:-1} -check_null_code=$!=nil&&$.RoomID<=0
streamFullInfo(c context.Context, id int64, sname string) (res *model.StreamFullInfo, err error)
// 获取rid
// cache: -singleflight=true -ignores=||sname -nullcache=&model.StreamFullInfo{RoomID:-1} -check_null_code=$!=nil&&$.RoomID<=0
streamRIDByName(c context.Context, sname string) (res *model.StreamFullInfo, err error)
// 批量获取接口
// cache: -nullcache=&model.StreamFullInfo{RoomID:-1} -check_null_code=$!=nil&&$.RoomID<=0
multiStreamInfo(c context.Context, rid []int64) (res map[int64]*model.StreamFullInfo, err error)
}
func (d *Dao) cacheSFstreamFullInfo(id int64, sname string) string {
if sname != "" {
return fmt.Sprintf("sf_sname_%s", sname)
}
return fmt.Sprintf("sf_rid_%d", id)
}
func (d *Dao) cacheSFstreamRIDByName(sname string) string {
return fmt.Sprintf("sf_rid_map_name_%s", sname)
}
// StreamFullInfo 传入rid或者sname 获取房间流信息
func (d *Dao) StreamFullInfo(c context.Context, rid int64, sname string) (res *model.StreamFullInfo, err error) {
info, err := d.streamFullInfo(c, rid, sname)
if err != nil {
return nil, err
}
if info == nil {
return nil, fmt.Errorf("can not find by room_id=%d", rid)
}
if len(info.List) == 1 && info.List[0].StreamName == "miss" {
return nil, fmt.Errorf("can not find any info by room_id=%d", rid)
}
return info, nil
}
// OriginUpStreamInfo 原始上行流名和src
func (d *Dao) OriginUpStreamInfo(c context.Context, rid int64) (sname string, origin int64, err error) {
info, err := d.streamFullInfo(c, rid, "")
if err != nil {
return "", 0, err
}
if info == nil {
return "", 0, fmt.Errorf("can not find by room_id=%d", rid)
}
for _, v := range info.List {
if v.Type == 1 {
// 优先级高
if v.Origin != 0 {
return v.StreamName, v.Origin, nil
}
return v.StreamName, v.DefaultUpStream, nil
}
}
return "", 0, fmt.Errorf("can not find by room_id=%d", rid)
}
func (d *Dao) DefaultUpStreamInfo(c context.Context, rid int64) (sname string, origin int64, err error) {
info, err := d.streamFullInfo(c, rid, "")
if err != nil {
return "", 0, err
}
if info == nil {
return "", 0, fmt.Errorf("can not find by room_id=%d", rid)
}
for _, v := range info.List {
if v.Type == 1 {
return v.StreamName, v.DefaultUpStream, nil
}
}
return "", 0, fmt.Errorf("can not find by room_id=%d", rid)
}
// OriginUpStreamInfoBySName 查询流的上行,正在推流上行和默认上行; 包含备用流
func (d *Dao) OriginUpStreamInfoBySName(c context.Context, sname string) (rid int64, origin int64, err error) {
info, err := d.streamFullInfo(c, 0, sname)
if err != nil {
return 0, 0, err
}
if info == nil {
return 0, 0, fmt.Errorf("can not find by sname=%s", sname)
}
for _, v := range info.List {
if v.StreamName == sname {
// 优先级高
if v.Origin != 0 {
return info.RoomID, v.Origin, nil
}
return info.RoomID, v.DefaultUpStream, nil
}
}
return 0, 0, fmt.Errorf("can not find by sname=%s", sname)
}
// StreamRIDByName 获取rid
func (d *Dao) StreamRIDByName(c context.Context, sname string) (int64, error) {
info, err := d.streamRIDByName(c, sname)
if err != nil {
return -1, err
}
if info != nil && info.RoomID > 0 {
return info.RoomID, nil
}
return -1, fmt.Errorf("can not find by sname=%s", sname)
}
// MultiStreamInfo 批量接口
func (d *Dao) MultiStreamInfo(c context.Context, rids []int64) (res map[int64]*model.StreamFullInfo, err error) {
infos, err := d.multiStreamInfo(c, rids)
if err != nil {
return res, err
}
resp := map[int64]*model.StreamFullInfo{}
for k, v := range infos {
if len(v.List) == 1 && v.List[0].StreamName == "miss" {
continue
}
resp[k] = v
}
return resp, nil
}

View File

@@ -0,0 +1,120 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaocacheSFstreamFullInfo(t *testing.T) {
convey.Convey("cacheSFstreamFullInfo", t, func(ctx convey.C) {
var (
id = int64(11891462)
sname = ""
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := d.cacheSFstreamFullInfo(id, sname)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaocacheSFstreamRIDByName(t *testing.T) {
convey.Convey("cacheSFstreamRIDByName", t, func(ctx convey.C) {
var (
sname = "live_19148701_6447624"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := d.cacheSFstreamRIDByName(sname)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoStreamFullInfo(t *testing.T) {
convey.Convey("StreamFullInfo", t, func(ctx convey.C) {
var (
c = context.Background()
rid = int64(11891462)
sname = ""
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.StreamFullInfo(c, rid, sname)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoOriginUpStreamInfo(t *testing.T) {
convey.Convey("OriginUpStreamInfo", t, func(ctx convey.C) {
var (
c = context.Background()
rid = int64(11891462)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
sname, origin, err := d.OriginUpStreamInfo(c, rid)
ctx.Convey("Then err should be nil.sname,origin should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(origin, convey.ShouldNotBeNil)
ctx.So(sname, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoOriginUpStreamInfoBySName(t *testing.T) {
convey.Convey("OriginUpStreamInfoBySName", t, func(ctx convey.C) {
var (
c = context.Background()
sname = "live_19148701_6447624"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
rid, origin, err := d.OriginUpStreamInfoBySName(c, sname)
ctx.Convey("Then err should be nil.rid,origin should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(origin, convey.ShouldNotBeNil)
ctx.So(rid, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoStreamRIDByName(t *testing.T) {
convey.Convey("StreamRIDByName", t, func(ctx convey.C) {
var (
c = context.Background()
sname = "live_19148701_6447624"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1, err := d.StreamRIDByName(c, sname)
ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoMultiStreamInfo(t *testing.T) {
convey.Convey("MultiStreamInfo", t, func(ctx convey.C) {
var (
c = context.Background()
rids = []int64{11891462}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.MultiStreamInfo(c, rids)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,177 @@
// Code generated by $GOPATH/src/go-common/app/tool/cache/gen. DO NOT EDIT.
/*
Package dao is a generated cache proxy package.
It is generated from:
type _cache interface {
// 获取流完整信息
// cache: -singleflight=true -ignores=||id,sname -nullcache=&model.StreamFullInfo{RoomID:-1} -check_null_code=$!=nil&&$.RoomID<=0
streamFullInfo(c context.Context, id int64, sname string) (res *model.StreamFullInfo, err error)
// 获取rid
// cache: -singleflight=true -ignores=||sname -nullcache=&model.StreamFullInfo{RoomID:-1} -check_null_code=$!=nil&&$.RoomID<=0
streamRIDByName(c context.Context, sname string) (res *model.StreamFullInfo, err error)
// 批量获取接口
// cache: -nullcache=&model.StreamFullInfo{RoomID:-1} -check_null_code=$!=nil&&$.RoomID<=0
multiStreamInfo(c context.Context, rid []int64) (res map[int64]*model.StreamFullInfo, err error)
}
*/
package dao
import (
"context"
"go-common/app/service/video/stream-mng/model"
"go-common/library/net/metadata"
"go-common/library/stat/prom"
"go-common/library/log"
"golang.org/x/sync/singleflight"
)
var _ _cache
var cacheSingleFlights = [2]*singleflight.Group{{}, {}}
// streamFullInfo 获取流完整信息
func (d *Dao) streamFullInfo(c context.Context, id int64, sname string) (res *model.StreamFullInfo, err error) {
addCache := true
res, err = d.CacheStreamFullInfo(c, id, sname)
if err != nil {
addCache = false
err = nil
}
defer func() {
if res != nil && res.RoomID <= 0 {
res = nil
}
}()
if res != nil {
log.Warn("get from redis")
prom.CacheHit.Incr("streamFullInfo")
return
}
var rr interface{}
sf := d.cacheSFstreamFullInfo(id, sname)
rr, err, _ = cacheSingleFlights[0].Do(sf, func() (r interface{}, e error) {
prom.CacheMiss.Incr("streamFullInfo")
r, e = d.RawStreamFullInfo(c, id, sname)
return
})
res = rr.(*model.StreamFullInfo)
if err != nil {
return
}
miss := res
if miss == nil {
miss = &model.StreamFullInfo{RoomID: -1}
}
if !addCache {
return
}
d.cache.Save(func() {
d.AddCacheStreamFullInfo(metadata.WithContext(c), id, miss)
})
return
}
// streamRIDByName 获取rid
func (d *Dao) streamRIDByName(c context.Context, id string) (res *model.StreamFullInfo, err error) {
addCache := true
res, err = d.CacheStreamRIDByName(c, id)
if err != nil {
addCache = false
err = nil
}
defer func() {
if res != nil && res.RoomID <= 0 {
res = nil
}
}()
if res != nil {
prom.CacheHit.Incr("streamRIDByName")
return
}
var rr interface{}
sf := d.cacheSFstreamRIDByName(id)
rr, err, _ = cacheSingleFlights[1].Do(sf, func() (r interface{}, e error) {
prom.CacheMiss.Incr("streamRIDByName")
r, e = d.RawStreamRIDByName(c, id)
return
})
res = rr.(*model.StreamFullInfo)
if err != nil {
return
}
miss := res
if miss == nil {
miss = &model.StreamFullInfo{RoomID: -1}
}
if !addCache {
return
}
d.cache.Save(func() {
d.AddCacheStreamRIDByName(metadata.WithContext(c), id, miss)
})
return
}
// multiStreamInfo 批量获取接口
func (d *Dao) multiStreamInfo(c context.Context, keys []int64) (res map[int64]*model.StreamFullInfo, err error) {
if len(keys) == 0 {
return
}
addCache := true
res, err = d.CacheMultiStreamInfo(c, keys)
if err != nil {
addCache = false
res = nil
err = nil
}
var miss []int64
for _, key := range keys {
if (res == nil) || (res[key] == nil) {
miss = append(miss, key)
}
}
prom.CacheHit.Add("multiStreamInfo", int64(len(keys)-len(miss)))
defer func() {
for k, v := range res {
if v != nil && v.RoomID <= 0 {
delete(res, k)
}
}
}()
if len(miss) == 0 {
log.Warn("get from redis")
return
}
var missData map[int64]*model.StreamFullInfo
prom.CacheMiss.Add("multiStreamInfo", int64(len(miss)))
missData, err = d.RawMultiStreamInfo(c, miss)
if res == nil {
res = make(map[int64]*model.StreamFullInfo)
}
for k, v := range missData {
res[k] = v
}
if err != nil {
return
}
for _, key := range keys {
if res[key] == nil {
if missData == nil {
missData = make(map[int64]*model.StreamFullInfo, len(keys))
}
missData[key] = &model.StreamFullInfo{RoomID: -1}
}
}
if !addCache {
return
}
d.cache.Save(func() {
d.AddCacheMultiStreamInfo(metadata.WithContext(c), missData)
})
return
}

View File

@@ -0,0 +1,57 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaostreamFullInfo(t *testing.T) {
convey.Convey("streamFullInfo", t, func(ctx convey.C) {
var (
c = context.Background()
id = int64(11891462)
sname = ""
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.streamFullInfo(c, id, sname)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaostreamRIDByName(t *testing.T) {
convey.Convey("streamRIDByName", t, func(ctx convey.C) {
var (
c = context.Background()
id = "live_19148701_6447624"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.streamRIDByName(c, id)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaomultiStreamInfo(t *testing.T) {
convey.Convey("multiStreamInfo", t, func(ctx convey.C) {
var (
c = context.Background()
keys = []int64{11891462}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.multiStreamInfo(c, keys)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,92 @@
package dao
import (
"context"
"go-common/app/service/video/stream-mng/conf"
"go-common/library/cache"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
xsql "go-common/library/database/sql"
bm "go-common/library/net/http/blademaster"
"go-common/library/sync/pipeline/fanout"
"github.com/bluele/gcache"
)
// Dao dao
type Dao struct {
c *conf.Config
mc *memcache.Pool
redis *redis.Pool
db *xsql.DB
tidb *xsql.DB
httpClient *bm.Client
cache *cache.Cache
liveAside *fanout.Fanout
localCache gcache.Cache
// DB
// 新版本主流
stmtMainStreamCreate *xsql.Stmt
stmtMainStreamChangeDefaultVendor *xsql.Stmt
stmtMainStreamChangeOptions *xsql.Stmt
stmtMainStreamClearAllStreaming *xsql.Stmt
// 备用流
stmtBackupStreamCreate *xsql.Stmt
// 旧版本流
stmtLegacyStreamCreate *xsql.Stmt
stmtLegacyStreamEnableNewUpRank *xsql.Stmt
stmtLegacyStreamDisableUpRank *xsql.Stmt
stmtLegacyStreamClearStreamFoward *xsql.Stmt
stmtLegacyStreamNotify *xsql.Stmt
// tidb
stmtUpStreamDispatch *xsql.Stmt
}
// New init mysql db
func New(c *conf.Config) (dao *Dao) {
dao = &Dao{
c: c,
mc: memcache.NewPool(c.Memcache),
redis: redis.NewPool(c.Redis),
db: xsql.NewMySQL(c.MySQL),
tidb: xsql.NewMySQL(c.TIDB),
httpClient: bm.NewClient(c.HTTPClient),
cache: cache.New(1, 10240),
liveAside: fanout.New("stream-mng"),
localCache: gcache.New(10240).Simple().Build(),
}
// 新版本主流
dao.stmtMainStreamCreate = dao.db.Prepared(_insertMainStream)
dao.stmtMainStreamChangeDefaultVendor = dao.db.Prepared(_changeDefaultVendor)
dao.stmtMainStreamChangeOptions = dao.db.Prepared(_changeOptions)
dao.stmtMainStreamClearAllStreaming = dao.db.Prepared(_clearAllStreaming)
// 备用流
dao.stmtBackupStreamCreate = dao.db.Prepared(_insertBackupStream)
// 旧版本流
dao.stmtLegacyStreamCreate = dao.db.Prepared(_insertOfficialStream)
dao.stmtLegacyStreamEnableNewUpRank = dao.db.Prepared(_updateUpOfficialStreamStatus)
dao.stmtLegacyStreamDisableUpRank = dao.db.Prepared(_updateForwardOfficialStreamStatus)
dao.stmtLegacyStreamClearStreamFoward = dao.db.Prepared(_updateOfficalStreamUpRankStatus)
dao.stmtLegacyStreamNotify = dao.db.Prepared(_setOriginStreamingStatus)
// tidb
dao.stmtUpStreamDispatch = dao.tidb.Prepared(_insertUpStreamInfo)
return
}
// Close close the resource.
func (d *Dao) Close() {
d.mc.Close()
d.redis.Close()
d.db.Close()
d.liveAside.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)
}

View File

@@ -0,0 +1,34 @@
package dao
import (
"flag"
"go-common/app/service/video/stream-mng/conf"
"os"
"testing"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "video.live.stream-mng")
flag.Set("conf_token", "a5640e59138e6b1845f802b6b7dcd348")
flag.Set("tree_id", "62771")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
} else {
flag.Set("conf", "../cmd/stream-mng.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}

View File

@@ -0,0 +1,49 @@
package dao
import (
"bytes"
"context"
"fmt"
"github.com/pkg/errors"
"go-common/library/conf/env"
"net/http"
"net/url"
)
// NewRequst http 请求
func (d *Dao) NewRequst(c context.Context, method string, url string, query url.Values, body []byte, headers map[string]string, resp interface{}) error {
var req *http.Request
if body != nil && len(body) > 0 {
req, _ = http.NewRequest(method, url, bytes.NewBuffer(body))
} else {
req, _ = http.NewRequest(method, url, nil)
}
if query != nil {
req.URL.RawQuery = query.Encode()
}
if headers != nil && len(headers) > 0 {
for k, v := range headers {
req.Header.Set(k, v)
}
}
if err := d.httpClient.Do(c, req, &resp); err != nil {
err = errors.WithStack(err)
return err
}
return nil
}
// getLiveStreamUrl 对接live-stream.bilibili.co的相关业务
func (d *Dao) getLiveStreamUrl(path string) string {
url := ""
if env.DeployEnv == env.DeployEnvProd {
url = fmt.Sprintf("%s%s", "http://prod-live-stream.bilibili.co", path)
} else {
url = fmt.Sprintf("%s%s", "http://live-stream.bilibili.co", path)
}
return url
}

View File

@@ -0,0 +1,24 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoNewRequst(t *testing.T) {
convey.Convey("NewRequst", t, func(ctx convey.C) {
var (
c = context.Background()
method = ""
url = "http://live-stream.bilibili.co/"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.NewRequst(c, method, url, nil, nil, nil, nil)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,162 @@
package dao
import (
"context"
"encoding/json"
"fmt"
"go-common/app/service/video/stream-mng/model"
"go-common/library/log"
"net/http"
"time"
)
const (
_localLiveStreamList = "live_stream"
_localStreamInfo = "rid:%d"
_liveExpiredTime = 600
_localStreamExpiredTime = 1
)
type OnAirStream struct {
StreamName string `json:"stream_name, omitempty"`
}
type OnAirStreamList struct {
List []*OnAirStream `json:"list,omitempty"`
}
type AllOnAirStream struct {
M map[string]*OnAirStreamList `json:"m,omitempty"`
}
func (d *Dao) getLocalLiveStreamListKey() string {
return _localLiveStreamList
}
func (d *Dao) getLocalStreamInfoKey(rid int64) string {
return fmt.Sprintf(_localStreamInfo, rid)
}
// loadLiveStreamList 判断流是否在播
func (d *Dao) LoadLiveStreamList(c context.Context, rids []int64) map[int64]bool {
list, _ := d.localCache.Get(d.getLocalLiveStreamListKey())
isLive := map[int64]bool{}
if res, ok := list.(map[string]int); ok {
for _, v := range rids {
// rid => stream
info, err := d.streamFullInfo(c, v, "")
if err != nil || info == nil {
continue
}
var sname string
var origin int64
for _, v := range info.List {
if v.Type == 1 {
sname = v.StreamName
origin = v.Origin
break
}
}
// 不在在播列表&orgin 为0===》不在播
if _, exe := res[sname]; !exe {
if origin == 0 {
isLive[v] = false
continue
}
}
isLive[v] = true
}
} else {
// 当从缓存中获取失败 or 项目刚刚启动
for _, v := range rids {
isLive[v] = true
}
}
return isLive
}
// StoreLiveStreamList 刷新在播列表缓存
func (d *Dao) StoreLiveStreamList() {
ctx := context.Background()
type liveStream struct {
Code int `json:"code,omitempty"`
Data *AllOnAirStream `json:"data,omitempty"`
}
resp := &liveStream{}
uri := d.getLiveStreamUrl("/api/live/vendor/onairstreamlist?cdn=bvc")
err := d.NewRequst(ctx, http.MethodGet, uri, nil, nil, nil, resp)
if err != nil {
log.Errorv(ctx, log.KV("log", fmt.Sprintf("http_live_err=%v", err)))
return
}
res := map[string]int{}
if resp.Code == 0 && resp.Data != nil && resp.Data.M != nil && len(resp.Data.M["BVC"].List) > 0 {
for _, v := range resp.Data.M["BVC"].List {
res[v.StreamName] = 1
}
} else {
res, _ := json.Marshal(resp)
log.Errorv(ctx, log.KV("log", fmt.Sprintf("http_live_err=%v", string(res))))
}
// 存10分钟
d.localCache.SetWithExpire(d.getLocalLiveStreamListKey(), res, _liveExpiredTime*time.Second)
}
// storeStreamInfo 存储单个流信息
func (d *Dao) storeStreamInfo(c context.Context, info *model.StreamFullInfo) {
if info == nil || info.RoomID < 0 {
return
}
key := d.getLocalStreamInfoKey(info.RoomID)
d.localCache.SetWithExpire(key, info, _localStreamExpiredTime*time.Second)
}
// loadStreamInfo 读取单个信息
func (d *Dao) loadStreamInfo(c context.Context, rid int64) *model.StreamFullInfo {
key := d.getLocalStreamInfoKey(rid)
info, _ := d.localCache.Get(key)
if res, ok := info.(*model.StreamFullInfo); ok {
if res != nil && res.RoomID > 0 {
return res
}
}
return nil
}
// storeMultiStreamInfo 存储多路流
func (d *Dao) storeMultiStreamInfo(c context.Context, infos map[int64]*model.StreamFullInfo) {
for _, v := range infos {
key := d.getLocalStreamInfoKey(v.RoomID)
//log.Warn("key=%v", key)
d.localCache.SetWithExpire(key, v, _localStreamExpiredTime*time.Second)
}
}
// loadMultiStreamInfo 读取批量信息
func (d *Dao) loadMultiStreamInfo(c context.Context, rids []int64) (map[int64]*model.StreamFullInfo, []int64) {
infos := map[int64]*model.StreamFullInfo{}
missRids := []int64{}
for _, v := range rids {
key := d.getLocalStreamInfoKey(v)
info, _ := d.localCache.Get(key)
if res, ok := info.(*model.StreamFullInfo); ok {
if res != nil && res.RoomID > 0 {
infos[v] = res
}
} else {
missRids = append(missRids, v)
}
}
return infos, missRids
}

View File

@@ -0,0 +1,44 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaogetLocalLiveStreamListKey(t *testing.T) {
convey.Convey("getLocalLiveStreamListKey", t, func(ctx convey.C) {
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := d.getLocalLiveStreamListKey()
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoLoadLiveStreamList(t *testing.T) {
convey.Convey("LoadLiveStreamList", t, func(ctx convey.C) {
var (
c = context.Background()
rids = []int64{11891462}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := d.LoadLiveStreamList(c, rids)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoStoreLiveStreamList(t *testing.T) {
convey.Convey("StoreLiveStreamList", t, func(ctx convey.C) {
ctx.Convey("When everything gose positive", func(ctx convey.C) {
d.StoreLiveStreamList()
ctx.Convey("No return values", func(ctx convey.C) {
})
})
})
}

View File

@@ -0,0 +1,207 @@
package dao
import (
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"go-common/app/service/video/stream-mng/common"
"go-common/app/service/video/stream-mng/model"
"go-common/library/database/sql"
"go-common/library/log"
"time"
"github.com/pkg/errors"
)
/*
全新推流结构
所需功能:
1. 创建流 (从老表搬运) done
2. 校验流/读取 done
3. 开关播回调
4. 切上行
5. 清理互推标记
*/
const (
// 创建流
_insertMainStream = "INSERT INTO `main_stream` (room_id, stream_name, `key`, default_vendor, options) VALUES (?, ?, ?, ?, ?);"
// 读取流
_getMainStreamWithoutConds = "SELECT `room_id`, `stream_name`, `key`, `default_vendor`, `origin_upstream`, `streaming`, `last_stream_time`, `options` from `main_stream` WHERE "
_getMultiMainStreamByRID = "SELECT `room_id`, `stream_name`, `key`, `default_vendor`, `origin_upstream`, `streaming`, `last_stream_time`, `options` from `main_stream` WHERE room_id = %d"
// 切上行
_changeDefaultVendor = "UPDATE `main_stream` SET `default_vendor` = ? WHERE `room_id` = ? AND status = 1"
// 切options
_changeOptions = "UPDATE `main_stream` SET `options` = ? WHERE `room_id` = ? AND status = 1 AND `options` = ?"
// 清理互推
_clearAllStreaming = "UPDATE `main_stream` SET `origin_upstream` = 0, `streaming` = 0, `options` = ? WHERE `room_id` = ? AND `options` = ? AND status = 1"
// 开关回调
_notifyMainStreamOrigin = "UPDATE `main_stream` SET `origin_upstream` = ?, `streaming` = ? WHERE `room_id` = ? and `streaming` = ? and status = 1 limit 1"
_notifyMainStreamOriginClose = "UPDATE `main_stream` SET `options` = ?,`origin_upstream` = 0, `streaming` = 0, `last_stream_time` = CURRENT_TIMESTAMP WHERE `room_id` = ? AND `options` = ? AND `status` = 1 limit 1"
_notifyMainStreamForward = "UPDATE `main_stream` SET `streaming` = ? WHERE `room_id` = ? and `streaming` = ? and status = 1 limit 1"
)
// CreateNewStream used to create new Stream record
func (d *Dao) CreateNewStream(c context.Context, stream *model.MainStream) (*model.MainStream, error) {
if stream.RoomID <= 0 {
return stream, fmt.Errorf("room id can not be empty")
}
if stream.StreamName == "" {
return stream, fmt.Errorf("stream name can not be empty")
}
if stream.Key == "" {
h := md5.New()
h.Write([]byte(fmt.Sprintf("%s%d", stream.StreamName, time.Now().Nanosecond())))
stream.Key = hex.EncodeToString(h.Sum(nil))
}
if stream.DefaultVendor == 0 {
stream.DefaultVendor = 1
}
res, err := d.stmtMainStreamCreate.Exec(c, stream.RoomID, stream.StreamName, stream.Key, stream.DefaultVendor, stream.Options)
if err != nil {
return stream, err
}
stream.ID, err = res.LastInsertId()
return stream, nil
}
// GetMainStreamFromDB 从DB中读取流信息
// roomID 和 streamName 可以只传一个,传哪个就用哪个查询,否则必须两者对应
func (d *Dao) GetMainStreamFromDB(c context.Context, roomID int64, streamName string) (*model.MainStream, error) {
if roomID <= 0 && streamName == "" {
return nil, errors.New("roomID and streamName cannot be empty at SAME time")
}
var row *sql.Row
if roomID > 0 && streamName != "" {
q := fmt.Sprintf("%s `room_id` = ? AND `stream_name` = ? AND status = 1", _getMainStreamWithoutConds)
row = d.db.QueryRow(c, q, roomID, streamName)
} else if roomID > 0 && streamName == "" {
q := fmt.Sprintf("%s `room_id` = ? AND status = 1", _getMainStreamWithoutConds)
row = d.db.QueryRow(c, q, roomID)
} else if roomID <= 0 && streamName != "" {
q := fmt.Sprintf("%s `stream_name` = ? AND status = 1", _getMainStreamWithoutConds)
row = d.db.QueryRow(c, q, streamName)
}
stream := new(model.MainStream)
err := row.Scan(&stream.RoomID, &stream.StreamName, &stream.Key,
&stream.DefaultVendor, &stream.OriginUpstream, &stream.Streaming,
&stream.LastStreamTime, &stream.Options)
if err != nil {
return nil, err
}
return stream, nil
}
// GetMultiMainStreamFromDB 批量从main-stream读取
func (d *Dao) GetMultiMainStreamFromDB(c context.Context, rids []int64) (mainStream []*model.MainStream, err error) {
len := len(rids)
muSql := ""
for i := 0; i < len; i++ {
ss := fmt.Sprintf(_getMultiMainStreamByRID, rids[i])
if i == 0 {
muSql = fmt.Sprintf("%s%s", muSql, ss)
} else {
muSql = fmt.Sprintf("%s UNION %s", muSql, ss)
}
}
var rows *sql.Rows
if rows, err = d.db.Query(c, muSql); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
stream := new(model.MainStream)
if err = rows.Scan(&stream.RoomID, &stream.StreamName, &stream.Key,
&stream.DefaultVendor, &stream.OriginUpstream, &stream.Streaming,
&stream.LastStreamTime, &stream.Options); err != nil {
if err == sql.ErrNoRows {
continue
}
err = errors.WithStack(err)
return
}
mainStream = append(mainStream, stream)
}
err = rows.Err()
return
}
// ChangeDefaultVendor 切换默认上行
func (d *Dao) ChangeDefaultVendor(c context.Context, roomID int64, newVendor int64) error {
if roomID <= 0 {
return errors.New("invalid roomID")
}
if _, ok := common.BitwiseMapName[newVendor]; !ok {
return errors.New("invalid vendor")
}
_, err := d.stmtMainStreamChangeDefaultVendor.Exec(c, newVendor, roomID)
return err
}
// ChangeMainStreamOptions 切换Options
func (d *Dao) ChangeMainStreamOptions(c context.Context, roomID int64, newOptions int64, options int64) error {
if roomID <= 0 {
return errors.New("invalid roomID")
}
_, err := d.stmtMainStreamChangeOptions.Exec(c, newOptions, roomID, options)
return err
}
// ClearMainStreaming 清理互推标记
func (d *Dao) ClearMainStreaming(c context.Context, roomID int64, newoptions int64, options int64) error {
if roomID <= 0 {
return errors.New("invalid roomID")
}
_, err := d.stmtMainStreamClearAllStreaming.Exec(c, newoptions, roomID, options)
return err
}
// MainStreamNotify 开关播回调
// @param roomID 房间号
// @param vendor 上行 CDN 位
// @param isOpen 是否是开播 true 开播 false 关播
// @param isOrigin 是否是原始上行 true 是 false 转推
func (d *Dao) MainStreamNotify(c context.Context, roomID, vendor int64, isOpen bool, isOrigin bool, options int64, newoptions int64) error {
if _, ok := common.BitwiseMapName[vendor]; !ok {
return fmt.Errorf("Unknow vendor %d", vendor)
}
log.Infov(c, log.KV("roomID", roomID), log.KV("vendor", vendor), log.KV("isOpen", isOpen), log.KV("isOrigin", isOrigin), log.KV("options", options), log.KV("newoptions", newoptions))
// "UPDATE `main_stream` SET `origin_upstream` = ?, `streaming` = ? WHERE `room_id` = ? AND `streaming` = ? AND `origin_upstream` = 0 and status = 1 limit 1"
// "UPDATE `main_stream` SET `streaming` = ? WHERE `room_id` = ? and `streaming` = ? and status = 1 limit 1"
ms, err := d.GetMainStreamFromDB(c, roomID, "")
if ms == nil || err != nil {
return fmt.Errorf("cannot found main stream by roomid (%d) with error%v", roomID, err)
}
// 开播
if isOpen {
if isOrigin { // 主推
_, err := d.db.Exec(c, _notifyMainStreamOrigin, vendor, ms.Streaming|vendor, roomID, ms.Streaming)
return err
}
// 转推
_, err := d.db.Exec(c, _notifyMainStreamForward, ms.Streaming|vendor, roomID, ms.Streaming)
return err
} else {
log.Infov(c, log.KV("----test----", fmt.Sprintf("---- %v ----- %v ---- %v ---- %v -", _notifyMainStreamOriginClose, newoptions, roomID, options)))
// 关播的时候, 必须是当前的origin=传递过来的cdn才可以关 修复开关播的时序性问题
if isOrigin && ms.OriginUpstream == vendor {
_, err := d.db.Exec(c, _notifyMainStreamOriginClose, newoptions, roomID, options)
return err
}
// 转推
_, err := d.db.Exec(c, _notifyMainStreamForward, ms.Streaming&^vendor, roomID, ms.Streaming)
return err
}
}

View File

@@ -0,0 +1,99 @@
package dao
import (
"context"
"go-common/app/service/video/stream-mng/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoCreateNewStream(t *testing.T) {
convey.Convey("CreateNewStream", t, func(ctx convey.C) {
var (
c = context.Background()
stream = &model.MainStream{
RoomID: 123456,
StreamName: "tetststest",
Key: "testtestetste",
}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1, err := d.CreateNewStream(c, stream)
ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoGetMainStreamFromDB(t *testing.T) {
convey.Convey("GetMainStreamFromDB", t, func(ctx convey.C) {
var (
c = context.Background()
roomID = int64(11891462)
streamName = ""
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1, err := d.GetMainStreamFromDB(c, roomID, streamName)
ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoChangeDefaultVendor(t *testing.T) {
convey.Convey("ChangeDefaultVendor", t, func(ctx convey.C) {
var (
c = context.Background()
roomID = int64(11891462)
newVendor = int64(2)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.ChangeDefaultVendor(c, roomID, newVendor)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoClearMainStreaming(t *testing.T) {
convey.Convey("ClearMainStreaming", t, func(ctx convey.C) {
var (
c = context.Background()
roomID = int64(11891462)
options = int64(0)
newoptions = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.ClearMainStreaming(c, roomID, newoptions, options)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoMainStreamNotify(t *testing.T) {
convey.Convey("MainStreamNotify", t, func(ctx convey.C) {
var (
c = context.Background()
roomID = int64(11891462)
vendor = int64(2)
isOpen bool
isOrigin bool
options = int64(0)
newoptions = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.MainStreamNotify(c, roomID, vendor, isOpen, isOrigin, options, newoptions)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,383 @@
package dao
import (
"context"
"errors"
"fmt"
"go-common/app/service/video/stream-mng/common"
"go-common/app/service/video/stream-mng/model"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/sync/errgroup"
)
// RawStreamFullInfo 直接从数据库中查询流信息,可传入流名, 也可传入rid
func (d *Dao) RawStreamFullInfo(c context.Context, id int64, sname string) (res *model.StreamFullInfo, err error) {
var (
official []*model.OfficialStream
backup []*model.StreamBase
mainStream *model.MainStream
)
if sname != "" {
official, err = d.GetOfficialStreamByName(c, sname)
// 可以从原表中查询到
if err == nil && official != nil && len(official) > 0 {
id = official[0].RoomID
goto END
}
var backUpInfo *model.BackupStream
// 原表中查询不到
backUpInfo, err = d.GetBackupStreamByStreamName(c, sname)
if err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("sql backup_stream err = %v", err)))
return
}
if backUpInfo == nil {
err = fmt.Errorf("can not find any info by %s", sname)
return
}
id = backUpInfo.RoomID
}
END:
// todo 这里用老的errgroup 新errgroup2 暂时未有人用,bug未知
group, errCtx := errgroup.WithContext(c)
// 如果还未查sv_ls_stream则需要查询
if id > 0 && len(official) == 0 {
group.Go(func() (err error) {
log.Warn("group offical")
if official, err = d.GetOfficialStreamByRoomID(errCtx, id); err != nil {
log.Errorv(errCtx, log.KV("log", fmt.Sprintf("group offical err=%v", err)))
}
return nil
})
}
if id > 0 {
group.Go(func() (err error) {
log.Warn("group main")
if mainStream, err = d.GetMainStreamFromDB(errCtx, id, ""); err != nil {
log.Errorv(errCtx, log.KV("log", fmt.Sprintf("group main err=%v", err)))
}
return nil
})
group.Go(func() (err error) {
log.Warn("group back")
back, err := d.GetBackupStreamByRoomID(errCtx, id)
if err != nil {
log.Errorv(errCtx, log.KV("log", fmt.Sprintf("group backup err=%v", err)))
} else {
backup = d.formatBackup2BaseInfo(c, back)
}
return nil
})
}
err = group.Wait()
if err != nil {
return
}
if len(official) == 0 {
err = fmt.Errorf("can not find any info by room_id=%d", id)
return
}
return d.formatStreamFullInfo(c, official, backup, mainStream)
}
// RawStreamRIDByName 查询rid
func (d *Dao) RawStreamRIDByName(c context.Context, sname string) (res *model.StreamFullInfo, err error) {
return d.RawStreamFullInfo(c, 0, sname)
}
// RawMultiStreamInfo 批量查询流信息
func (d *Dao) RawMultiStreamInfo(c context.Context, rids []int64) (res map[int64]*model.StreamFullInfo, err error) {
var (
official []*model.OfficialStream
backup []*model.BackupStream
mainStream []*model.MainStream
)
group, errCtx := errgroup.WithContext(c)
group.Go(func() (err error) {
if official, err = d.GetMultiOfficalStreamByRID(errCtx, rids); err != nil {
log.Errorv(errCtx, log.KV("log", fmt.Sprintf("group offical err=%v", err)))
}
return nil
})
group.Go(func() (err error) {
if backup, err = d.GetMultiBackupStreamByRID(errCtx, rids); err != nil {
log.Errorv(errCtx, log.KV("log", fmt.Sprintf("group back err=%v", err)))
}
return nil
})
group.Go(func() (err error) {
if mainStream, err = d.GetMultiMainStreamFromDB(errCtx, rids); err != nil {
log.Errorv(errCtx, log.KV("log", fmt.Sprintf("group back err=%v", err)))
}
return nil
})
err = group.Wait()
if err != nil {
return
}
// 把rid相同的放为一组
ridMapOfficial := map[int64][]*model.OfficialStream{}
for _, v := range official {
ridMapOfficial[v.RoomID] = append(ridMapOfficial[v.RoomID], v)
}
ridMapBackup := map[int64][]*model.BackupStream{}
for _, v := range backup {
ridMapBackup[v.RoomID] = append(ridMapBackup[v.RoomID], v)
}
ridMapBackupBase := map[int64][]*model.StreamBase{}
for id, v := range ridMapBackup {
ridMapBackupBase[id] = d.formatBackup2BaseInfo(c, v)
}
ridMapMain := map[int64]*model.MainStream{}
for _, v := range mainStream {
ridMapMain[v.RoomID] = v
}
infos := map[int64]*model.StreamFullInfo{}
flag := false
for id, v := range ridMapOfficial {
flag = true
infos[id], _ = d.formatStreamFullInfo(c, v, ridMapBackupBase[id], ridMapMain[id])
}
if flag {
return infos, nil
}
log.Errorv(c, log.KV("log", fmt.Errorf("can not find any info by room_ids=%d", rids)))
return nil, nil
}
// formatStreamFullInfo 格式化流信息
func (d *Dao) formatStreamFullInfo(c context.Context, official []*model.OfficialStream, backup []*model.StreamBase, main *model.MainStream) (*model.StreamFullInfo, error) {
resp := &model.StreamFullInfo{}
resp.List = []*model.StreamBase{}
var roomID int64
roomID = official[0].RoomID
resp.RoomID = official[0].RoomID
base := &model.StreamBase{}
base.StreamName = official[0].Name
base.Type = 1
base.Key = official[0].Key
if main != nil {
base.Options = main.Options
if 4&base.Options == 4 {
base.Wmask = true
}
if 8&base.Options == 8 {
base.Mmask = true
}
}
for _, item := range official {
if item.UpRank == 1 {
if val, ok := common.SrcMapBitwise[item.Src]; ok {
// todo origin为main-stream取
if main != nil {
base.Origin = main.OriginUpstream
} else {
// 做个兜底逻辑, main-stream中没有这个数据但是sv_ls_stream确实在播
base.Origin = val
}
base.DefaultUpStream = val
} else {
// 如果上行不在现在的任意一家, 则重新设置上行
if err := d.UpdateOfficialStreamStatus(c, roomID, common.BVCSrc); err == nil {
if main != nil {
base.Origin = main.OriginUpstream
} else {
base.Origin = common.BitWiseBVC
}
base.DefaultUpStream = common.BitWiseBVC
go func(c context.Context, rid int64, fromOrigin int8, toOrigin int64, sname string) {
d.UpdateStreamStatusCache(c, &model.StreamStatus{
RoomID: rid,
StreamName: sname,
DefaultChange: true,
DefaultUpStream: toOrigin,
})
// 插入日志
d.InsertChangeLog(c, &model.StreamChangeLog{
RoomID: rid,
FromOrigin: int64(fromOrigin),
ToOrigin: toOrigin,
Reason: fmt.Sprintf("上行不在五家CDN,old origin=%d", fromOrigin),
OperateName: "auto_change",
Source: "background",
})
}(metadata.WithContext(c), roomID, item.Src, common.BitWiseBVC, item.Name)
}
}
} else if item.UpRank == 2 {
if val, ok := common.SrcMapBitwise[item.Src]; ok {
base.Forward = append(base.Forward, val)
}
}
}
resp.List = append(resp.List, base)
if len(backup) > 0 {
for _, v := range backup {
resp.List = append(resp.List, v)
}
}
d.liveAside.Do(c, func(ctx context.Context) {
d.diffStreamInfo(ctx, resp, main)
})
return resp, nil
}
// formatBackup2Base backup 格式化为base
func (d *Dao) formatBackup2BaseInfo(c context.Context, back []*model.BackupStream) (resp []*model.StreamBase) {
if len(back) > 0 {
for _, b := range back {
bs := &model.StreamBase{}
bs.StreamName = b.StreamName
bs.Type = 2
bs.Key = b.Key
// 原始上行
bs.Origin = b.OriginUpstream
bs.DefaultUpStream = b.DefaultVendor
bs.Options = b.Options
// 位运算:可满足9家cdn
var n int64
for n = 256; n > 0; n /= 2 {
if (b.Streaming&n) == n && n != bs.Origin {
bs.Forward = append(bs.Forward, n)
}
}
resp = append(resp, bs)
}
}
return
}
// 比较新表和老表
func (d *Dao) diffStreamInfo(c context.Context, info *model.StreamFullInfo, mainStream *model.MainStream) {
if info != nil && info.RoomID != 0 && len(info.List) > 0 {
if mainStream == nil {
d.syncMainStream(c, info.RoomID, "")
log.Infov(c, log.KV("log", fmt.Sprintf("diff_err:can find any info, room_id=%d", info.RoomID)))
return
}
offical := info.List[0]
if mainStream.StreamName != offical.StreamName {
log.Infov(c, log.KV("log", fmt.Sprintf("diff_err:stream name is differentroom_id=%d", info.RoomID)))
return
}
if mainStream.Key != offical.Key {
log.Infov(c, log.KV("log", fmt.Sprintf("diff_err:key is differentroom_id=%d", info.RoomID)))
return
}
if mainStream.DefaultVendor != offical.DefaultUpStream {
log.Infov(c, log.KV("log", fmt.Sprintf("diff_err:DefaultVendor is differentroom_id=%d,main=%d,offical=%d", info.RoomID, mainStream.DefaultVendor, offical.DefaultUpStream)))
return
}
if mainStream.OriginUpstream != 0 && (mainStream.OriginUpstream != mainStream.DefaultVendor) {
log.Infov(c, log.KV("log", fmt.Sprintf("diff_err:OriginUpstream is differentroom_id=%d, main origin=%d, main default=%d", info.RoomID, mainStream.OriginUpstream, mainStream.DefaultVendor)))
return
}
streaming := offical.DefaultUpStream
for _, v := range offical.Forward {
streaming += v
}
if mainStream.Streaming != streaming {
log.Infov(c, log.KV("log", fmt.Sprintf("diff_err:Streaming is differentroom_id=%d, main=%d, offical=%d", info.RoomID, mainStream.Streaming, streaming)))
return
}
}
}
func (d *Dao) syncMainStream(c context.Context, roomID int64, streamName string) error {
if roomID <= 0 && streamName == "" {
return errors.New("invalid params")
}
var err error
exists, err := d.GetMainStreamFromDB(c, roomID, streamName)
if err != nil && err.Error() != "sql: no rows in result set" {
log.Errorv(c, log.KV("log", fmt.Sprintf("sync_stream_data_error = %v", err)))
return err
}
if exists != nil && (exists.RoomID == roomID || exists.StreamName == streamName) {
return nil
}
var full *model.StreamFullInfo
if roomID > 0 && streamName == "" {
full, err = d.StreamFullInfo(c, roomID, "")
} else if roomID <= 0 && streamName != "" {
full, err = d.StreamFullInfo(c, 0, streamName)
}
if err != nil {
return err
}
if full == nil {
return errors.New("unknow response")
}
for _, ss := range full.List {
if ss.Type == 1 {
ms := &model.MainStream{
RoomID: full.RoomID,
StreamName: ss.StreamName,
Key: ss.Key,
DefaultVendor: ss.DefaultUpStream,
Status: 1,
}
if ms.DefaultVendor == 0 {
ms.DefaultVendor = 1
}
_, err := d.CreateNewStream(c, ms)
if err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("sync_stream_data_error = %v", err)))
}
break
}
}
return nil
}

View File

@@ -0,0 +1,73 @@
package dao
import (
"context"
"go-common/app/service/video/stream-mng/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoRawStreamFullInfo(t *testing.T) {
convey.Convey("RawStreamFullInfo", t, func(ctx convey.C) {
var (
c = context.Background()
id = int64(11891462)
sname = ""
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.RawStreamFullInfo(c, id, sname)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoRawStreamRIDByName(t *testing.T) {
convey.Convey("RawStreamRIDByName", t, func(ctx convey.C) {
var (
c = context.Background()
sname = "live_19148701_6447624"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.RawStreamRIDByName(c, sname)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoRawMultiStreamInfo(t *testing.T) {
convey.Convey("RawMultiStreamInfo", t, func(ctx convey.C) {
var (
c = context.Background()
rids = []int64{11891462}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.RawMultiStreamInfo(c, rids)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaodiffStreamInfo(t *testing.T) {
convey.Convey("diffStreamInfo", t, func(ctx convey.C) {
var (
c = context.Background()
info = &model.StreamFullInfo{}
main = &model.MainStream{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
d.diffStreamInfo(c, info, main)
ctx.Convey("No return values", func(ctx convey.C) {
})
})
})
}

View File

@@ -0,0 +1,28 @@
package dao
import (
"context"
"fmt"
"go-common/library/log"
)
const (
_setOriginStreamingStatus = "UPDATE `sv_ls_stream` SET `up_rank` = ? where `room_id` = ? and `src` = ? and `up_rank` = ? LIMIT 1;"
)
// SetOriginStreamingStatus 用于设置 老版本数据结构的 推流状态
func (d *Dao) SetOriginStreamingStatus(c context.Context, rid int64, src, from, to int) error {
res, err := d.stmtLegacyStreamNotify.Exec(c, to, rid, src, from)
if err != nil {
return err
}
er, err := res.RowsAffected()
if err != nil {
return err
}
if er == 0 {
log.Infow(c, "no_record_updated", fmt.Sprintf("%d_%d_%d_%d", rid, src, from, to))
return nil
}
return err
}

View File

@@ -0,0 +1,26 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoSetOriginStreamingStatus(t *testing.T) {
convey.Convey("SetOriginStreamingStatus", t, func(ctx convey.C) {
var (
c = context.Background()
rid = int64(11891462)
src = int(1)
from = int(1)
to = int(16)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.SetOriginStreamingStatus(c, rid, src, from, to)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,189 @@
package dao
import (
"context"
"fmt"
"go-common/app/service/video/stream-mng/model"
"go-common/library/database/sql"
"go-common/library/log"
"time"
"github.com/pkg/errors"
)
const (
_getOfficialStreamByName = "SELECT id, room_id, src, `name`, `key`, up_rank, down_rank, `status` FROM `sv_ls_stream` WHERE name = ?"
_getOfficialStreamByRoomID = "SELECT id, room_id, src, `name`, `key`, up_rank, down_rank, `status` FROM `sv_ls_stream` WHERE room_id = ?"
_getMultiOfficalStreamByRID = "SELECT id, room_id, src, `name`, `key`, up_rank, down_rank, `status` FROM `sv_ls_stream` WHERE room_id = %d"
_insertOfficialStream = "INSERT INTO `sv_ls_stream` (room_id, `name`, `src`,`key`, `status`, up_rank, down_rank, last_status_updated_at, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?)"
_updateUpOfficialStreamStatus = "UPDATE `sv_ls_stream` SET `up_rank` = 1,`last_status_updated_at` = CURRENT_TIMESTAMP WHERE `room_id` = ? and `src` = ?"
_updateForwardOfficialStreamStatus = "UPDATE `sv_ls_stream` SET `up_rank` = 0,`last_status_updated_at` = CURRENT_TIMESTAMP WHERE `room_id` = ? and `src` != ?"
_updateOfficalStreamUpRankStatus = "UPDATE `sv_ls_stream` SET `up_rank` = ?,`last_status_updated_at` = CURRENT_TIMESTAMP WHERE `room_id` = ? AND `up_rank` = ?;"
)
// GetOfficialStreamByName 根据流名查流信息, 可以获取多条记录
func (d *Dao) GetOfficialStreamByName(c context.Context, name string) (infos []*model.OfficialStream, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, _getOfficialStreamByName, name); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
info := new(model.OfficialStream)
if err = rows.Scan(&info.ID, &info.RoomID,
&info.Src, &info.Name, &info.Key,
&info.UpRank, &info.DownRank, &info.Status); err != nil {
log.Warn("sv_ls_stream sql err = %v", err)
err = errors.WithStack(err)
infos = nil
return
}
infos = append(infos, info)
}
err = rows.Err()
return
}
// GetOfficialStreamByRoomID 根据roomid查询流信息, 可以获取多条记录
func (d *Dao) GetOfficialStreamByRoomID(c context.Context, rid int64) (infos []*model.OfficialStream, err error) {
var rows *sql.Rows
if rows, err = d.db.Query(c, _getOfficialStreamByRoomID, rid); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
info := new(model.OfficialStream)
if err = rows.Scan(&info.ID, &info.RoomID,
&info.Src, &info.Name, &info.Key,
&info.UpRank, &info.DownRank, &info.Status); err != nil {
log.Warn("sv_ls_stream sql err = %v", err)
err = errors.WithStack(err)
infos = nil
return
}
infos = append(infos, info)
}
err = rows.Err()
// 查询多个数据,不会报错, 只能判断为空
if err == nil && len(infos) == 0 && rid < 10000 {
infos = append(infos, &model.OfficialStream{
RoomID: rid,
Name: "miss",
Src: 32,
UpRank: 1,
Key: "miss",
})
}
return
}
// GetMultiOfficalStreamByRID 批量获取
func (d *Dao) GetMultiOfficalStreamByRID(c context.Context, rids []int64) (infos []*model.OfficialStream, err error) {
len := len(rids)
muSql := ""
for i := 0; i < len; i++ {
ss := fmt.Sprintf(_getMultiOfficalStreamByRID, rids[i])
if i == 0 {
muSql = fmt.Sprintf("%s%s", muSql, ss)
} else {
muSql = fmt.Sprintf("%s UNION %s", muSql, ss)
}
}
var rows *sql.Rows
if rows, err = d.db.Query(c, muSql); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
info := new(model.OfficialStream)
if err = rows.Scan(&info.ID, &info.RoomID,
&info.Src, &info.Name, &info.Key,
&info.UpRank, &info.DownRank, &info.Status); err != nil {
log.Warn("sv_ls_stream sql err = %v", err)
if err == sql.ErrNoRows {
continue
} else {
err = errors.WithStack(err)
infos = nil
return infos, err
}
}
infos = append(infos, info)
}
err = rows.Err()
return
}
// CreateOfficialStream 创建正式流
func (d *Dao) CreateOfficialStream(c context.Context, infos []*model.OfficialStream) (err error) {
tx, err := d.db.Begin(c)
if err != nil {
return err
}
defer func() {
if err != nil {
tx.Rollback()
}
}()
ts := time.Now().Format("2006-01-02 15:04:05")
for _, v := range infos {
if _, err = d.stmtLegacyStreamCreate.Exec(c, v.RoomID, v.Name, v.Src, v.Key, v.Status, v.UpRank, v.DownRank, ts, ts, ts); err != nil {
return err
}
}
err = tx.Commit()
return err
}
// UpdateOfficialStreamStatus 切换cdn时更新流状态
func (d *Dao) UpdateOfficialStreamStatus(c context.Context, rid int64, src int8) (err error) {
// 事务操作, 同时操作多条记录,任何一条失败均回滚
tx, err := d.db.Begin(c)
if err != nil {
return err
}
defer func() {
if err != nil {
tx.Rollback()
}
}()
if _, err = d.stmtLegacyStreamEnableNewUpRank.Exec(c, rid, src); err != nil {
return err
}
if _, err = d.stmtLegacyStreamDisableUpRank.Exec(c, rid, src); err != nil {
return err
}
err = tx.Commit()
return err
}
// UpdateOfficialUpRankStatus 清理互推标准更新up_rank
func (d *Dao) UpdateOfficialUpRankStatus(c context.Context, rid int64, whereSrc int8, toSrc int8) error {
res, err := d.stmtLegacyStreamClearStreamFoward.Exec(c, toSrc, rid, whereSrc)
if err != nil {
return err
}
_, err = res.RowsAffected()
return err
}

View File

@@ -0,0 +1,40 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoGetOfficialStreamByName(t *testing.T) {
convey.Convey("GetOfficialStreamByName", t, func(ctx convey.C) {
var (
c = context.Background()
name = "live_19148701_6447624"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
infos, err := d.GetOfficialStreamByName(c, name)
ctx.Convey("Then err should be nil.infos should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(infos, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoGetOfficialStreamByRoomID(t *testing.T) {
convey.Convey("GetOfficialStreamByRoomID", t, func(ctx convey.C) {
var (
c = context.Background()
rid = int64(11891462)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
infos, err := d.GetOfficialStreamByRoomID(c, rid)
ctx.Convey("Then err should be nil.infos should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(infos, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,813 @@
package dao
import (
"context"
"fmt"
"go-common/app/service/video/stream-mng/model"
"go-common/library/cache/redis"
"go-common/library/log"
"strconv"
"strings"
)
/*
kv结构
streamName: room_id
hash结构如下
// 存储所有的流名
room_id + name + name1,name2, name3
// 默认直推
room_id + name1:default + src
// 一个流名下的直推
room_id + name1:origin + src
// 一个流名下的转推
room_id + name1:streaming + src
// 一个流名下的key
room_id + name1:key + key
。。。
*/
const (
// kv => name: rid
_streamNameKey = "mng:name:%s"
// hash key => rid: field :value
_streamRooIDKey = "mng:rid:%d"
_streamRoomFieldAllName = "mng:name:all"
_streamRoomFieldDefault = "%s:default"
_streamRoomFieldOrigin = "%s:origin"
_streamRoomFieldForward = "%s:forward"
_streamRoomFieldSecret = "%s:key"
_streamRoomFieldOption = "%s:options"
_streamRoomFieldHot = "%d:hot"
_streamLastCDN = "last:cdn:%d"
// 切流记录
_streamChangeSrc = "change:src:%d"
//房间冷热流
_streamRoomHot = "room:hot:%d"
// 存12小时
_streamExpireTime = 4 * 3600
// stream_name map room_id 流名对应房间号 这个是不变的
_nameExpireTime = 365 * 86400
// 存一年
_lastCDNExpireTime = 365 * 86400
_changeSrcExpireTime = 365 * 86400
)
func (d *Dao) getStreamNamekey(streamName string) string {
return fmt.Sprintf(_streamNameKey, streamName)
}
func (d *Dao) getRoomIDKey(rid int64) string {
return fmt.Sprintf(_streamRooIDKey, rid)
}
func (d *Dao) getRoomFieldDefaultKey(streamName string) string {
return fmt.Sprintf(_streamRoomFieldDefault, streamName)
}
func (d *Dao) getRoomFieldOriginKey(streamName string) string {
return fmt.Sprintf(_streamRoomFieldOrigin, streamName)
}
func (d *Dao) getRoomFieldForwardKey(streamName string) string {
return fmt.Sprintf(_streamRoomFieldForward, streamName)
}
func (d *Dao) getRoomFieldSecretKey(streamName string) string {
return fmt.Sprintf(_streamRoomFieldSecret, streamName)
}
func (d *Dao) getRoomFieldOption(streamName string) string {
return fmt.Sprintf(_streamRoomFieldOption, streamName)
}
func (d *Dao) getRoomFieldHotKey(rid int64) string {
return fmt.Sprintf(_streamRoomFieldHot, rid)
}
func (d *Dao) getRoomHotKey(rid int64) string {
return fmt.Sprintf(_streamRoomHot, rid)
}
func (d *Dao) getLastCDNKey(rid int64) string {
return fmt.Sprintf(_streamLastCDN, rid)
}
func (d *Dao) getChangeSrcKey(rid int64) string {
return fmt.Sprintf(_streamChangeSrc, rid)
}
// CacheStreamFullInfo 从缓存取流信息, 可传入流名, 也可以传入rid
func (d *Dao) CacheStreamFullInfo(c context.Context, rid int64, sname string) (res *model.StreamFullInfo, err error) {
if sname != "" {
infos, err := d.CacheStreamRIDByName(c, sname)
if err != nil {
return nil, err
}
if infos == nil || infos.RoomID <= 0 {
return nil, fmt.Errorf("can not find any info by sname =%s", sname)
}
rid = infos.RoomID
}
// 先从本地缓存中取
res = d.loadStreamInfo(c, rid)
if res != nil {
log.Warn("get from local cache")
return res, nil
}
conn := d.redis.Get(c)
defer conn.Close()
roomKey := d.getRoomIDKey(rid)
hotKey := d.getRoomFieldHotKey(rid)
// 先判断过期时间是否为-1 为-1则删除
//ttl, _ := redis.Int(conn.Do("TTL", roomKey))
//if ttl == -1 {
// d.DeleteStreamByRIDFromCache(c, rid)
// return nil, nil
//}
values, err := redis.StringMap(conn.Do("HGETALL", roomKey))
log.Warn("%v", values)
if err != nil {
return nil, err
}
if len(values) == 0 {
return nil, nil
}
resp := model.StreamFullInfo{}
hot, _ := strconv.ParseInt(values[hotKey], 10, 64)
resp.Hot = hot
allNames := strings.Split(values[_streamRoomFieldAllName], "|")
for _, n := range allNames {
defaultUpStream := d.getRoomFieldDefaultKey(n)
originKey := d.getRoomFieldOriginKey(n)
forwardKey := d.getRoomFieldForwardKey(n)
key := d.getRoomFieldSecretKey(n)
options := d.getRoomFieldOption(n)
if values[originKey] != "" && values[forwardKey] != "" && values[key] != "" && values[defaultUpStream] != "" {
resp.RoomID = rid
base := model.StreamBase{}
base.StreamName = n
or, _ := strconv.ParseInt(values[originKey], 10, 64)
base.Origin = or
de, _ := strconv.ParseInt(values[defaultUpStream], 10, 64)
base.DefaultUpStream = de
if de == 0 {
return nil, fmt.Errorf("default is 0")
}
forward, _ := strconv.ParseInt(values[forwardKey], 10, 64)
var num int64
for num = 256; num > 0; num /= 2 {
if ((forward & num) == num) && (num != or) {
base.Forward = append(base.Forward, num)
}
}
op, _ := strconv.ParseInt(values[options], 10, 64)
base.Options = op
//这里判断是否有wmask mmask的流
if 4&op == 4 {
base.Wmask = true
}
if 8&op == 8 {
base.Mmask = true
}
base.Key = values[key]
if strings.Contains(n, "_bs_") {
base.Type = 2
} else {
base.Type = 1
}
resp.List = append(resp.List, &base)
}
}
if len(resp.List) > 0 {
// 存储到local cache
d.storeStreamInfo(c, &resp)
return &resp, nil
}
return nil, nil
}
// AddCacheStreamFullInfo 修改缓存数据
func (d *Dao) AddCacheStreamFullInfo(c context.Context, id int64, stream *model.StreamFullInfo) error {
if stream == nil || stream.RoomID <= 0 {
return nil
}
conn := d.redis.Get(c)
defer func() {
//conn.Do("EXPIRE", d.getRoomIDKey(id), _streamExpireTime)
conn.Close()
}()
streamExpireTime := _streamExpireTime
rid := stream.RoomID
roomKey := d.getRoomIDKey(rid)
allName := ""
len := 0
for _, v := range stream.List {
len++
allName = fmt.Sprintf("%s%s|", allName, v.StreamName)
// kv 设置流名和room_id映射关系
nameKey := d.getStreamNamekey(v.StreamName)
if err := conn.Send("SET", nameKey, rid); err != nil {
return fmt.Errorf("conn.Do(set, %s, %d) error(%v)", nameKey, rid, err)
}
if err := conn.Send("EXPIRE", nameKey, _nameExpireTime); err != nil {
return fmt.Errorf("conn.Do(EXPIRE, %s,%d) error(%v)", nameKey, _nameExpireTime, err)
}
// hash 设置room_id下的field 和key
field := d.getRoomFieldDefaultKey(v.StreamName)
if v.DefaultUpStream == 0 {
return fmt.Errorf("rid= %v, default is 0", roomKey)
}
if err := conn.Send("HSET", roomKey, field, v.DefaultUpStream); err != nil {
return fmt.Errorf("conn.Do(HSET, %s, %s, %v) error(%v)", roomKey, field, v.DefaultUpStream, err)
}
if len == 1 {
if err := conn.Send("EXPIRE", roomKey, streamExpireTime); err != nil {
log.Infov(c, log.KV("conn.EXPIRE error", err.Error()))
return fmt.Errorf("conn.Do(EXPIRE, %s, %d) error(%v)", roomKey, streamExpireTime, err)
}
}
field = d.getRoomFieldOriginKey(v.StreamName)
if err := conn.Send("HSET", roomKey, field, v.Origin); err != nil {
return fmt.Errorf("conn.Do(HSET, %s, %s, %v) error(%v)", roomKey, field, v.Origin, err)
}
field = d.getRoomFieldForwardKey(v.StreamName)
var num int64
for _, f := range v.Forward {
num += f
}
if err := conn.Send("HSET", roomKey, field, num); err != nil {
return fmt.Errorf("conn.Do(HSET, %s, %s, %v) error(%v)", roomKey, field, v.Forward, err)
}
field = d.getRoomFieldSecretKey(v.StreamName)
if err := conn.Send("HSET", roomKey, field, v.Key); err != nil {
return fmt.Errorf("conn.Do(HSET, %s, %s, %v) error(%v)", roomKey, field, v.Key, err)
}
field = d.getRoomFieldOption(v.StreamName)
if err := conn.Send("HSET", roomKey, field, v.Options); err != nil {
return fmt.Errorf("conn.Do(HSET, %s, %s, %v) error(%v)", roomKey, field, v.Options, err)
}
}
// 去除最后的|
allName = strings.Trim(allName, "|")
//log.Warn("%v", allName)
if err := conn.Send("HSET", roomKey, _streamRoomFieldAllName, allName); err != nil {
return fmt.Errorf("conn.Do(HSET, %s, %s, %v) error(%v)", roomKey, _streamRoomFieldAllName, allName, err)
}
if err := conn.Flush(); err != nil {
log.Infov(c, log.KV("conn.Flush error(%v)", err.Error()))
return fmt.Errorf("conn.Flush error(%v)", err)
}
for i := 0; i < 7*len+2; i++ {
if _, err := conn.Receive(); err != nil {
log.Infov(c, log.KV("conn.Receive error(%v)", err.Error()))
return fmt.Errorf("conn.Receive error(%v)", err)
}
}
return nil
}
// CacheStreamRIDByName 根据流名查房间号
func (d *Dao) CacheStreamRIDByName(c context.Context, sname string) (res *model.StreamFullInfo, err error) {
conn := d.redis.Get(c)
defer conn.Close()
nameKey := d.getStreamNamekey(sname)
rid, err := redis.Int64(conn.Do("GET", nameKey))
if err != nil {
return nil, err
}
if rid <= 0 {
return nil, nil
}
res = &model.StreamFullInfo{
RoomID: rid,
}
return res, nil
}
// AddCacheStreamRIDByName 增加缓存
func (d *Dao) AddCacheStreamRIDByName(c context.Context, sname string, stream *model.StreamFullInfo) error {
return d.AddCacheStreamFullInfo(c, 0, stream)
}
// AddCacheMultiStreamInfo 批量增加redis
func (d *Dao) AddCacheMultiStreamInfo(c context.Context, res map[int64]*model.StreamFullInfo) error {
conn := d.redis.Get(c)
defer func() {
//for _, stream := range res {
// conn.Do("EXPIRE", d.getRoomIDKey(stream.RoomID), _streamExpireTime)
//}
conn.Close()
}()
count := 0
for _, stream := range res {
streamExpireTime := _streamExpireTime
rid := stream.RoomID
roomKey := d.getRoomIDKey(rid)
allName := ""
len := 0
for _, v := range stream.List {
len++
allName = fmt.Sprintf("%s%s|", allName, v.StreamName)
// kv 设置流名和room_id映射关系
nameKey := d.getStreamNamekey(v.StreamName)
if err := conn.Send("SET", nameKey, rid); err != nil {
return fmt.Errorf("conn.Do(set, %s, %d) error(%v)", nameKey, rid, err)
}
if err := conn.Send("EXPIRE", nameKey, _nameExpireTime); err != nil {
return fmt.Errorf("conn.Do(EXPIRE, %s,%d) error(%v)", nameKey, _nameExpireTime, err)
}
// hash 设置room_id下的field 和key
field := d.getRoomFieldDefaultKey(v.StreamName)
if err := conn.Send("HSET", roomKey, field, v.DefaultUpStream); err != nil {
return fmt.Errorf("conn.Do(HSET, %s, %s, %v) error(%v)", roomKey, field, v.DefaultUpStream, err)
}
if len == 1 {
if err := conn.Send("EXPIRE", roomKey, streamExpireTime); err != nil {
return fmt.Errorf("conn.Do(EXPIRE, %s, %d) error(%v)", roomKey, streamExpireTime, err)
}
}
field = d.getRoomFieldOriginKey(v.StreamName)
if err := conn.Send("HSET", roomKey, field, v.Origin); err != nil {
return fmt.Errorf("conn.Do(HSET, %s, %s, %v) error(%v)", roomKey, field, v.Origin, err)
}
field = d.getRoomFieldForwardKey(v.StreamName)
var num int64
for _, f := range v.Forward {
num += f
}
if err := conn.Send("HSET", roomKey, field, num); err != nil {
return fmt.Errorf("conn.Do(HSET, %s, %s, %v) error(%v)", roomKey, field, v.Forward, err)
}
field = d.getRoomFieldSecretKey(v.StreamName)
if err := conn.Send("HSET", roomKey, field, v.Key); err != nil {
return fmt.Errorf("conn.Do(HSET, %s, %s, %v) error(%v)", roomKey, field, v.Key, err)
}
field = d.getRoomFieldOption(v.StreamName)
if err := conn.Send("HSET", roomKey, field, v.Options); err != nil {
return fmt.Errorf("conn.Do(HSET, %s, %s, %v) error(%v)", roomKey, field, v.Options, err)
}
}
// 去除最后的|
allName = strings.Trim(allName, "|")
if err := conn.Send("HSET", roomKey, _streamRoomFieldAllName, allName); err != nil {
return fmt.Errorf("conn.Do(HSET, %s, %s, %v) error(%v)", roomKey, _streamRoomFieldAllName, allName, err)
}
count += (7*len + 2)
}
if err := conn.Flush(); err != nil {
return fmt.Errorf("conn.Flush error(%v)", err)
}
// 这里要len
for i := 0; i < count; i++ {
if _, err := conn.Receive(); err != nil {
return fmt.Errorf("conn.Receive error(%v)", err)
}
}
return nil
}
// CacheMultiStreamInfo 批量从redis中获取数据
func (d *Dao) CacheMultiStreamInfo(c context.Context, rids []int64) (res map[int64]*model.StreamFullInfo, err error) {
if len(rids) == 0 {
return nil, nil
}
infos := map[int64]*model.StreamFullInfo{}
// 先从local cache读取
localInfos, missRids := d.loadMultiStreamInfo(c, rids)
//log.Warn("%v=%v", localInfos, missRids)
// 若全部命中local cache,直接返回
if len(missRids) == 0 {
log.Warn("all hit local cache")
return localInfos, nil
}
if len(localInfos) != 0 {
infos = localInfos
}
conn := d.redis.Get(c)
defer conn.Close()
for _, id := range missRids {
key := d.getRoomIDKey(id)
if err := conn.Send("HGETALL", key); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("redis: conn.Send(HGETALL, %s) error(%v)", key, err)))
return nil, fmt.Errorf("redis: conn.Send(HGETALL, %s) error(%v)", key, err)
}
}
if err := conn.Flush(); err != nil {
return nil, fmt.Errorf("redis: conn.Flush error(%v)", err)
}
for i := 0; i < len(missRids); i++ {
if values, err := redis.StringMap(conn.Receive()); err == nil {
if len(values) == 0 {
continue
}
item := model.StreamFullInfo{}
hotKey := d.getRoomFieldHotKey(missRids[i])
hot, _ := strconv.ParseInt(values[hotKey], 10, 64)
item.Hot = hot
allNames := strings.Split(values[_streamRoomFieldAllName], "|")
for _, n := range allNames {
defaultUpStream := d.getRoomFieldDefaultKey(n)
originKey := d.getRoomFieldOriginKey(n)
forwardKey := d.getRoomFieldForwardKey(n)
key := d.getRoomFieldSecretKey(n)
options := d.getRoomFieldOption(n)
if values[originKey] != "" && values[forwardKey] != "" && values[key] != "" {
item.RoomID = missRids[i]
base := model.StreamBase{}
base.StreamName = n
or, _ := strconv.ParseInt(values[originKey], 10, 64)
base.Origin = or
de, _ := strconv.ParseInt(values[defaultUpStream], 10, 64)
base.DefaultUpStream = de
forward, _ := strconv.ParseInt(values[forwardKey], 10, 64)
var num int64
for num = 256; num > 0; num /= 2 {
if ((forward & num) == num) && (num != or) {
base.Forward = append(base.Forward, num)
}
}
op, _ := strconv.ParseInt(values[options], 10, 64)
base.Options = op
//这里判断是否有wmask mmask的流
if 4&op == 4 {
base.Wmask = true
}
if 8&op == 8 {
base.Mmask = true
}
base.Key = values[key]
if strings.Contains(n, "_bs_") {
base.Type = 2
} else {
base.Type = 1
}
item.List = append(item.List, &base)
}
}
if len(item.List) > 0 {
//log.Warn("miss=%v", missRids[i])
infos[missRids[i]] = &item
}
}
}
// 更新local cache
d.storeMultiStreamInfo(c, infos)
return infos, nil
}
// UpdateLastCDNCache 设置last cdn
func (d *Dao) UpdateLastCDNCache(c context.Context, rid int64, origin int64) error {
conn := d.redis.Get(c)
defer conn.Close()
key := d.getLastCDNKey(rid)
if err := conn.Send("SET", key, origin); err != nil {
return fmt.Errorf("redis: conn.Send(SET, %s, %v) error(%v)", key, origin, err)
}
if err := conn.Send("EXPIRE", key, _lastCDNExpireTime); err != nil {
return fmt.Errorf("redis: conn.Send(EXPIRE key(%s) expire(%d)) error(%v)", key, _lastCDNExpireTime, err)
}
if err := conn.Flush(); err != nil {
return fmt.Errorf("redis: conn.Flush error(%v)", err)
}
for i := 0; i < 2; i++ {
if _, err := conn.Receive(); err != nil {
return fmt.Errorf("redis: conn.Receive error(%v)", err)
}
}
return nil
}
// UpdateChangeSrcCache 切流
func (d *Dao) UpdateChangeSrcCache(c context.Context, rid int64, origin int64) error {
conn := d.redis.Get(c)
defer conn.Close()
key := d.getChangeSrcKey(rid)
if err := conn.Send("SET", key, origin); err != nil {
return fmt.Errorf("redis: conn.Send(SET, %s, %v) error(%v)", key, origin, err)
}
if err := conn.Send("EXPIRE", key, _changeSrcExpireTime); err != nil {
return fmt.Errorf("redis: conn.Send(EXPIRE key(%s) expire(%d)) error(%v)", key, _changeSrcExpireTime, err)
}
if err := conn.Flush(); err != nil {
return fmt.Errorf("redis: conn.Flush error(%v)", err)
}
for i := 0; i < 2; i++ {
if _, err := conn.Receive(); err != nil {
return fmt.Errorf("redis: conn.Receive error(%v)", err)
}
}
return nil
}
// UpdateStreamForwardStatus 更新forward值
func (d *Dao) UpdateStreamStatusCache(c context.Context, stream *model.StreamStatus) {
var (
exist bool
err error
conn = d.redis.Get(c)
allName string
)
defer conn.Close()
// 首先判断是否存在
key := d.getRoomIDKey(stream.RoomID)
if exist, err = redis.Bool(conn.Do("EXISTS", key)); err != nil {
if err == redis.ErrNil {
return
}
log.Errorv(c, log.KV("log", fmt.Sprintf("update_status_err=%v", err)))
d.DeleteStreamByRIDFromCache(c, stream.RoomID)
return
}
if !exist {
return
}
// 不传sname 默认为主流
if stream.StreamName == "" {
if allName, err = redis.String(conn.Do("HGET", key, _streamRoomFieldAllName)); err != nil {
if err == redis.ErrNil {
return
}
log.Errorv(c, log.KV("log", fmt.Sprintf("update_status_err=%v", err)))
d.DeleteStreamByRIDFromCache(c, stream.RoomID)
return
}
names := strings.Split(allName, "|")
for _, v := range names {
if !strings.Contains(v, "_bs_") {
stream.StreamName = v
break
}
}
}
count := 0
// 是否是新增备用流
if stream.Add {
var names string
if names, err = redis.String(conn.Do("HGET", key, _streamRoomFieldAllName)); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("update_status_err=%v", err)))
d.DeleteStreamByRIDFromCache(c, stream.RoomID)
return
}
count++
if err := conn.Send("HSET", key, _streamRoomFieldAllName, fmt.Sprintf("%s|%s", names, stream.StreamName)); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("update_status_err=%v", err)))
d.DeleteStreamByRIDFromCache(c, stream.RoomID)
return
}
count++
secret := d.getRoomFieldSecretKey(stream.StreamName)
if err := conn.Send("HSET", key, secret, stream.Key); err != nil {
log.Errorv(c, log.KV("log", fmt.Sprintf("update_status_err=%v", err)))
d.DeleteStreamByRIDFromCache(c, stream.RoomID)
return
}
}
// 如果origin改变
if stream.OriginChange {
count++
originKey := d.getRoomFieldOriginKey(stream.StreamName)
if err := conn.Send("HSET", key, originKey, stream.Origin); err != nil {
// 如果设置失败则删除key
log.Errorv(c, log.KV("log", fmt.Sprintf("update_status_err=%v", err)))
d.DeleteStreamByRIDFromCache(c, stream.RoomID)
return
}
}
// forward
if stream.ForwardChange {
count++
forwardKey := d.getRoomFieldForwardKey(stream.StreamName)
if err := conn.Send("HSET", key, forwardKey, stream.Forward); err != nil {
// 如果设置失败则删除key
log.Errorv(c, log.KV("log", fmt.Sprintf("update_status_err=%v", err)))
d.DeleteStreamByRIDFromCache(c, stream.RoomID)
return
}
}
// 切上行
if stream.DefaultChange {
count++
defaultUpKey := d.getRoomFieldDefaultKey(stream.StreamName)
if err := conn.Send("HSET", key, defaultUpKey, stream.DefaultUpStream); err != nil {
// 如果设置失败则删除key
log.Errorv(c, log.KV("log", fmt.Sprintf("update_status_err=%v", err)))
d.DeleteStreamByRIDFromCache(c, stream.RoomID)
return
}
}
//切换options
if stream.OptionsChange {
if stream.Options >= 0 {
count++
optionsKey := d.getRoomFieldOption(stream.StreamName)
if err := conn.Send("HSET", key, optionsKey, stream.Options); err != nil {
// 如果设置失败则删除key
d.DeleteStreamByRIDFromCache(c, stream.RoomID)
return
}
}
}
if err := conn.Flush(); err != nil {
log.Infov(c, log.KV("conn.Flush error(%v)", err.Error()))
return
}
for i := 0; i < count; i++ {
if _, err := conn.Receive(); err != nil {
log.Infov(c, log.KV("conn.Receive error(%v)", err.Error()))
return
}
}
}
// GetLastCDNFromCache 查询上一次cdn
func (d *Dao) GetLastCDNFromCache(c context.Context, rid int64) (int64, error) {
conn := d.redis.Get(c)
defer conn.Close()
key := d.getLastCDNKey(rid)
origin, err := redis.Int64(conn.Do("GET", key))
if err != nil {
if err != redis.ErrNil {
return 0, fmt.Errorf("redis: conn.Do(GET, %s) error(%v)", key, err)
}
}
if origin <= 0 {
return 0, nil
}
return origin, nil
}
// GetChangeSrcFromCache 查询上一次cdn
func (d *Dao) GetChangeSrcFromCache(c context.Context, rid int64) (int64, error) {
conn := d.redis.Get(c)
defer conn.Close()
key := d.getChangeSrcKey(rid)
origin, err := redis.Int64(conn.Do("GET", key))
if err != nil {
if err != redis.ErrNil {
return 0, fmt.Errorf("redis: conn.Do(GET, %s) error(%v)", key, err)
}
}
if origin <= 0 {
return 0, nil
}
return origin, nil
}
// DeleteStreamByRIDFromCache 删除一个房间的的缓存信息
func (d *Dao) DeleteStreamByRIDFromCache(c context.Context, rid int64) (err error) {
// todo 删除内存
// 删除redis
// redis删除失败进行重试确保缓存的信息是最新的
conn := d.redis.Get(c)
defer conn.Close()
roomKey := d.getRoomIDKey(rid)
for i := 0; i < 3; i++ {
_, err = conn.Do("DEL", roomKey)
if err != nil {
log.Error("conn.Do(DEL, %s) error(%v)", roomKey, err)
continue
} else {
return nil
}
}
return err
}
// DeleteLastCDNFromCache 删除上一次到cdn
func (d *Dao) DeleteLastCDNFromCache(c context.Context, rid int64) error {
conn := d.redis.Get(c)
defer conn.Close()
key := d.getLastCDNKey(rid)
_, err := conn.Do("DEL", key)
return err
}
// UpdateRoomOptionsCache 更新Options状态
func (d *Dao) UpdateRoomOptionsCache(c context.Context, rid int64, streamname string, options int64) error {
conn := d.redis.Get(c)
defer conn.Close()
roomKey := d.getRoomIDKey(rid)
optionsKey := d.getRoomFieldOption(streamname)
_, err := conn.Do("HSET", roomKey, optionsKey, options)
return err
}
// UpdateRoomHotStatusCache 更新房间冷热流状态
func (d *Dao) UpdateRoomHotStatusCache(c context.Context, rid int64, hot int64) error {
conn := d.redis.Get(c)
defer conn.Close()
roomKey := d.getRoomIDKey(rid)
hotKey := d.getRoomFieldHotKey(rid)
_, err := conn.Do("HSET", roomKey, hotKey, hot)
return err
}

View File

@@ -0,0 +1,311 @@
package dao
import (
"context"
"go-common/app/service/video/stream-mng/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaogetStreamNamekey(t *testing.T) {
convey.Convey("getStreamNamekey", t, func(ctx convey.C) {
var (
streamName = "live_19148701_6447624"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := d.getStreamNamekey(streamName)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaogetRoomIDKey(t *testing.T) {
convey.Convey("getRoomIDKey", t, func(ctx convey.C) {
var (
rid = int64(11891462)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := d.getRoomIDKey(rid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaogetRoomFieldDefaultKey(t *testing.T) {
convey.Convey("getRoomFieldDefaultKey", t, func(ctx convey.C) {
var (
streamName = "live_19148701_6447624"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := d.getRoomFieldDefaultKey(streamName)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaogetRoomFieldOriginKey(t *testing.T) {
convey.Convey("getRoomFieldOriginKey", t, func(ctx convey.C) {
var (
streamName = "live_19148701_6447624"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := d.getRoomFieldOriginKey(streamName)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaogetRoomFieldForwardKey(t *testing.T) {
convey.Convey("getRoomFieldForwardKey", t, func(ctx convey.C) {
var (
streamName = "live_19148701_6447624"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := d.getRoomFieldForwardKey(streamName)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaogetRoomFieldSecretKey(t *testing.T) {
convey.Convey("getRoomFieldSecretKey", t, func(ctx convey.C) {
var (
streamName = "live_19148701_6447624"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := d.getRoomFieldSecretKey(streamName)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaogetLastCDNKey(t *testing.T) {
convey.Convey("getLastCDNKey", t, func(ctx convey.C) {
var (
rid = int64(11891462)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := d.getLastCDNKey(rid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaogetChangeSrcKey(t *testing.T) {
convey.Convey("getChangeSrcKey", t, func(ctx convey.C) {
var (
rid = int64(11891462)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := d.getChangeSrcKey(rid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoCacheStreamFullInfo(t *testing.T) {
convey.Convey("CacheStreamFullInfo", t, func(ctx convey.C) {
var (
c = context.Background()
rid = int64(11891462)
sname = ""
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.CacheStreamFullInfo(c, rid, sname)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoAddCacheStreamFullInfo(t *testing.T) {
convey.Convey("AddCacheStreamFullInfo", t, func(ctx convey.C) {
var (
c = context.Background()
id = int64(11891462)
stream = &model.StreamFullInfo{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddCacheStreamFullInfo(c, id, stream)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoCacheStreamRIDByName(t *testing.T) {
convey.Convey("CacheStreamRIDByName", t, func(ctx convey.C) {
var (
c = context.Background()
sname = "live_19148701_6447624"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.CacheStreamRIDByName(c, sname)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoAddCacheStreamRIDByName(t *testing.T) {
convey.Convey("AddCacheStreamRIDByName", t, func(ctx convey.C) {
var (
c = context.Background()
sname = "live_19148701_6447624"
stream = &model.StreamFullInfo{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddCacheStreamRIDByName(c, sname, stream)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoAddCacheMultiStreamInfo(t *testing.T) {
convey.Convey("AddCacheMultiStreamInfo", t, func(ctx convey.C) {
var (
c = context.Background()
res map[int64]*model.StreamFullInfo
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddCacheMultiStreamInfo(c, res)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoCacheMultiStreamInfo(t *testing.T) {
convey.Convey("CacheMultiStreamInfo", t, func(ctx convey.C) {
var (
c = context.Background()
rids = []int64{11891462}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.CacheMultiStreamInfo(c, rids)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoUpdateLastCDNCache(t *testing.T) {
convey.Convey("UpdateLastCDNCache", t, func(ctx convey.C) {
var (
c = context.Background()
rid = int64(11891462)
origin = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.UpdateLastCDNCache(c, rid, origin)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoUpdateChangeSrcCache(t *testing.T) {
convey.Convey("UpdateChangeSrcCache", t, func(ctx convey.C) {
var (
c = context.Background()
rid = int64(11891462)
origin = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.UpdateChangeSrcCache(c, rid, origin)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoGetLastCDNFromCache(t *testing.T) {
convey.Convey("GetLastCDNFromCache", t, func(ctx convey.C) {
var (
c = context.Background()
rid = int64(11891462)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1, err := d.GetLastCDNFromCache(c, rid)
ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoGetChangeSrcFromCache(t *testing.T) {
convey.Convey("GetChangeSrcFromCache", t, func(ctx convey.C) {
var (
c = context.Background()
rid = int64(11891462)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1, err := d.GetChangeSrcFromCache(c, rid)
ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoDeleteStreamByRIDFromCache(t *testing.T) {
convey.Convey("DeleteStreamByRIDFromCache", t, func(ctx convey.C) {
var (
c = context.Background()
rid = int64(11891462)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DeleteStreamByRIDFromCache(c, rid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoDeleteLastCDNFromCache(t *testing.T) {
convey.Convey("DeleteLastCDNFromCache", t, func(ctx convey.C) {
var (
c = context.Background()
rid = int64(11891462)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DeleteLastCDNFromCache(c, rid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,51 @@
package dao
import (
"context"
"fmt"
"go-common/app/service/video/stream-mng/model"
"go-common/library/database/sql"
"time"
"github.com/pkg/errors"
)
const (
_insertStreamChangeLog = "INSERT INTO %s (room_id, from_origin,to_origin, source,operate_name,reason) VALUES (?,?,?,?,?,?)"
_selectStreamChangelog = "SELECT room_id, from_origin, to_origin, source, operate_name,reason, ctime FROM %s where room_id = ? ORDER BY mtime DESC LIMIT ?"
)
// InsertChangeLog 插入日志
func (d *Dao) InsertChangeLog(c context.Context, change *model.StreamChangeLog) error {
// 判断当前年和月
now := time.Now().Format("200601")
tableName := fmt.Sprintf("stream_change_log_%s", now)
_, err := d.db.Exec(c, fmt.Sprintf(_insertStreamChangeLog, tableName), change.RoomID, change.FromOrigin, change.ToOrigin, change.Source, change.OperateName, change.Reason)
return err
}
// GetChangeLogByRoomID 查询日志
func (d *Dao) GetChangeLogByRoomID(c context.Context, rid int64, limit int64) (infos []*model.StreamChangeLog, err error) {
now := time.Now().Format("200601")
tableName := fmt.Sprintf("stream_change_log_%s", now)
var rows *sql.Rows
if rows, err = d.db.Query(c, fmt.Sprintf(_selectStreamChangelog, tableName), rid, limit); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
info := new(model.StreamChangeLog)
if err = rows.Scan(&info.RoomID, &info.FromOrigin, &info.ToOrigin, &info.Source, &info.OperateName, &info.Reason, &info.CTime); err != nil {
err = errors.WithStack(err)
infos = nil
return
}
infos = append(infos, info)
}
err = rows.Err()
return
}

View File

@@ -0,0 +1,48 @@
package dao
import (
"context"
"go-common/app/service/video/stream-mng/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoInsertChangeLog(t *testing.T) {
convey.Convey("InsertChangeLog", t, func(ctx convey.C) {
var (
c = context.Background()
change = &model.StreamChangeLog{
RoomID: 11891462,
FromOrigin: 1,
ToOrigin: 2,
Source: "app",
OperateName: "yy",
Reason: "auto",
}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.InsertChangeLog(c, change)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoGetChangeLogByRoomID(t *testing.T) {
convey.Convey("GetChangeLogByRoomID", t, func(ctx convey.C) {
var (
c = context.Background()
rid = int64(11891462)
limit = int64(1)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
infos, err := d.GetChangeLogByRoomID(c, rid, limit)
ctx.Convey("Then err should be nil.infos should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(infos, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,146 @@
package dao
import (
"context"
"github.com/pkg/errors"
"go-common/app/service/video/stream-mng/model"
"go-common/library/database/sql"
)
// 存储上行调度信息
const (
_insertUpStreamInfo = "INSERT INTO `upstream_info` (room_id,cdn,platform,ip,country,city,isp) values(?,?,?,?,?,?,?)"
_getSummaryUpStreamRtmp = "SELECT cdn, count(id) as value FROM upstream_info where mtime >= FROM_UNIXTIME(?) AND mtime <= FROM_UNIXTIME(?) group by cdn"
_getSummaryUpStreamISP = "SELECT isp, count(id) as value FROM upstream_info where mtime >= FROM_UNIXTIME(?) AND mtime <= FROM_UNIXTIME(?) group by isp"
_getSummaryUpStreamCountry = "SELECT country, count(id) as value FROM upstream_info where mtime >= FROM_UNIXTIME(?) AND mtime <= FROM_UNIXTIME(?) group by country"
_getSummaryUpStreamPlatform = "SELECT platform, count(id) as value FROM upstream_info where mtime >= FROM_UNIXTIME(?) AND mtime <= FROM_UNIXTIME(?) group by platform"
_getSummaryUpStreamCity = "SELECT city, count(id) as value FROM upstream_info where mtime >= FROM_UNIXTIME(?) AND mtime <= FROM_UNIXTIME(?) group by city"
)
// CreateUpStreamDispatch 创建一条上行调度信息
func (d *Dao) CreateUpStreamDispatch(c context.Context, info *model.UpStreamInfo) error {
_, err := d.stmtUpStreamDispatch.Exec(c, info.RoomID, info.CDN, info.PlatForm, info.IP, info.Country, info.City, info.ISP)
return err
}
// GetSummaryUpStreamRtmp 得到统计信息
func (d *Dao) GetSummaryUpStreamRtmp(c context.Context, start int64, end int64) (infos []*model.SummaryUpStreamRtmp, err error) {
res := []*model.SummaryUpStreamRtmp{}
var rows *sql.Rows
if rows, err = d.tidb.Query(c, _getSummaryUpStreamRtmp, start, end); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
info := new(model.SummaryUpStreamRtmp)
if err = rows.Scan(&info.CDN, &info.Count); err != nil {
err = errors.WithStack(err)
infos = nil
return
}
res = append(res, info)
}
err = rows.Err()
return res, err
}
// GetSummaryUpStreamISP 得到ISP统计信息
func (d *Dao) GetSummaryUpStreamISP(c context.Context, start int64, end int64) (infos []*model.SummaryUpStreamRtmp, err error) {
res := []*model.SummaryUpStreamRtmp{}
var rows *sql.Rows
if rows, err = d.tidb.Query(c, _getSummaryUpStreamISP, start, end); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
info := new(model.SummaryUpStreamRtmp)
if err = rows.Scan(&info.ISP, &info.Count); err != nil {
err = errors.WithStack(err)
infos = nil
return
}
res = append(res, info)
}
err = rows.Err()
return res, err
}
// GetSummaryUpStreamCountry 得到Country统计信息
func (d *Dao) GetSummaryUpStreamCountry(c context.Context, start int64, end int64) (infos []*model.SummaryUpStreamRtmp, err error) {
res := []*model.SummaryUpStreamRtmp{}
var rows *sql.Rows
if rows, err = d.tidb.Query(c, _getSummaryUpStreamCountry, start, end); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
info := new(model.SummaryUpStreamRtmp)
if err = rows.Scan(&info.Country, &info.Count); err != nil {
err = errors.WithStack(err)
infos = nil
return
}
res = append(res, info)
}
err = rows.Err()
return res, err
}
// GetSummaryUpStreamPlatform 得到Platform统计信息
func (d *Dao) GetSummaryUpStreamPlatform(c context.Context, start int64, end int64) (infos []*model.SummaryUpStreamRtmp, err error) {
res := []*model.SummaryUpStreamRtmp{}
var rows *sql.Rows
if rows, err = d.tidb.Query(c, _getSummaryUpStreamPlatform, start, end); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
info := new(model.SummaryUpStreamRtmp)
if err = rows.Scan(&info.PlatForm, &info.Count); err != nil {
err = errors.WithStack(err)
infos = nil
return
}
res = append(res, info)
}
err = rows.Err()
return res, err
}
// GetSummaryUpStreamCity 得到City统计信息
func (d *Dao) GetSummaryUpStreamCity(c context.Context, start int64, end int64) (infos []*model.SummaryUpStreamRtmp, err error) {
res := []*model.SummaryUpStreamRtmp{}
var rows *sql.Rows
if rows, err = d.tidb.Query(c, _getSummaryUpStreamCity, start, end); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
info := new(model.SummaryUpStreamRtmp)
if err = rows.Scan(&info.City, &info.Count); err != nil {
err = errors.WithStack(err)
infos = nil
return
}
res = append(res, info)
}
err = rows.Err()
return res, err
}

View File

@@ -0,0 +1,117 @@
package dao
import (
"context"
"go-common/app/service/video/stream-mng/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoCreateUpStreamDispatch(t *testing.T) {
convey.Convey("CreateUpStreamDispatch", t, func(ctx convey.C) {
var (
c = context.Background()
info = &model.UpStreamInfo{
RoomID: 11891462,
CDN: 1,
PlatForm: "ios",
Country: "中国",
City: "上海",
ISP: "电信",
IP: "12.12.12.12",
}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.CreateUpStreamDispatch(c, info)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoGetSummaryUpStreamRtmp(t *testing.T) {
convey.Convey("GetSummaryUpStreamRtmp", t, func(ctx convey.C) {
var (
c = context.Background()
start = int64(1546593007)
end = int64(1546830611)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
infos, err := d.GetSummaryUpStreamRtmp(c, start, end)
ctx.Convey("Then err should be nil.infos should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(infos, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoGetSummaryUpStreamISP(t *testing.T) {
convey.Convey("GetSummaryUpStreamISP", t, func(ctx convey.C) {
var (
c = context.Background()
start = int64(1546593007)
end = int64(1546830611)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
infos, err := d.GetSummaryUpStreamISP(c, start, end)
ctx.Convey("Then err should be nil.infos should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(infos, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoGetSummaryUpStreamCountry(t *testing.T) {
convey.Convey("GetSummaryUpStreamCountry", t, func(ctx convey.C) {
var (
c = context.Background()
start = int64(1546593007)
end = int64(1546830611)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
infos, err := d.GetSummaryUpStreamCountry(c, start, end)
ctx.Convey("Then err should be nil.infos should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(infos, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoGetSummaryUpStreamPlatform(t *testing.T) {
convey.Convey("GetSummaryUpStreamPlatform", t, func(ctx convey.C) {
var (
c = context.Background()
start = int64(1546593007)
end = int64(1546830611)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
infos, err := d.GetSummaryUpStreamPlatform(c, start, end)
ctx.Convey("Then err should be nil.infos should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(infos, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoGetSummaryUpStreamCity(t *testing.T) {
convey.Convey("GetSummaryUpStreamCity", t, func(ctx convey.C) {
var (
c = context.Background()
start = int64(1546593007)
end = int64(1546830611)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
infos, err := d.GetSummaryUpStreamCity(c, start, end)
ctx.Convey("Then err should be nil.infos should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(infos, convey.ShouldNotBeNil)
})
})
})
}