387 lines
11 KiB
Go
387 lines
11 KiB
Go
|
package dao
|
|||
|
|
|||
|
import (
|
|||
|
"context"
|
|||
|
"encoding/json"
|
|||
|
"fmt"
|
|||
|
"math/rand"
|
|||
|
"net/url"
|
|||
|
"sort"
|
|||
|
"strconv"
|
|||
|
"strings"
|
|||
|
"time"
|
|||
|
|
|||
|
"go-common/app/interface/bbq/app-bbq/api/http/v1"
|
|||
|
"go-common/app/interface/bbq/app-bbq/model"
|
|||
|
"go-common/app/interface/bbq/app-bbq/model/grpc"
|
|||
|
rec "go-common/app/service/bbq/recsys/api/grpc/v1"
|
|||
|
video "go-common/app/service/bbq/video/api/grpc/v1"
|
|||
|
"go-common/library/database/sql"
|
|||
|
"go-common/library/log"
|
|||
|
bm "go-common/library/net/http/blademaster"
|
|||
|
"go-common/library/net/metadata"
|
|||
|
|
|||
|
xgrpc "google.golang.org/grpc"
|
|||
|
)
|
|||
|
|
|||
|
const (
|
|||
|
_defaultPlatform = "html5"
|
|||
|
_playBcNum = 1
|
|||
|
_queryList = "select `avid`, `cid`, `svid`, `title`, `mid`, `content`, `pubtime`,`duration`,`tid`,`sub_tid`,`ctime`,`cover_url`,`cover_width`,`cover_height`,`state` from video where svid in (%s)"
|
|||
|
_queryRand = "select `svid` from video where state in (4,5) order by mtime desc limit 400;"
|
|||
|
_queryStatisticsList = "select `svid`, `play`, `subtitles`, `like`, `share`, `report` from video_statistics where svid in (%s)"
|
|||
|
_querySvPlay = "select `svid`,`path`,`resolution_retio`,`code_rate`,`video_code`,`file_size`,`duration` from %s where svid in (%s) and is_deleted = 0 order by code_rate desc"
|
|||
|
)
|
|||
|
|
|||
|
// GetList 获取列表,按照db排序,按page返回,用于推荐的降级
|
|||
|
func (d *Dao) GetList(c context.Context, pageSize int64) (result []int64, err error) {
|
|||
|
rows, err := d.db.Query(c, _queryRand)
|
|||
|
if err != nil {
|
|||
|
log.Error("Query(%s) error(%v)", _queryRand, err)
|
|||
|
return
|
|||
|
}
|
|||
|
defer rows.Close()
|
|||
|
|
|||
|
var list []int64
|
|||
|
for rows.Next() {
|
|||
|
sv := new(model.SvInfo)
|
|||
|
if err = rows.Scan(&sv.SVID); err != nil {
|
|||
|
log.Error("row.Scan() error(%v)", err)
|
|||
|
return
|
|||
|
}
|
|||
|
if tag, ok := d.c.Tmap[strconv.FormatInt(sv.TID, 10)]; ok {
|
|||
|
sv.Tag = tag
|
|||
|
}
|
|||
|
list = append(list, sv.SVID)
|
|||
|
}
|
|||
|
|
|||
|
size := len(list)
|
|||
|
if size > int(pageSize) {
|
|||
|
size = int(pageSize)
|
|||
|
}
|
|||
|
rand.Seed(time.Now().Unix())
|
|||
|
sort.Slice(list, func(i, j int) bool {
|
|||
|
return rand.Float32() > 0.5
|
|||
|
})
|
|||
|
result = list[:size]
|
|||
|
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// AttentionRecList 关注页为空时的推荐
|
|||
|
func (d *Dao) AttentionRecList(ctx context.Context, size int64, mid int64, buvid string) (svIDs []int64, err error) {
|
|||
|
svIDs, err = d.abstractRawRecList(ctx, d.recsysClient.UpsRecService, size, mid, buvid)
|
|||
|
log.V(1).Infow(ctx, "log", "get ups rec service", "method", "UpsRecService")
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// RawRecList 获取推荐列表
|
|||
|
func (d *Dao) RawRecList(ctx context.Context, size int64, mid int64, buvid string) (svIDs []int64, err error) {
|
|||
|
svIDs, err = d.abstractRawRecList(ctx, d.recsysClient.RecService, size, mid, buvid)
|
|||
|
log.V(1).Infow(ctx, "log", "get ups rec service", "method", "RecService")
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
type recsysFunc func(ctx context.Context, in *rec.RecsysRequest, opts ...xgrpc.CallOption) (*rec.RecsysResponse, error)
|
|||
|
|
|||
|
// abstractRawRecList 由于访问recsys的方法都是同样的请求&回包,同时过程也一样,因此
|
|||
|
func (d *Dao) abstractRawRecList(ctx context.Context, f recsysFunc, size int64, mid int64, buvid string) (svIDs []int64, err error) {
|
|||
|
var (
|
|||
|
res *rec.RecsysResponse
|
|||
|
)
|
|||
|
|
|||
|
req := &rec.RecsysRequest{
|
|||
|
MID: mid,
|
|||
|
BUVID: buvid,
|
|||
|
Limit: int32(size),
|
|||
|
}
|
|||
|
if tmp, ok := ctx.(*bm.Context).Get("BBQBase"); ok && tmp != nil {
|
|||
|
switch tmp.(type) {
|
|||
|
case *v1.Base:
|
|||
|
base := tmp.(*v1.Base)
|
|||
|
req.App = base.App
|
|||
|
req.AppVersion = base.Version
|
|||
|
}
|
|||
|
}
|
|||
|
if tmp, ok := ctx.(*bm.Context).Get("QueryID"); ok && tmp != nil {
|
|||
|
switch tmp.(type) {
|
|||
|
case string:
|
|||
|
req.QueryID = tmp.(string)
|
|||
|
}
|
|||
|
}
|
|||
|
log.Info("Rec请求 params: [%v]", req)
|
|||
|
//res, err = d.recsysClient.RecService(ctx, req)
|
|||
|
res, err = f(ctx, req)
|
|||
|
debug := ctx.(*bm.Context).Request.Header.Get("debug")
|
|||
|
if err != nil {
|
|||
|
log.Errorv(ctx,
|
|||
|
log.KV("log", fmt.Sprintf("d.recsysClient.RecService err [%v]", err)),
|
|||
|
)
|
|||
|
// 降级(推荐服务已挂)
|
|||
|
svIDs = d.GetRandSvList(int(size))
|
|||
|
return
|
|||
|
} else if len(res.List) == 0 || debug == "1" {
|
|||
|
log.Warnv(ctx,
|
|||
|
log.KV("log", fmt.Sprintf("d.recsysClient.RecService return empty [%v]", res.List)),
|
|||
|
)
|
|||
|
|
|||
|
// 降级(推荐接口返回空)
|
|||
|
svIDs = d.GetRandSvList(int(size))
|
|||
|
return
|
|||
|
} else {
|
|||
|
num := len(res.List)
|
|||
|
if int64(num) != size {
|
|||
|
log.Warnv(ctx,
|
|||
|
log.KV("log", fmt.Sprintf("d.recsysClient.RecService return num[%d] not match size[%d]", num, size)),
|
|||
|
)
|
|||
|
}
|
|||
|
for n, sv := range res.List {
|
|||
|
if int64(n) > size {
|
|||
|
break
|
|||
|
}
|
|||
|
svIDs = append(svIDs, sv.Svid)
|
|||
|
}
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// GetVideoDetail 从数据库video中获取svid相应的信息
|
|||
|
func (d *Dao) GetVideoDetail(ctx context.Context, svIDs []int64) (list []*model.SvInfo, retIDs []int64, err error) {
|
|||
|
list = make([]*model.SvInfo, 0)
|
|||
|
num := len(svIDs)
|
|||
|
if num == 0 {
|
|||
|
return
|
|||
|
}
|
|||
|
var IDsStr string
|
|||
|
for i, id := range svIDs {
|
|||
|
if i < num-1 {
|
|||
|
IDsStr += strconv.FormatInt(id, 10) + ","
|
|||
|
} else {
|
|||
|
IDsStr += strconv.FormatInt(id, 10)
|
|||
|
}
|
|||
|
}
|
|||
|
query := fmt.Sprintf(_queryList, IDsStr)
|
|||
|
rows, err := d.db.Query(ctx, query)
|
|||
|
if err != nil {
|
|||
|
log.Error("Query error(%v)", err)
|
|||
|
return
|
|||
|
}
|
|||
|
defer rows.Close()
|
|||
|
for rows.Next() {
|
|||
|
sv := new(model.SvInfo)
|
|||
|
if err = rows.Scan(&sv.AVID, &sv.CID, &sv.SVID, &sv.Title, &sv.MID, &sv.Content, &sv.Pubtime, &sv.Duration, &sv.TID, &sv.SubTID, &sv.Ctime, &sv.CoverURL, &sv.CoverWidth, &sv.CoverHeight, &sv.State); err != nil {
|
|||
|
log.Error("row.Scan() error(%v)", err)
|
|||
|
return
|
|||
|
}
|
|||
|
if tag, ok := d.c.Tmap[strconv.FormatInt(sv.TID, 10)]; ok {
|
|||
|
sv.Tag = tag
|
|||
|
}
|
|||
|
retIDs = append(retIDs, sv.SVID)
|
|||
|
list = append(list, sv)
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// RawVideos 从数据库批量获取视频信息
|
|||
|
func (d *Dao) RawVideos(ctx context.Context, svIDs []int64) (res map[int64]*model.SvInfo, err error) {
|
|||
|
res = make(map[int64]*model.SvInfo)
|
|||
|
num := len(svIDs)
|
|||
|
if num == 0 {
|
|||
|
return
|
|||
|
}
|
|||
|
var IDsStr string
|
|||
|
for i, id := range svIDs {
|
|||
|
if i < num-1 {
|
|||
|
IDsStr += strconv.FormatInt(id, 10) + ","
|
|||
|
} else {
|
|||
|
IDsStr += strconv.FormatInt(id, 10)
|
|||
|
}
|
|||
|
}
|
|||
|
query := fmt.Sprintf(_queryList, IDsStr)
|
|||
|
rows, err := d.db.Query(ctx, query)
|
|||
|
if err != nil {
|
|||
|
log.Error("Query error(%v)", err)
|
|||
|
return
|
|||
|
}
|
|||
|
defer rows.Close()
|
|||
|
for rows.Next() {
|
|||
|
sv := new(model.SvInfo)
|
|||
|
if err = rows.Scan(&sv.AVID, &sv.CID, &sv.SVID, &sv.Title, &sv.MID, &sv.Content, &sv.Pubtime, &sv.Duration, &sv.TID, &sv.SubTID, &sv.Ctime, &sv.CoverURL, &sv.CoverWidth, &sv.CoverHeight, &sv.State); err != nil {
|
|||
|
log.Error("row.Scan() error(%v)", err)
|
|||
|
return
|
|||
|
}
|
|||
|
res[sv.SVID] = sv
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// RawVideoStatistic get video statistics
|
|||
|
func (d *Dao) RawVideoStatistic(c context.Context, svids []int64) (res map[int64]*model.SvStInfo, err error) {
|
|||
|
const maxIDNum = 20
|
|||
|
var (
|
|||
|
idStr string
|
|||
|
)
|
|||
|
res = make(map[int64]*model.SvStInfo)
|
|||
|
if len(svids) > maxIDNum {
|
|||
|
svids = svids[:maxIDNum]
|
|||
|
}
|
|||
|
l := len(svids)
|
|||
|
for k, svid := range svids {
|
|||
|
if k < l-1 {
|
|||
|
idStr += strconv.FormatInt(svid, 10) + ","
|
|||
|
} else {
|
|||
|
idStr += strconv.FormatInt(svid, 10)
|
|||
|
}
|
|||
|
res[svid] = &model.SvStInfo{}
|
|||
|
}
|
|||
|
rows, err := d.db.Query(c, fmt.Sprintf(_queryStatisticsList, idStr))
|
|||
|
if err != nil {
|
|||
|
log.Error("query error(%s)", err.Error())
|
|||
|
return
|
|||
|
}
|
|||
|
defer rows.Close()
|
|||
|
for rows.Next() {
|
|||
|
ssv := new(model.SvStInfo)
|
|||
|
if err = rows.Scan(&ssv.SVID, &ssv.Play, &ssv.Subtitles, &ssv.Like, &ssv.Share, &ssv.Report); err != nil {
|
|||
|
log.Error("RawVideoStatistic rows.Scan() error(%v)", err)
|
|||
|
return
|
|||
|
}
|
|||
|
res[ssv.SVID] = ssv
|
|||
|
}
|
|||
|
cmtCount, _ := d.ReplyCounts(c, svids, model.DefaultCmType)
|
|||
|
for id, cmt := range cmtCount {
|
|||
|
if _, ok := res[id]; ok {
|
|||
|
res[id].Reply = cmt.Count
|
|||
|
}
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// RawPlayURLs 批量获取cid playurl
|
|||
|
func (d *Dao) RawPlayURLs(c context.Context, cids []int64, qn int64, plat string) (res map[int64]*v1.CVideo, err error) {
|
|||
|
res = make(map[int64]*v1.CVideo)
|
|||
|
var cs string
|
|||
|
// transfer cid array to string
|
|||
|
l := len(cids)
|
|||
|
for i := 0; i < l; i++ {
|
|||
|
if i != l-1 {
|
|||
|
cs += strconv.FormatInt(cids[i], 10) + ","
|
|||
|
} else {
|
|||
|
cs += strconv.FormatInt(cids[i], 10)
|
|||
|
}
|
|||
|
}
|
|||
|
ip := metadata.String(c, metadata.RemoteIP)
|
|||
|
params := url.Values{}
|
|||
|
params.Set("cid", cs)
|
|||
|
params.Set("qn", strconv.FormatInt(qn, 10))
|
|||
|
params.Set("platform", plat)
|
|||
|
var ret struct {
|
|||
|
Code int `json:"code"`
|
|||
|
Data map[string]map[string]*v1.CVideo `json:"data"`
|
|||
|
}
|
|||
|
err = d.httpClient.Get(c, d.c.URLs["bvc_batch"], ip, params, &ret)
|
|||
|
if err != nil || ret.Data["cids"] == nil {
|
|||
|
log.Error("http Get err %v", err)
|
|||
|
return
|
|||
|
}
|
|||
|
for id, v := range ret.Data["cids"] {
|
|||
|
var cid int64
|
|||
|
cid, err = strconv.ParseInt(id, 10, 64)
|
|||
|
if err != nil {
|
|||
|
log.Error("strconv.ParseInt err %v", err)
|
|||
|
}
|
|||
|
if _, ok := res[cid]; !ok {
|
|||
|
res[cid] = new(v1.CVideo)
|
|||
|
}
|
|||
|
res[cid] = v
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// RelPlayURLs 相对地址批量获取playurl
|
|||
|
func (d *Dao) RelPlayURLs(c context.Context, addrs []string) (res map[string]*grpc.VideoKeyItem, err error) {
|
|||
|
res = make(map[string]*grpc.VideoKeyItem)
|
|||
|
req := &grpc.RequestMsg{
|
|||
|
Keys: addrs,
|
|||
|
Backup: uint32(_playBcNum),
|
|||
|
Platform: _defaultPlatform,
|
|||
|
UIP: metadata.String(c, metadata.RemoteIP),
|
|||
|
}
|
|||
|
_str, _ := json.Marshal(req)
|
|||
|
log.V(5).Infov(c, log.KV("log", fmt.Sprintf("bvc play req (%s)", string(_str))))
|
|||
|
r, err := d.bvcPlayClient.ProtobufPlayurl(c, req)
|
|||
|
_str, _ = json.Marshal(r)
|
|||
|
if err != nil {
|
|||
|
log.Error("bvc play err[%v] ret[%s]", err, string(_str))
|
|||
|
return
|
|||
|
}
|
|||
|
log.V(5).Infov(c, log.KV("log", fmt.Sprintf("bvc play ret (%s)", string(_str))))
|
|||
|
res = r.Data
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
//RawSVBvcKey 批量获取playurl相对地址
|
|||
|
func (d *Dao) RawSVBvcKey(c context.Context, svids []int64) (res map[int64][]*model.SVBvcKey, err error) {
|
|||
|
var (
|
|||
|
tb map[string][]string
|
|||
|
rows *sql.Rows
|
|||
|
)
|
|||
|
res = make(map[int64][]*model.SVBvcKey)
|
|||
|
tb = make(map[string][]string)
|
|||
|
tName := "video_bvc_%02d"
|
|||
|
for _, v := range svids {
|
|||
|
if v <= 0 {
|
|||
|
continue
|
|||
|
}
|
|||
|
tbName := fmt.Sprintf(tName, v%100)
|
|||
|
tb[tbName] = append(tb[tbName], strconv.FormatInt(v, 10))
|
|||
|
}
|
|||
|
for k, v := range tb {
|
|||
|
query := fmt.Sprintf(_querySvPlay, k, strings.Join(v, ","))
|
|||
|
if rows, err = d.db.Query(c, query); err != nil {
|
|||
|
log.Errorv(c, log.KV("log", "RawSVBvcKey query sql"), log.KV("err", err))
|
|||
|
continue
|
|||
|
}
|
|||
|
for rows.Next() {
|
|||
|
tmp := model.SVBvcKey{}
|
|||
|
if err = rows.Scan(&tmp.SVID, &tmp.Path, &tmp.ResolutionRetio, &tmp.CodeRate, &tmp.VideoCode, &tmp.FileSize, &tmp.Duration); err != nil {
|
|||
|
log.Errorv(c, log.KV("log", "RawSVBvcKey scan"), log.KV("err", err))
|
|||
|
continue
|
|||
|
}
|
|||
|
res[tmp.SVID] = append(res[tmp.SVID], &tmp)
|
|||
|
}
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// RelRecList 相关推荐列表
|
|||
|
func (d *Dao) RelRecList(ctx context.Context, req *rec.RecsysRequest) (svIDs []int64, err error) {
|
|||
|
log.V(1).Infov(ctx, log.KV("log", fmt.Sprintf("RelatedRecService req [%+v]", req)))
|
|||
|
res, err := d.recsysClient.RelatedRecService(ctx, req)
|
|||
|
if err != nil {
|
|||
|
log.Errorv(ctx,
|
|||
|
log.KV("log", fmt.Sprintf("RelatedRecService err [%v]", err)),
|
|||
|
)
|
|||
|
return
|
|||
|
}
|
|||
|
num := len(res.List)
|
|||
|
if int32(num) != req.Limit {
|
|||
|
log.Errorv(ctx,
|
|||
|
log.KV("log", fmt.Sprintf("RelatedRecService ret num[%d] not match req size[%d]", num, req.Limit)),
|
|||
|
)
|
|||
|
}
|
|||
|
for n, sv := range res.List {
|
|||
|
if int32(n) > req.Limit {
|
|||
|
break
|
|||
|
}
|
|||
|
svIDs = append(svIDs, sv.Svid)
|
|||
|
}
|
|||
|
log.V(1).Infov(ctx, log.KV("log", fmt.Sprintf("RelatedRecService svid [%+v]", svIDs)))
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// SvDel 视频删除
|
|||
|
func (d *Dao) SvDel(c context.Context, in *video.VideoDeleteRequest) (interface{}, error) {
|
|||
|
return d.videoClient.VideoDelete(c, in)
|
|||
|
}
|