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,64 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"app.go",
"comment.go",
"feed.go",
"location.go",
"notice.go",
"push.go",
"report.go",
"search.go",
"service.go",
"share.go",
"sv.go",
"topic.go",
"upload.go",
"user.go",
],
importpath = "go-common/app/interface/bbq/app-bbq/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/bbq/app-bbq/api/http/v1:go_default_library",
"//app/interface/bbq/app-bbq/conf:go_default_library",
"//app/interface/bbq/app-bbq/dao:go_default_library",
"//app/interface/bbq/app-bbq/model:go_default_library",
"//app/interface/bbq/app-bbq/model/grpc:go_default_library",
"//app/service/bbq/common:go_default_library",
"//app/service/bbq/notice-service/api/v1:go_default_library",
"//app/service/bbq/recsys/api/grpc/v1:go_default_library",
"//app/service/bbq/topic/api:go_default_library",
"//app/service/bbq/user/api:go_default_library",
"//app/service/bbq/video/api/grpc/v1:go_default_library",
"//library/conf/env:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/trace:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/Dai0522/go-hash/murmur3:go_default_library",
"//vendor/github.com/json-iterator/go:go_default_library",
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,80 @@
package service
import (
"context"
"time"
"github.com/json-iterator/go"
"go-common/app/interface/bbq/app-bbq/api/http/v1"
"go-common/app/interface/bbq/app-bbq/conf"
)
// AppSetting .
func (s *Service) AppSetting(c context.Context, arg *v1.AppSettingRequest) (resp *v1.AppSettingResponse, err error) {
plat := 1
if arg.Base.Client == "ios" {
plat = 2
}
appVerison, err := s.dao.FetchNewAppVersion(c, plat, arg.VersionCode)
newVersion := uint8(0)
if err == nil && appVerison.ID > 0 {
newVersion = uint8(1)
}
// TODO暂时先这样快速上线以后改(也许。。)
ver := 1
if arg.Base.Client == "ios" && arg.VersionCode > 100000 {
ver = 2
} else if arg.Base.Client == "android" && arg.VersionCode > 101000 {
ver = 2
}
appResource, err := s.dao.FetchAppResource(c, plat, ver)
current := time.Now().Unix()
pubSetting := make(map[string]interface{})
b, _ := jsoniter.Marshal(conf.App)
jsoniter.Unmarshal(b, &pubSetting)
for _, v := range appResource {
if v.StartTime.Time().Unix() < current && current < v.EndTime.Time().Unix() {
pubSetting["dynamic_effect"] = v.ID
}
}
resp = &v1.AppSettingResponse{
Public: pubSetting,
Update: &v1.AppUpdate{
NewVersion: newVersion,
Info: appVerison,
},
Resources: appResource,
}
return
}
// AppPackage .
func (s *Service) AppPackage(c context.Context, lastest int) (resp []*v1.AppPackage, err error) {
resp = make([]*v1.AppPackage, 0)
if lastest > 0 {
result, e := s.dao.FetchNewAppVersion(c, 1, 0)
resp = append(resp, &v1.AppPackage{
ID: int64(result.ID),
Platform: uint8(result.Platform),
VersionName: result.Name,
VersionCode: uint32(result.Code),
Title: result.Title,
Content: result.Content,
Download: result.Download,
MD5: result.MD5,
Size: int32(result.Size),
Force: uint8(result.Force),
Status: uint8(result.Status),
})
err = e
} else {
resp, err = s.dao.FetchAppPackage(c)
}
return
}

View File

@@ -0,0 +1,292 @@
package service
import (
"context"
"fmt"
"go-common/app/interface/bbq/app-bbq/api/http/v1"
"go-common/app/interface/bbq/app-bbq/dao"
"go-common/app/interface/bbq/app-bbq/model"
notice "go-common/app/service/bbq/notice-service/api/v1"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"net/url"
"strconv"
)
//CommentSubCursor 游标评论列表
func (s *Service) CommentSubCursor(c context.Context, mid int64, arg *v1.CommentSubCursorReq, device *bm.Device) (res *model.SubCursorRes, err error) {
res = new(model.SubCursorRes)
if _, err = s.dao.VideoBase(c, mid, arg.SvID); err != nil {
log.Warnw(c, "log", "get video base fail", "svid", arg.SvID)
return
}
req := map[string]interface{}{
"oid": arg.SvID,
"type": arg.Type,
"sort": arg.Sort,
"root": arg.Root,
}
if len(arg.Access) != 0 {
req["access_key"] = arg.Access
}
if arg.RpID != 0 {
req["rpid"] = arg.RpID
}
if arg.Size != 0 {
req["size"] = arg.Size
}
if arg.MinID > 0 && arg.MaxID > 0 {
err = ecode.ParamInvalid
return
}
if arg.MinID != 0 {
req["min_id"] = arg.MinID
}
if arg.MaxID != 0 {
req["max_id"] = arg.MaxID
}
res, err = s.dao.ReplySubCursor(c, req)
return
}
//CommentList 游标评论列表
func (s *Service) CommentList(c context.Context, arg *v1.CommentListReq, device *bm.Device) (res *model.ReplyList, err error) {
res = new(model.ReplyList)
if _, err = s.dao.VideoBase(c, arg.MID, arg.SvID); err != nil {
log.Warnw(c, "log", "get video base fail", "svid", arg.SvID)
return
}
req := map[string]interface{}{
"oid": arg.SvID,
"type": arg.Type,
"sort": arg.Sort,
"nohot": arg.NoHot,
}
if len(arg.Access) != 0 {
req["access_key"] = arg.Access
}
if arg.Pn != 0 {
req["pn"] = arg.Pn
}
if arg.Ps != 0 {
req["ps"] = arg.Ps
}
if device.Build != 0 {
req["build"] = arg.Build
}
if device.RawPlatform != "" {
req["plat"] = arg.Plat
}
if device.RawMobiApp != "" {
req["mobi_app"] = device.RawMobiApp
}
res, err = s.dao.ReplyList(c, req)
return
}
//CommentCursor 游标评论列表
func (s *Service) CommentCursor(c context.Context, arg *v1.CommentCursorReq, device *bm.Device) (res *model.CursorRes, err error) {
res = new(model.CursorRes)
if _, err = s.dao.VideoBase(c, arg.MID, arg.SvID); err != nil {
log.Warnw(c, "log", "get video base fail", "svid", arg.SvID)
return
}
req := map[string]interface{}{
"oid": arg.SvID,
"type": arg.Type,
"sort": arg.Sort,
"max_id": arg.MaxID,
"min_id": arg.MinID,
"size": arg.Size,
}
if arg.RpID != 0 {
req["rpid"] = arg.RpID
}
if len(arg.Access) != 0 {
req["access_key"] = arg.Access
}
res, err = s.dao.ReplyCursor(c, req)
return
}
//CommentAdd 发表评论评论服务
func (s *Service) CommentAdd(c context.Context, mid int64, arg *v1.CommentAddReq, device *bm.Device) (res *model.AddRes, err error) {
res = new(model.AddRes)
// 屏蔽词
level, filterErr := s.dao.Filter(c, arg.Message, dao.FilterAreaReply)
if filterErr != nil {
log.Errorv(c, log.KV("log", "filter fail"))
} else if level >= dao.FilterLevel {
err = ecode.CommentFilterErr
log.Warnv(c, log.KV("log", fmt.Sprintf("content filter fail: content=%s, level=%d", arg.Message, level)))
return
}
var upMid int64
videoBase, err := s.dao.VideoBase(c, mid, arg.SvID)
if err != nil {
log.Warnw(c, "log", "get video base fail", "svid", arg.SvID)
return
}
upMid = videoBase.Mid
parentMid := upMid
req := map[string]interface{}{
"oid": arg.SvID,
"type": arg.Type,
"message": arg.Message,
"access_key": arg.AccessKey,
}
if arg.At != "" {
req["at"] = arg.At
}
if arg.Parent != 0 {
req["parent"] = arg.Parent
req["root"] = arg.Root
} else if arg.Root != 0 {
req["root"] = arg.Root
}
if arg.Plat != 0 {
req["plat"] = arg.Plat
}
if arg.Device != "" {
req["device"] = arg.Plat
}
if arg.Code != "" {
req["code"] = arg.Code
}
res, err = s.dao.ReplyAdd(c, req)
//wrap error
switch ecode.Cause(err).Code() {
case ecode.ReplyDeniedAsCaptcha.Code():
err = ecode.CommentForbidden
case ecode.ReplyContentOver.Code():
err = ecode.CommentLengthIllegal
}
// 推送评论给通知中心
if err == nil {
title := "评论了你的作品"
bizType := int32(notice.NoticeBizTypeSv)
rootID := res.RpID
if arg.Parent != 0 {
title = "回复了你的评论"
bizType = int32(notice.NoticeBizTypeComment)
rootID = arg.Parent
// get root comment's owner
list, tmpErr := s.dao.ReplyMinfo(c, arg.SvID, []int64{arg.Parent})
if tmpErr != nil || len(list) == 0 {
log.Warnv(c, log.KV("log", "get root reply rpid info fail"), log.KV("rsp_size", len(list)))
return
}
reply, exists := list[arg.Parent]
if !exists {
log.Errorv(c, log.KV("log", "not found reply rpid's info"))
return
} else if reply.Mid == 0 {
log.Errorv(c, log.KV("log", "reply rpid's owner mid=0"))
return
}
parentMid = reply.Mid
}
if parentMid == mid {
log.V(1).Infov(c, log.KV("log", "action_mid=mid"), log.KV("mid", mid))
return
}
urlVal := make(url.Values)
urlVal.Add("svid", strconv.FormatInt(arg.SvID, 10))
urlVal.Add("rootid", strconv.FormatInt(rootID, 10))
urlVal.Add("rpid", strconv.FormatInt(res.RpID, 10))
jumpURL := fmt.Sprintf("qing://commentdetail?%s", urlVal.Encode())
notice := &notice.NoticeBase{
Mid: parentMid, ActionMid: mid, SvId: arg.SvID, NoticeType: notice.NoticeTypeComment, Title: title, Text: arg.Message,
JumpUrl: jumpURL, BizType: bizType, BizId: res.RpID}
tmpErr := s.dao.CreateNotice(c, notice)
if tmpErr != nil {
log.Error("create comment notice fail: notice_msg=%s", notice.String())
}
}
return
}
//CommentLike 评论点赞服务
func (s *Service) CommentLike(c context.Context, mid int64, arg *v1.CommentLikeReq, device *bm.Device) (err error) {
if _, err = s.dao.VideoBase(c, mid, arg.SvID); err != nil {
log.Warnw(c, "log", "get video base fail", "svid", arg.SvID)
return
}
req := map[string]interface{}{
"oid": arg.SvID,
"type": arg.Type,
"rpid": arg.RpID,
"action": arg.Action,
"access_key": arg.AccessKey,
}
err = s.dao.ReplyLike(c, req)
if ecode.Cause(err).Code() == ecode.ReplyForbidAction.Code() {
err = ecode.CommentForbidLike
return
}
// TODO: 推送评论给通知中心
if arg.Action == 1 && err == nil {
// get root comment's owner
list, tmpErr := s.dao.ReplyMinfo(c, arg.SvID, []int64{arg.RpID})
if tmpErr != nil || len(list) == 0 {
log.Warnv(c, log.KV("log", "get root rpid info fail"))
return
}
reply, exists := list[arg.RpID]
if !exists {
log.Errorv(c, log.KV("log", "not found reply rpid's info"))
return
} else if reply.Mid == 0 {
log.Errorv(c, log.KV("log", "reply rpid's owner mid=0"))
return
}
parentMid := reply.Mid
if parentMid == mid {
log.V(1).Infov(c, log.KV("log", "action_mid=mid"), log.KV("mid", mid))
return
}
text := ""
if reply.Content != nil {
text = reply.Content.Message
}
title := "点赞了你的评论"
bizType := int32(notice.NoticeBizTypeComment)
notice := &notice.NoticeBase{
Mid: parentMid, ActionMid: mid, SvId: arg.SvID, NoticeType: notice.NoticeTypeLike, Title: title, Text: text,
BizType: bizType, BizId: arg.RpID}
tmpErr = s.dao.CreateNotice(c, notice)
if tmpErr != nil {
log.Errorv(c, log.KV("log", "create like notice fail: notice_msg="+notice.String()+", err="+err.Error()))
return
}
}
return
}
//CommentReport 评论举报服务
func (s *Service) CommentReport(c context.Context, arg *v1.CommentReportReq) (err error) {
req := map[string]interface{}{
"oid": arg.SvID,
"type": arg.Type,
"rpid": arg.RpID,
"reason": arg.Reason,
"access_key": arg.AccessKey,
}
if arg.Content != "" {
req["content"] = arg.Content
}
err = s.dao.ReplyReport(c, req)
return
}

View File

@@ -0,0 +1,324 @@
package service
import (
"context"
"encoding/json"
"fmt"
"go-common/app/service/bbq/common"
"time"
v1 "go-common/app/interface/bbq/app-bbq/api/http/v1"
"go-common/app/interface/bbq/app-bbq/model"
video "go-common/app/service/bbq/video/api/grpc/v1"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/trace"
xtime "go-common/library/time"
)
// FeedUpdateNum 关注页红点
func (s *Service) FeedUpdateNum(c context.Context, mid int64) (res v1.FeedUpdateNumResponse, err error) {
// 0. 获取关注链
followedMid, err := s.dao.FetchFollowList(c, mid)
if err != nil {
log.Errorv(c, log.KV("log", "fetch follow fail"), log.KV("mid", mid), log.KV("err", err))
return
}
if len(followedMid) == 0 {
res.Num = 0
log.V(1).Infov(c, log.KV("log", "no_follow"), log.KV("mid", mid))
return
}
// 1. 获取mid上次浏览点
pubTime, _ := s.dao.GetMIDLastPubtime(c, mid)
// 2. 获取新的视频
newlySvID, err := s.dao.FetchAvailableOutboxList(c, common.FeedStates, followedMid, false, 0, xtime.Time(pubTime), 1)
if err != nil {
log.Errorv(c, log.KV("log", "fetch available outbox list fail"), log.KV("mid", mid), log.KV("pubTime", pubTime))
return
}
// 3. form rsp
if len(newlySvID) == 0 {
res.Num = 0
} else {
res.Num = 1
}
return
}
// FeedList 关注页短视屏列表
func (s *Service) FeedList(c context.Context, req *v1.FeedListRequest) (res *v1.FeedListResponse, err error) {
var (
mid = req.MID
markStr = req.Mark
)
res = new(v1.FeedListResponse)
res.List = make([]*v1.SvDetail, 0)
res.RecList = make([]*v1.SvDetail, 0)
// 0.前期校验
// 解析mark获取last_svid
var mark model.FeedMark
isFirstPage := false
// 关注up的总视频列表是否空
needRecList := false
if len(markStr) != 0 {
var markData = []byte(markStr)
err = json.Unmarshal(markData, &mark)
if err != nil {
err = ecode.ReqParamErr
log.Errorv(c, log.KV("log", "mark_unmarshal"), log.KV("mark", markStr))
return
}
log.V(1).Infov(c, log.KV("mark", markData))
}
if mark.IsRec {
needRecList = true
} else if mark.LastSvID == 0 {
isFirstPage = true
mark.LastSvID = model.MaxInt64
mark.LastPubtime = xtime.Time(time.Now().Unix())
// 更新最新浏览svid
s.dao.SetMIDLastPubtime(c, mid, int64(mark.LastPubtime))
}
// 1. 需要获取详情的svid列表
var svids []int64
if !needRecList {
// 1.获取关注链
var followedMid []int64
followedMid, err = s.dao.FetchFollowList(c, mid)
if err != nil {
log.Errorv(c, log.KV("log", "fetch_follow"), log.KV("mid", mid))
return
}
// 无关注人,直接返回
if len(followedMid) == 0 {
res.HasMore = false
needRecList = true
log.V(1).Infov(c, log.KV("log", "no_follow"), log.KV("mid", mid))
} else {
// 2.获取svid列表
svids, err = s.dao.FetchAvailableOutboxList(c, common.FeedStates, followedMid, true, mark.LastSvID, mark.LastPubtime, model.FeedListLen)
if err != nil {
log.Warnw(c, "log", "fetch available outbox list fail")
return
}
// 为了保护列表,所以/2后面切换获取逻辑就可以去掉了
if len(svids) < model.FeedListLen/2 {
res.HasMore = false
// 关注人了,但是这些人没有发布过视频
if len(svids) == 0 && isFirstPage {
needRecList = true
}
} else {
res.HasMore = true
}
}
}
// 如果第一页就是empty或者mark携带了is_rec需要为用户推荐一些视频
if needRecList {
svids, err = s.dao.AttentionRecList(c, model.FeedListLen, mid, req.BUVID)
if err != nil {
log.Warnw(c, "log", "get attention feed rec fail")
return
}
}
// 2.获取sv信息列表
var list []*v1.SvDetail
// 2.0 获取sv详情
detailMap, err := s.getVideoDetail(c, req.MID, req.Qn, req.Device, svids, true)
if err != nil {
log.Warnv(c, log.KV("log", "get video detail fail"))
return
} else if len(detailMap) == 0 {
log.Warnv(c, log.KV("log", "feed list empty"), log.KV("svid_num", len(svids)))
return
}
// 2.1 获取热评
hots, hotErr := s.dao.ReplyHot(c, mid, svids)
if hotErr != nil {
log.Warnv(c, log.KV("log", "get hot reply fail"))
}
log.V(1).Infov(c, log.KV("log", "get_video_detail"), log.KV("req_size", len(svids)),
log.KV("rsp_size", len(detailMap)))
for _, svID := range svids {
v, exists := detailMap[svID]
if exists {
if hots, ok := hots[svID]; ok {
v.HotReply.Hots = hots
}
list = append(list, v)
} else {
log.Warnv(c, log.KV("log", "sv_not_found"), log.KV("mid", mid), log.KV("svid", svID))
}
}
// 3. 组装回包判断往哪个list塞数据
var nextMark model.FeedMark
if needRecList {
res.RecList = list
nextMark.IsRec = true
} else {
res.List = list
if len(list) > 0 && res.HasMore {
nextMark.LastSvID = list[len(list)-1].SVID
nextMark.LastPubtime = list[len(list)-1].Pubtime
}
}
jsonStr, _ := json.Marshal(nextMark) // marshal的时候相信库函数不做err判断
res.Mark = string(jsonStr)
return
}
// SpaceSvList 个人空间视频列表
func (s *Service) SpaceSvList(c context.Context, req *v1.SpaceSvListRequest) (res *v1.SpaceSvListResponse, err error) {
// 0.前期校验
// 这里就不校验up主是否存在
res = new(v1.SpaceSvListResponse)
res.List = make([]*v1.SvDetail, 0)
upMid := req.UpMid
if upMid == 0 {
err = ecode.ReqParamErr
log.Errorv(c, log.KV("log", "up mid is 0"), log.KV("up_mid", 0))
return
}
// parseCursor
cursor, cursorNext, err := parseCursor(req.CursorPrev, req.CursorNext)
if err != nil {
return
}
// 1. 如果是主人态其第一页则进行额外prepare_list
if req.MID == req.UpMid && len(req.CursorNext) == 0 && len(req.CursorPrev) == 0 {
prepareRes, tmpErr := s.videoClient.ListPrepareVideo(c, &video.PrepareVideoRequest{Mid: req.MID})
if tmpErr != nil {
log.Warnw(c, "log", "get prepare video fail", "mid", req.MID)
} else {
res.PrepareList = prepareRes.List
}
}
// 2. 获取svid列表
states := common.SpaceFanStates
if req.MID == req.UpMid {
states = common.SpaceOwnerStates
}
svids, err := s.dao.FetchAvailableOutboxList(c, states, []int64{upMid}, cursorNext, cursor.CursorID, cursor.CursorTime, req.Size)
if err != nil {
log.Infov(c, log.KV("log", "fetch_outbox_list"), log.KV("error", err))
return
}
if len(svids) < req.Size/2 {
res.HasMore = false
if len(svids) == 0 {
return
}
} else {
res.HasMore = true
}
// 3.获取sv详情
detailMap, err := s.svInfos(c, svids, req.MID, true)
if err != nil {
log.Errorv(c, log.KV("log", "get video detail fail"))
return
} else if len(detailMap) == 0 {
log.Warnv(c, log.KV("log", "feed list empty"), log.KV("svid_num", len(svids)))
return
}
for _, svID := range svids {
item, exists := detailMap[svID]
if exists {
sv := new(v1.SvDetail)
sv.VideoResponse = *item
res.List = append(res.List, sv)
} else {
log.Warnv(c, log.KV("log", "sv_not_found"), log.KV("svid", svID))
}
}
// query id
tracer, _ := trace.FromContext(c)
queryID := fmt.Sprintf("%s", tracer)
// 4. 后处理为每个item添加cursor值
var itemCursor model.CursorValue
for _, item := range res.List {
itemCursor.CursorID = item.SVID
itemCursor.CursorTime = item.Pubtime
jsonStr, _ := json.Marshal(itemCursor) // marshal的时候相信库函数不做err判断
item.CursorValue = string(jsonStr)
item.QueryID = queryID
}
return
}
// getVideoDetail 返回SvDetail的列表返回的list顺序和svids顺序一致但不保证svid都能出现在list中
func (s *Service) getVideoDetail(c context.Context, mid int64, qn int64, device *bm.Device, svids []int64, fullVersion bool) (res map[int64]*v1.SvDetail, err error) {
res = make(map[int64]*v1.SvDetail)
if len(svids) == 0 {
return
}
// 拉取视频详情
svRes, err := s.svInfos(c, svids, mid, true)
if err != nil {
log.Errorv(c, log.KV("log", "get video detail from dao fail"))
return
}
log.V(1).Infov(c, log.KV("log", "get_video_detail"), log.KV("req_size", len(svids)), log.KV("rsp_size", len(svRes)))
if len(svRes) == 0 {
log.Warnv(c, log.KV("log", "get_video_detail_empty"), log.KV("req_size", len(svids)), log.KV("rsp_size", len(svRes)))
return
}
// 开始组装回包
currentTs := time.Now().Unix()
for svid, svInfo := range svRes {
sv := new(v1.SvDetail)
// 组装video基础信息
sv.VideoResponse = *svInfo
if currentTs > int64(sv.Pubtime) {
sv.ElapsedTime = currentTs - int64(sv.Pubtime)
}
res[svid] = sv
}
return
}
// parseCursor从cursor_prev和cursor_next判断请求的方向以及生成cursor
func parseCursor(cursorPrev string, cursorNext string) (cursor model.CursorValue, directionNext bool, err error) {
// 判断是向前还是向后查询
directionNext = true
cursorStr := cursorNext
if len(cursorNext) == 0 && len(cursorPrev) > 0 {
directionNext = false
cursorStr = cursorPrev
}
// 解析cursor中的cursor_id
if len(cursorStr) != 0 {
var cursorData = []byte(cursorStr)
err = json.Unmarshal(cursorData, &cursor)
if err != nil {
err = ecode.ReqParamErr
return
}
}
// 第一次请求的时候携带的svid=0需要转成max传给dao层
if directionNext && cursor.CursorID == 0 {
cursor.CursorID = model.MaxInt64
cursor.CursorTime = xtime.Time(time.Now().Unix())
}
return
}

View File

@@ -0,0 +1,86 @@
package service
import (
"context"
http "go-common/app/interface/bbq/app-bbq/api/http/v1"
"go-common/app/interface/bbq/app-bbq/model"
)
// GetLocaitonAll .
func (s *Service) GetLocaitonAll(c context.Context, arg *http.LocationRequest) (*http.LocationResponse, error) {
result := &http.LocationResponse{}
m, err := s.dao.GetLocationAll(c)
if err != nil {
return result, err
}
var coutries []*model.Location
for _, item := range (*m)[arg.PID] {
coutry := &model.Location{
ID: item.ID,
PID: item.PID,
Name: item.Name,
}
var provices []*model.Location
for _, v := range (*m)[item.ID] {
provice := &model.Location{
ID: v.ID,
PID: v.PID,
Name: v.Name,
}
var citys []*model.Location
for _, u := range (*m)[v.ID] {
city := &model.Location{
ID: u.ID,
PID: u.PID,
Name: u.Name,
}
var area []*model.Location
for _, w := range (*m)[u.ID] {
var child []*model.Location
area = append(area, &model.Location{
ID: w.ID,
PID: w.PID,
Name: w.Name,
Child: child,
})
}
city.Child = area
citys = append(citys, city)
}
provice.Child = citys
provices = append(provices, provice)
}
coutry.Child = provices
coutries = append(coutries, coutry)
}
result.List = coutries
return result, err
}
// GetLocationChild .
func (s *Service) GetLocationChild(c context.Context, arg *http.LocationRequest) (*http.LocationResponse, error) {
result := &http.LocationResponse{}
m, err := s.dao.GetLocationChild(c, arg.PID)
if err != nil {
return result, err
}
var provices []*model.Location
for _, v := range (*m)[arg.PID] {
var child []*model.Location
provice := &model.Location{
ID: v.ID,
PID: v.PID,
Name: v.Name,
Child: child,
}
provices = append(provices, provice)
}
result.List = provices
return result, err
}

View File

@@ -0,0 +1,170 @@
package service
import (
"context"
"encoding/json"
"go-common/app/interface/bbq/app-bbq/api/http/v1"
"go-common/app/interface/bbq/app-bbq/conf"
"go-common/app/interface/bbq/app-bbq/model"
"go-common/app/service/bbq/common"
notice "go-common/app/service/bbq/notice-service/api/v1"
"go-common/library/ecode"
"go-common/library/log"
)
var noticeTypes map[int32]bool
const (
_noticeTypeLike = 1
_noticeTypeComment = 2
_noticeTypeFan = 3
_noticeTypeSysMsg = 4
)
func init() {
noticeTypes = map[int32]bool{
_noticeTypeLike: true,
_noticeTypeComment: true,
_noticeTypeFan: true,
_noticeTypeSysMsg: true,
}
}
// GetNoticeNum 通知红点
func (s *Service) GetNoticeNum(ctx context.Context, mid int64) (res *v1.NoticeNumResponse, err error) {
res = new(v1.NoticeNumResponse)
unreadList, err := s.dao.GetNoticeUnread(ctx, mid)
if err != nil {
log.Warnv(ctx, log.KV("log", "get notice unread fail: err="+err.Error()))
return
}
var redDot int64
for _, v := range unreadList {
redDot += v.UnreadNum
}
res.RedDot = redDot
return
}
// NoticeOverview 通知中心概述
func (s *Service) NoticeOverview(ctx context.Context, mid int64) (res *v1.NoticeOverviewResponse, err error) {
res = new(v1.NoticeOverviewResponse)
unreadList, err := s.dao.GetNoticeUnread(ctx, mid)
if err != nil {
log.Warnv(ctx, log.KV("log", "get notice unread fail: err="+err.Error()))
return
}
unreadMap := make(map[int32]*notice.UnreadItem)
for _, item := range unreadList {
unreadMap[item.NoticeType] = item
}
for _, v := range conf.Conf.Notices {
var unreadNum int64
if unreadInfo, exists := unreadMap[v.NoticeType]; exists {
unreadNum = unreadInfo.UnreadNum
}
res.Notices = append(res.Notices, &v1.NoticeOverview{NoticeType: v.NoticeType, ShowType: v.ShowType, Name: v.Name, UnreadNum: unreadNum})
}
return
}
// NoticeList 请求通知列表,组装成通知消息列表
func (s *Service) NoticeList(ctx context.Context, req *v1.NoticeListRequest) (res *v1.NoticeListResponse, err error) {
res = new(v1.NoticeListResponse)
// 0. 校验请求合法性
if _, exists := noticeTypes[req.NoticeType]; !exists {
return nil, ecode.NoticeTypeErr
}
cursor, _, err := parseCursor("", req.CursorNext)
if err != nil {
log.Errorv(ctx, log.KV("log", "parse cursor fail: cursor_next="+req.CursorNext))
return
}
// 1. 请求notice列表
list, err := s.dao.NoticeList(ctx, req.NoticeType, req.Mid, cursor.CursorID)
if err != nil {
return
}
// 2. 根据notice请求相应业务方
// 2.0 整理notice列表
var actionMIDs []int64 // 不是map有可能重复
var svIDs []int64
for _, item := range list {
if item.ActionMid != 0 {
actionMIDs = append(actionMIDs, item.ActionMid)
}
if item.SvId != 0 {
svIDs = append(svIDs, item.SvId)
}
}
needUserInfo := true
needFollowState := false
needSvInfo := false
switch req.NoticeType {
case _noticeTypeLike:
needSvInfo = true
case _noticeTypeComment:
needSvInfo = true
case _noticeTypeFan:
needFollowState = true
case _noticeTypeSysMsg:
needUserInfo = false
}
// 2. 请求用户信息
var userInfos map[int64]*v1.UserInfo
if needUserInfo && len(actionMIDs) > 0 {
userInfos, err = s.dao.BatchUserInfo(ctx, req.Mid, actionMIDs, false, false, needFollowState)
if err != nil {
log.Errorv(ctx, log.KV("log", "batch fetch user infos fail"))
err = nil
}
}
// 2. 请求视频信息
svInfos := make(map[int64]*model.SvInfo)
if needSvInfo && len(svIDs) > 0 {
svList, _, err := s.dao.GetVideoDetail(ctx, svIDs)
if err != nil {
log.Warnv(ctx, log.KV("log", "batch fetch sv detail fail"))
err = nil
} else {
for _, sv := range svList {
svInfos[sv.SVID] = sv
}
}
}
// 3. 组成回包
for _, item := range list {
var noticeMsg v1.NoticeMsg
noticeMsg.NoticeBase = item
// showtype没有用这里就按照noticetype返回
// md想的好好的扩展灵活性完全没被践行
noticeMsg.ShowType = req.NoticeType
if needUserInfo && (noticeMsg.ActionMid != 0) {
if val, exists := userInfos[noticeMsg.ActionMid]; exists {
noticeMsg.UserInfo = val
}
}
if needSvInfo && (noticeMsg.SvId != 0) {
if val, exists := svInfos[noticeMsg.SvId]; exists && noticeMsg.State >= common.VideoStPendingPassReview {
noticeMsg.Pic = val.CoverURL
} else {
noticeMsg.State = 1
noticeMsg.ErrMsg = "视频不见了"
}
}
// cursor_value
var cursor model.CursorValue
cursor.CursorID = item.Id
jsonBytes, _ := json.Marshal(cursor)
noticeMsg.CursorValue = string(jsonBytes)
res.List = append(res.List, &noticeMsg)
}
return
}

View File

@@ -0,0 +1,27 @@
package service
import (
"context"
"go-common/app/interface/bbq/app-bbq/api/http/v1"
notice "go-common/app/service/bbq/notice-service/api/v1"
)
// PushRegister .
func (s *Service) PushRegister(c context.Context, args *notice.UserPushDev) (response *v1.PushRegisterResponse, err error) {
err = s.dao.PushLogin(c, args)
response = &v1.PushRegisterResponse{}
return
}
// PushLogout .
func (s *Service) PushLogout(c context.Context, args *notice.UserPushDev) (response *v1.PushRegisterResponse, err error) {
err = s.dao.PushLogout(c, args)
response = &v1.PushRegisterResponse{}
return
}
// PushCallback .
func (s *Service) PushCallback(c context.Context, args *v1.PushCallbackRequest, mid int64, buvid string) (response *v1.PushCallbackResponse, err error) {
s.dao.PushCallback(c, args.TID, args.NID, mid, buvid)
return
}

View File

@@ -0,0 +1,66 @@
package service
import (
"context"
"go-common/app/interface/bbq/app-bbq/api/http/v1"
"go-common/app/interface/bbq/app-bbq/model"
"go-common/library/ecode"
"go-common/library/log"
"github.com/golang/protobuf/ptypes/empty"
)
//Report ..
func (s *Service) Report(c context.Context, arg *v1.ReportRequest, mid int64, ak string) (res *empty.Empty, err error) {
res = &empty.Empty{}
switch arg.Type {
case model.TypeVideo:
if err = s.dao.ReportVideo(c, arg.SVID, mid, model.MapReasons[arg.Reason]); err != nil {
log.Warnv(c, log.KV("event", "report video err"), log.KV("err", err), log.KV("req", arg))
return
}
case model.TypeComment:
reason := model.BiliReasonsMap[arg.Reason]
content := ""
if reason == 0 {
content = "BBQ其他理由"
}
req := &v1.CommentReportReq{
SvID: arg.SVID,
RpID: arg.RpID,
Reason: reason,
Content: content,
Type: model.DefaultCmType,
AccessKey: ak,
}
if err = s.CommentReport(c, req); err != nil {
log.Warnv(c, log.KV("event", "report comment err"), log.KV("err", err), log.KV("req", arg))
return
}
case model.TypeDanmu:
var (
dm *model.Danmu
)
if dm, err = s.dao.RawBullet(c, arg.Danmu); err != nil {
log.Errorw(c, "event", "query mid", "err", err)
err = ecode.ReportDanmuError
return
}
if err = s.dao.ReportDanmu(c, arg.Danmu, dm.MID, mid, model.MapReasons[arg.Reason]); err != nil {
log.Warnv(c, log.KV("event", "report danmu err"), log.KV("err", err), log.KV("req", arg))
return
}
case model.TypeUser:
var rType int
if arg.Reason == 5 {
rType = 0
} else {
rType = 1
}
if err = s.dao.ReportUser(c, rType, arg.UpMID, mid, model.MapReasons[arg.Reason]); err != nil {
log.Warnv(c, log.KV("event", "report user err"), log.KV("err", err), log.KV("req", arg))
return
}
}
return
}

View File

@@ -0,0 +1,330 @@
package service
import (
"context"
"encoding/json"
"go-common/app/interface/bbq/app-bbq/api/http/v1"
"go-common/app/interface/bbq/app-bbq/model"
"go-common/app/service/bbq/common"
"go-common/library/ecode"
"go-common/library/log"
"github.com/json-iterator/go"
)
const (
searchTypeVideo = "bbq_video"
searchTypeUser = "bbq_user"
suggestType = "bbq"
suggestVer = "v4"
searchLen = 20
)
// SearchCursor .
type SearchCursor struct {
Offset int64 `json:"offset"` // 默认为0数据从1开始
}
func parseSearchCursor(c context.Context, cursorPrev, cursorNext string) (cursor *SearchCursor, directionNext bool, err error) {
cursor = new(SearchCursor)
// 判断是向前还是向后查询
directionNext = true
cursorStr := cursorNext
if len(cursorNext) == 0 && len(cursorPrev) > 0 {
directionNext = false
cursorStr = cursorPrev
}
// 解析
if len(cursorStr) != 0 {
var cursorData = []byte(cursorStr)
err = json.Unmarshal(cursorData, &cursor)
if err != nil {
err = ecode.ReqParamErr
return
}
}
return
}
//HotWord .
func (s *Service) HotWord(c context.Context, req *v1.HotWordRequest) (res *v1.HotWordResponse, err error) {
res = new(v1.HotWordResponse)
res.List = append(res.List, "小姐姐")
res.List = append(res.List, "舞蹈")
res.List = append(res.List, "MMD")
return
}
// VideoSearch 视频搜索
func (s *Service) VideoSearch(c context.Context, arg *v1.BaseSearchReq) (res *v1.VideoSearchRes, err error) {
res = new(v1.VideoSearchRes)
var (
sres []*model.VideoSearchResult
searchRes *model.RawSearchRes
relids []int32
svids []int64
svidMap map[int64]int64
)
// 0. check
// 1. 生成请求,会根据请求参数
// v2接口
reqPage := int64(1)
firstItemOffset := int64(0)
var cursor *SearchCursor
var directionNext bool
if arg.Page == 0 {
cursor, directionNext, err = parseSearchCursor(c, arg.CursorPrev, arg.CursorNext)
if err != nil {
log.Warnw(c, "log", "parse search cursor fail", "prev", arg.CursorPrev, "next", arg.CursorNext)
return
}
firstItemOffset = cursor.Offset - 1
if directionNext {
firstItemOffset = cursor.Offset + 1
}
reqPage = (firstItemOffset-1)/searchLen + 1
log.V(1).Infow(c, "log", "v2 request", "next", arg.CursorNext, "prev", arg.CursorPrev)
} else {
reqPage = arg.Page
}
var json = jsoniter.ConfigCompatibleWithStandardLibrary
sp := &model.SearchBaseReq{
KeyWord: arg.Key,
Type: searchTypeVideo,
Page: reqPage,
PageSize: searchLen,
Highlight: int64(arg.Highlight),
}
// 相信搜索结果返回的就是searchLen
searchRes, err = s.dao.SearchBBQ(c, sp)
if searchRes.Res == nil {
return
}
if err = json.Unmarshal(searchRes.Res, &sres); err != nil {
return
}
for _, v := range sres {
relids = append(relids, v.ID)
}
if len(relids) == 0 {
log.Infov(c, log.KV("log", "search ret empty"))
return
}
ids := s.dao.ParseRel2ID(relids)
svidMap, err = s.dao.ConvID2SVID(c, ids)
if err != nil {
log.Error("ConvID2SVID err [%v]", err)
err = nil
return
}
for _, svid := range svidMap {
svids = append(svids, svid)
}
svMap, _ := s.svInfos(c, svids, 0, false)
for index, sinfo := range sres {
id := s.dao.ParseRel2ID([]int32{sinfo.ID})
if id[0] == 0 {
continue
}
if svid, ok := svidMap[id[0]]; ok {
if sv, ok := svMap[svid]; ok {
if common.IsSearchSvStateAvailable(int64(sv.State)) {
r := new(v1.VideoSearchList)
r.VideoResponse = *sv
r.SVID = svid
r.HitColumns = sinfo.HitColumns
r.TitleHighlight = sinfo.Title
r.Offset = (reqPage-1)*int64(searchLen) + int64(index) + 1
res.List = append(res.List, r)
} else {
log.Warnw(c, "log", "get error svid in search list", "svid", svid, "sv", sv)
}
}
}
}
res.NumPage = searchRes.PageNum
res.Page = searchRes.Page
res.HasMore = false
if arg.Page == 0 {
if (directionNext && searchRes.PageNum > searchRes.Page) || (!directionNext && searchRes.Page > 1) {
res.HasMore = true
}
var list []*v1.VideoSearchList
if directionNext {
for _, item := range res.List {
if item.Offset < firstItemOffset {
continue
}
list = append(list, item)
}
} else {
if firstItemOffset >= int64(len(res.List)) {
firstItemOffset = int64(len(res.List)) - 1
}
for i := firstItemOffset - 1; i >= 0; i-- {
list = append(list, res.List[i])
}
}
res.List = list
}
var cursorValue SearchCursor
for _, item := range res.List {
cursorValue.Offset = item.Offset
data, _ := json.Marshal(cursorValue)
item.CursorValue = string(data)
}
return
}
// UserSearch 用户搜索
func (s *Service) UserSearch(c context.Context, mid int64, arg *v1.BaseSearchReq) (res *v1.UserSearchRes, err error) {
res = new(v1.UserSearchRes)
var (
sres []*model.UserSearchResult
searchRes *model.RawSearchRes
mids []int64
)
// 1. 生成请求,会根据请求参数
// v2接口
reqPage := int64(1)
firstItemOffset := int64(0)
var cursor *SearchCursor
var directionNext bool
if arg.Page == 0 {
cursor, directionNext, err = parseSearchCursor(c, arg.CursorPrev, arg.CursorNext)
if err != nil {
log.Warnw(c, "log", "parse search cursor fail", "prev", arg.CursorPrev, "next", arg.CursorNext)
return
}
firstItemOffset = cursor.Offset - 1
if directionNext {
firstItemOffset = cursor.Offset + 1
}
reqPage = (firstItemOffset-1)/searchLen + 1
log.V(1).Infow(c, "log", "v2 request", "next", arg.CursorNext, "prev", arg.CursorPrev)
} else {
reqPage = arg.Page
}
sp := &model.SearchBaseReq{
KeyWord: arg.Key,
Type: searchTypeUser,
Page: reqPage,
PageSize: searchLen,
Highlight: int64(arg.Highlight),
}
searchRes, err = s.dao.SearchBBQ(c, sp)
if searchRes.Res == nil {
return
}
if err = json.Unmarshal(searchRes.Res, &sres); err != nil {
return
}
for _, v := range sres {
mids = append(mids, v.ID)
}
if len(mids) == 0 {
log.Infov(c, log.KV("log", "search ret empty"))
return
}
uMap, _ := s.dao.BatchUserInfo(c, mid, mids, false, true, true)
for index, sinfo := range sres {
if u, ok := uMap[sinfo.ID]; ok {
r := &v1.UserSearchList{
UserInfo: *u,
}
st := new(v1.UserStatic)
st.Fan = u.Fan
st.Follow = u.Follow
st.Like = u.Like
st.Liked = u.Liked
st.FollowState = u.FollowState
r.UserStatic = st
r.HitColumns = sinfo.HitColumns
r.UnameHighlight = sinfo.Uname
r.Offset = (reqPage-1)*int64(searchLen) + int64(index) + 1
res.List = append(res.List, r)
}
}
res.NumPage = searchRes.PageNum
res.Page = searchRes.Page
res.HasMore = false
if arg.Page == 0 {
if (directionNext && searchRes.PageNum > searchRes.Page) || (!directionNext && searchRes.Page > 1) {
res.HasMore = true
}
var list []*v1.UserSearchList
if directionNext {
for _, item := range res.List {
if item.Offset < firstItemOffset {
continue
}
list = append(list, item)
}
} else {
if firstItemOffset >= int64(len(res.List)) {
firstItemOffset = int64(len(res.List)) - 1
}
for i := firstItemOffset - 1; i >= 0; i-- {
list = append(list, res.List[i])
}
}
res.List = list
}
var cursorValue SearchCursor
for _, item := range res.List {
cursorValue.Offset = item.Offset
data, _ := json.Marshal(cursorValue)
item.CursorValue = string(data)
}
return
}
// BBQSug BBQSUG服务
func (s *Service) BBQSug(c context.Context, arg *v1.SugReq) (res []*v1.SugTag, err error) {
var (
sres []*model.RawSugTag
data json.RawMessage
)
sp := &model.SugBaseReq{
Term: arg.KeyWord,
SuggestType: suggestType,
MainVer: suggestVer,
SugNum: arg.PageSize,
Highlight: int64(arg.Highlight),
}
data, err = s.dao.SugBBQ(c, sp)
if err != nil {
log.Error("s.dao.SugBBQ err[%v] data [%s]", err, string(data))
return
}
var json = jsoniter.ConfigCompatibleWithStandardLibrary
if err = json.Unmarshal(data, &sres); err != nil {
log.Error("json.Unmarshal err[%v] data [%s]", err, string(data))
return
}
for _, sinfo := range sres {
r := new(v1.SugTag)
name := sinfo.Name
r.Name = name
r.Value = sinfo.Value
r.Type = sinfo.Type
r.Ref = sinfo.Ref
res = append(res, r)
}
return
}

View File

@@ -0,0 +1,64 @@
package service
import (
"context"
"go-common/app/interface/bbq/app-bbq/conf"
"go-common/app/interface/bbq/app-bbq/dao"
"go-common/library/log"
topic "go-common/app/service/bbq/topic/api"
video_v1 "go-common/app/service/bbq/video/api/grpc/v1"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/rpc/warden"
)
// Service struct
type Service struct {
c *conf.Config
dao *dao.Dao
videoClient video_v1.VideoClient
topicClient topic.TopicClient
httpClient *bm.Client
}
// New init
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
videoClient: newVideoClient(c.GRPCClient["video"]),
httpClient: bm.NewClient(c.HTTPClient.Normal),
}
var err error
if s.topicClient, err = topic.NewClient(nil); err != nil {
log.Errorw(context.Background(), "log", "get topic client fail")
panic(err)
}
return s
}
// newVideoClient .
func newVideoClient(cfg *conf.GRPCConf) video_v1.VideoClient {
cc, err := warden.NewClient(cfg.WardenConf).Dial(context.Background(), cfg.Addr)
if err != nil {
panic(err)
}
return video_v1.NewVideoClient(cc)
}
// Ping Service
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
// Close Service
func (s *Service) Close() {
s.dao.Close()
}
func buvid(device *bm.Device) string {
// if device.RawMobiApp == "" {
// return device.Buvid3
// }
return device.Buvid
}

View File

@@ -0,0 +1,86 @@
package service
import (
"context"
"fmt"
"strconv"
"time"
v1 "go-common/app/interface/bbq/app-bbq/api/http/v1"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"github.com/Dai0522/go-hash/murmur3"
)
// GetShareURL .
func (s *Service) GetShareURL(ctx context.Context, mid int64, device *bm.Device, req *v1.ShareRequest) (*v1.ShareResponse, error) {
_, err := s.dao.VideoBase(ctx, mid, req.Svid)
if err != nil {
log.Warnw(ctx, "log", "get video base fail", "svid", req.Svid)
return nil, err
}
token := s.dao.GetUserShareToken(ctx, mid)
if token == "" {
hash := murmur3.NewWithSeed(uint32(time.Now().Unix()))
str := fmt.Sprintf("%d:%s", mid, buvid(device))
token = toHex(hash.Murmur3_128([]byte(str)))
s.dao.SetUserShareToken(ctx, mid, token)
}
var url, params []*v1.Tuple
params = append(params, &v1.Tuple{
Key: "mid",
Val: strconv.Itoa(int(mid)),
}, &v1.Tuple{
Key: "svid",
Val: strconv.Itoa(int(req.Svid)),
}, &v1.Tuple{
Key: "token",
Val: token,
})
url = append(url, &v1.Tuple{
Key: "1",
Val: "https://bbq.bilibili.com/video/?id={svid}&token={token}",
})
url = append(url, &v1.Tuple{
Key: "2",
Val: "https://bbq.bilibili.com/user/?id={mid}&token={token}",
})
return &v1.ShareResponse{
URL: url,
Params: params,
}, nil
}
func toHex(b []byte) string {
var res string
pattern := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "e", "f", "g"}
for _, v := range b {
res += pattern[v&15]
res += pattern[(v>>4)&15]
}
return res
}
// ShareCallback .
func (s *Service) ShareCallback(ctx context.Context, mid int64, device *bm.Device, args *v1.ShareCallbackRequest) (resp *v1.ShareCallbackResponse, err error) {
// 增加分享数
share := int32(0)
if args.Svid != int64(0) {
share, err = s.dao.IncrVideoStatisticsShare(ctx, args.Svid)
if err != nil {
log.Errorv(ctx, log.KV("log", err))
}
}
resp = &v1.ShareCallbackResponse{
ShareCount: share,
}
return
}

View File

@@ -0,0 +1,350 @@
package service
import (
"context"
"fmt"
"go-common/app/service/bbq/common"
"go-common/library/conf/env"
"go-common/library/ecode"
"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"
user "go-common/app/service/bbq/user/api"
video "go-common/app/service/bbq/video/api/grpc/v1"
"go-common/library/log"
"go-common/library/net/trace"
)
// SvList 短视屏推荐列表
func (s *Service) SvList(c context.Context, pageSize int64, mid int64, base *v1.Base, deviceID string) (res []*v1.VideoResponse, err error) {
var (
svids []int64
svRes map[int64]*v1.VideoResponse
)
res = make([]*v1.VideoResponse, 0)
//推荐列表
svids, err = s.dao.RawRecList(c, pageSize, mid, base.BUVID)
if err != nil || len(svids) == 0 || env.DeployEnv == env.DeployEnvUat {
log.Warnv(c, log.KV("log", fmt.Sprintf("s.dao.GetList err[%v]", err)))
// 降级
svids, _ = s.dao.GetList(c, pageSize)
}
svRes, err = s.svInfos(c, svids, mid, false)
for _, id := range svids {
if sv, ok := svRes[id]; ok {
if common.IsRecommendSvStateAvailable(int64(sv.State)) {
res = append(res, sv)
} else {
log.Warnw(c, "log", "get error svid in recommend list", "svid", id, "mid", mid, "sv", sv)
}
}
}
return
}
// svPlays 批量获取playurl(相对地址方法)
func (s *Service) svPlays(c context.Context, svids []int64) map[int64]*v1.VideoPlay {
var (
relAddr []string
err error
bvcUrls map[string]*grpc.VideoKeyItem
bvcKeys map[int64][]*model.SVBvcKey
)
playMap := make(map[int64]*v1.VideoPlay)
bvcKeys, err = s.dao.RawSVBvcKey(c, svids)
if err != nil {
log.Error("s.dao.RawSVBvcKey err[%v]", err)
}
for id, keys := range bvcKeys {
playMap[id] = &v1.VideoPlay{
SVID: id,
}
for k, v := range keys {
if k == 0 {
playMap[id].Quality = int64(v.CodeRate)
}
fi := &v1.FileInfo{
TimeLength: v.Duration,
FileSize: v.FileSize,
Path: v.Path,
}
playMap[id].FileInfo = append(playMap[id].FileInfo, fi)
playMap[id].SupportQuality = append(playMap[id].SupportQuality, int64(v.CodeRate))
relAddr = append(relAddr, v.Path)
}
}
bvcUrls, err = s.dao.RelPlayURLs(c, relAddr)
if err != nil {
log.Error("s.dao.RelPlayURLs err[%v]", err)
}
//拼装playurl
for _, svid := range svids {
if play, ok := playMap[svid]; ok {
for fk, f := range play.FileInfo {
if urls, ok := bvcUrls[f.Path]; ok {
playMap[svid].ExpireTime = int64(urls.Etime)
playMap[svid].CurrentTime = time.Now().Unix()
for _, u := range urls.URL {
if playMap[svid].FileInfo[fk].URL == "" {
playMap[svid].FileInfo[fk].URL = u
if playMap[svid].URL == "" {
playMap[svid].URL = u
}
continue
}
if playMap[svid].FileInfo[fk].URLBc == "" {
playMap[svid].FileInfo[fk].URLBc = u
break
}
}
} else {
delete(playMap, svid)
break
}
}
playMap[svid] = play
}
}
return playMap
}
// SvStatistics 视频统计服务
func (s *Service) SvStatistics(c context.Context, mid int64, svids []int64) (res []*v1.SvStatRes, err error) {
var (
stMap map[int64]*model.SvStInfo
ulike map[int64]bool
upIDs []int64
)
svInfos, _ := s.dao.RawVideos(c, svids)
for _, sv := range svInfos {
upIDs = append(upIDs, sv.MID)
}
stMap, err = s.dao.RawVideoStatistic(c, svids)
if err != nil {
log.Error("s.dao.RawVideoStatistic err[%v]", err)
}
//点赞状态
ulike, err = s.dao.CheckUserLike(c, mid, svids)
if err != nil {
log.Error("s.dao.CheckUserLike err[%v]", err)
}
uflw, _ := s.dao.BatchUserInfo(c, mid, upIDs, false, false, true)
if err != nil {
log.Error("s.dao.IsFollow err[%v]", err)
}
for _, id := range svids {
rp := &v1.SvStatRes{}
rp.SVID = id
if st, ok := stMap[id]; ok {
rp.Like = st.Like
rp.Share = st.Share
rp.Play = st.Play
rp.Subtitles = st.Subtitles
rp.Reply = st.Reply
}
if l, ok := ulike[id]; ok {
rp.IsLike = l
}
if sv, ok := svInfos[id]; ok {
if f, ok2 := uflw[sv.MID]; ok2 {
rp.FollowState = f.FollowState
}
}
res = append(res, rp)
}
return
}
// SvCPlays 批量拉取playurl
func (s *Service) SvCPlays(c context.Context, svids []int64, mid int64) (res []*v1.VideoPlay, err error) {
res = make([]*v1.VideoPlay, 0)
//视频列表
svRes, err := s.dao.RawVideos(c, svids)
if err != nil {
log.Error("s.dao.RawVideos err[%v]", err)
return
}
avaliableSvids := make([]int64, 0)
for _, v := range svRes {
if (mid == 0 || v.MID != mid) && common.IsSvStateGuestAvailable(int64(v.State)) {
avaliableSvids = append(avaliableSvids, v.SVID)
} else if v.MID == mid && common.IsSvStateOwnerAvailable(int64(v.State)) {
avaliableSvids = append(avaliableSvids, v.SVID)
}
}
playMap := s.svPlays(c, avaliableSvids)
for _, svid := range avaliableSvids {
var play *v1.VideoPlay
if p, ok := playMap[svid]; !ok {
log.Warn("play不存在 svid[%d]", svid)
continue
} else {
play = p
}
res = append(res, play)
}
return
}
// SvDetail 单条sv的视频详情暂时只用于评论中转页
func (s *Service) SvDetail(c context.Context, svid int64, mid int64) (res *v1.VideoResponse, err error) {
_, err = s.dao.VideoBase(c, mid, svid)
if err != nil {
return
}
svInfos, err := s.svInfos(c, []int64{svid}, mid, true)
if err != nil {
return
}
if val, exists := svInfos[svid]; exists {
res = val
} else {
err = ecode.VideoUnExists
log.Infow(c, "log", "not sv info", "svid", svid)
}
return
}
// svInfos 批量获取视频信息
// @params allowState 可放出状态传空为app整体可露出状态
// @params needStInfo 是否需要视频统计数据
func (s *Service) svInfos(c context.Context, ids []int64, mid int64, needStInfo bool) (res map[int64]*v1.VideoResponse, err error) {
var (
mids []int64
svRes map[int64]*model.SvInfo
ulike map[int64]bool
stMap map[int64]*model.SvStInfo
)
res = make(map[int64]*v1.VideoResponse)
stMap = make(map[int64]*model.SvStInfo)
//视频列表
svRes, err = s.dao.RawVideos(c, ids)
if err != nil {
log.Error("s.dao.RawVideos err[%v]", err)
return
}
for _, v := range svRes {
mids = append(mids, v.MID)
}
if mid != 0 {
ulike, err = s.dao.CheckUserLike(c, mid, ids)
if err != nil {
log.Error("s.dao.CheckUserLike err[%v]", err)
}
}
// query id
tracer, _ := trace.FromContext(c)
queryID := fmt.Sprintf("%s", tracer)
//账号
var userMap map[int64]*user.UserBase
userMap, err = s.dao.JustGetUserBase(c, mids)
if err != nil {
log.Error("s.dao.UserBase err[%v]", err)
}
// play信息
playMap := s.svPlays(c, ids)
if needStInfo {
stMap, err = s.dao.RawVideoStatistic(c, ids)
if err != nil {
log.Error("s.dao.RawVideoStatistic err[%v]", err)
}
}
// extension信息
extensions, tmpErr := s.getExtension(c, ids)
if tmpErr != nil {
log.Warnw(c, "log", "get extension fail")
}
for _, v := range svRes {
if common.IsSvStateAvailable(int64(v.State)) {
sv := &v1.VideoResponse{}
if acc, ok := userMap[v.MID]; ok {
sv.UserInfo = *acc
}
if lk, ok := ulike[v.SVID]; ok {
sv.IsLike = lk
}
sv.SVID = v.SVID
sv.Title = v.Title
sv.Content = v.Content
sv.MID = v.MID
sv.Duration = v.Duration
sv.Pubtime = v.Pubtime
sv.Ctime = v.Ctime
sv.AVID = v.AVID
sv.CID = v.CID
sv.From = v.From
sv.CoverURL = v.CoverURL
sv.CoverHeight = v.CoverHeight
sv.CoverWidth = v.CoverWidth
sv.QueryID = queryID
sv.State = v.State
if play, ok := playMap[v.SVID]; ok {
sv.Play = *play
res[v.SVID] = sv
} else {
log.Warn("play不存在 svid[%d],此条记录直接舍弃", v.SVID)
}
if st, ok := stMap[v.SVID]; ok {
sv.SvStInfo = *st
}
if extension, exists := extensions[v.SVID]; exists {
sv.Extension = extension.Extension
}
res[v.SVID] = sv
}
}
return
}
// SvRelRec 相关推荐服务
func (s *Service) SvRelRec(c context.Context, data *v1.SvRelReq) (res map[string]interface{}, err error) {
res = make(map[string]interface{})
var svMap map[int64]*v1.VideoResponse
list := make([]*v1.VideoResponse, 0)
relReq := &rec.RecsysRequest{
SVID: data.SVID,
Offset: data.Offset,
Limit: data.Limit,
QueryID: data.QueryID,
App: data.APP,
AppVersion: data.APPVersion,
BUVID: data.BUVID,
MID: data.MID,
}
IDList, err := s.dao.RelRecList(c, relReq)
if err != nil {
err = nil
return
}
svMap, err = s.svInfos(c, IDList, data.MID, false)
if err != nil {
err = nil
return
}
for _, id := range IDList {
if sv, ok := svMap[id]; ok {
list = append(list, sv)
}
}
res["list"] = list
return
}
// SvDel 视频删除
func (s *Service) SvDel(c context.Context, in *video.VideoDeleteRequest) (interface{}, error) {
return s.dao.SvDel(c, in)
}

View File

@@ -0,0 +1,255 @@
package service
import (
"context"
"encoding/json"
"go-common/app/interface/bbq/app-bbq/api/http/v1"
topic "go-common/app/service/bbq/topic/api"
"go-common/library/ecode"
"go-common/library/log"
"net/http"
"net/url"
)
func (s *Service) getExtension(ctx context.Context, svids []int64) (res map[int64]*topic.VideoExtension, err error) {
res = make(map[int64]*topic.VideoExtension, len(svids))
// 0. check
// 1. get extension
req := &topic.ListExtensionReq{Svids: svids}
reply, err := s.topicClient.ListExtension(ctx, req)
if err != nil {
log.Warnw(ctx, "log", "get extension fail")
return
}
// 2. form extension
for _, extension := range reply.List {
res[extension.Svid] = extension
}
return
}
// TopicDetail 获取话题详情
func (s *Service) TopicDetail(ctx context.Context, mid int64, req *topic.TopicVideosReq) (res *v1.TopicDetail, err error) {
res = new(v1.TopicDetail)
// 0. check
if req.TopicId == 0 {
err = ecode.TopicReqParamErr
log.Warnw(ctx, "log", "topic id is 0")
return
}
// 1. 获取话题信息及话题内视频
topicDetails, err := s.topicClient.ListTopicVideos(ctx, req)
if err != nil {
log.Errorw(ctx, "log", "get list topic videos fail")
return
}
// 2. 获取视频详情
// 2.0 获取视频id
var svids []int64
topicVideoMap := make(map[int64]*topic.VideoItem)
for _, item := range topicDetails.List {
if _, exists := topicVideoMap[item.Svid]; !exists {
topicVideoMap[item.Svid] = item
svids = append(svids, item.Svid)
}
}
// 2.1 获取详情
svInfos, err := s.svInfos(ctx, svids, mid, false)
if err != nil {
log.Warnw(ctx, "log", "get sv infos fail", "svid", svids)
return
}
// 3. 组装回包
res.TopicInfo = topicDetails.TopicInfo
res.HasMore = topicDetails.HasMore
for _, item := range topicDetails.List {
var topicVideo *v1.TopicVideo
if svInfo, exists := svInfos[item.Svid]; !exists {
log.Errorw(ctx, "log", "cannot find topicVideo response in topic detail", "svid", item.Svid)
continue
} else {
topicVideo = new(v1.TopicVideo)
topicVideo.VideoResponse = svInfo
}
topicVideo.CursorValue = item.CursorValue
topicVideo.HotType = item.HotType
res.List = append(res.List, topicVideo)
}
return
}
func (s *Service) getDiscoveryData(ctx context.Context, uri string) (data []byte, err error) {
var ret struct {
Code int `json:"code"`
Msg string `json:"message"`
Data json.RawMessage `json:"data"`
}
req, err := s.httpClient.NewRequest(http.MethodGet, uri, "", url.Values{})
if err != nil {
log.Errorw(ctx, "log", "http.NewRequest error", "err", err)
return
}
if err = s.httpClient.Do(ctx, req, &ret); err != nil {
log.Errorw(ctx, "log", "client Do error", "err", err)
return
}
if ret.Code != 0 {
log.Errorw(ctx, "log", "return code error", "code", ret.Code)
return
}
data = ret.Data
return
}
func (s *Service) getHotWords(ctx context.Context) (list []string, err error) {
list = make([]string, 0, 1)
data, err := s.getDiscoveryData(ctx, "http://bbq-mng.bilibili.co/bbq/cms/hotword/api")
if err != nil {
log.Warnw(ctx, "log", "get discovery data fail")
return
}
var hotWordResponse struct {
OnshelfList []string `json:"onshelf_list"`
}
err = json.Unmarshal(data, &hotWordResponse)
if err != nil {
log.Errorw(ctx, "log", "unmarshal hot word response fail", "data", string(data))
return
}
list = hotWordResponse.OnshelfList
if list == nil {
list = make([]string, 0, 1)
}
return
}
func (s *Service) getBanner(ctx context.Context) (list []*v1.Banner, err error) {
list = make([]*v1.Banner, 0, 1)
data, err := s.getDiscoveryData(ctx, "http://bbq-mng.bilibili.co/bbq/cms/banner/api")
if err != nil {
log.Warnw(ctx, "log", "get discovery data fail")
return
}
type httpBanner struct {
Title string `json:"title"`
ImgUrl string `json:"img_url"`
JumpUrl string `json:"jump_url"`
}
var bannerResponse struct {
BannerList []*httpBanner `json:"banner_list"`
}
bannerResponse.BannerList = make([]*httpBanner, 0)
err = json.Unmarshal(data, &bannerResponse)
if err != nil {
log.Errorw(ctx, "log", "unmarshal hot word response fail", "data", string(data))
return
}
for _, item := range bannerResponse.BannerList {
banner := new(v1.Banner)
banner.Name = item.Title
banner.PIC = item.ImgUrl
banner.Scheme = item.JumpUrl
list = append(list, banner)
}
if list == nil {
list = make([]*v1.Banner, 0, 1)
}
return
}
// Discovery 发现页
func (s *Service) Discovery(ctx context.Context, mid int64, req *v1.DiscoveryReq) (res *v1.DiscoveryRes, err error) {
res = new(v1.DiscoveryRes)
res.BannerList = make([]*v1.Banner, 0, 10)
res.HotWords = make([]string, 0, 10)
res.TopicList = make([]*v1.TopicDetail, 0, 10)
res.HasMore = false
// check
// 1. 条件判断
if req.Page == 1 {
// 请求热词
if res.HotWords, err = s.getHotWords(ctx); err != nil {
log.Warnw(ctx, "log", "get hot words fail", "err", err)
}
// 请求banner
if res.BannerList, err = s.getBanner(ctx); err != nil {
log.Warnw(ctx, "log", "get banner fail", "err", err)
}
}
// 2. 请求话题详情
reply, err := s.topicClient.ListDiscoveryTopics(ctx, &topic.ListDiscoveryTopicReq{Page: req.Page})
if err != nil {
log.Errorw(ctx, "log", "get discovery topics list fail")
return
}
// 2.1 收集svid
var svids []int64
for _, topicDetail := range reply.List {
for _, videoItem := range topicDetail.List {
svids = append(svids, videoItem.Svid)
}
}
// 2.2 获取视频详情
svInfos, err := s.svInfos(ctx, svids, mid, false)
if err != nil {
log.Warnw(ctx, "log", "get sv infos fail", "svids", svids)
return
}
// 3. 组装回包
res.HasMore = reply.HasMore
for _, topicDetail := range reply.List {
newTopicDetail := new(v1.TopicDetail)
newTopicDetail.TopicInfo = topicDetail.TopicInfo
newTopicDetail.HasMore = topicDetail.HasMore
for _, videoItem := range topicDetail.List {
if val, exists := svInfos[videoItem.Svid]; exists {
topicVideo := &v1.TopicVideo{CursorValue: videoItem.CursorValue, VideoResponse: val}
newTopicDetail.List = append(newTopicDetail.List, topicVideo)
} else {
log.Warnw(ctx, "log", "get sv info fail", "svid", videoItem.Svid)
}
}
if len(newTopicDetail.List) == 0 {
log.Warnw(ctx, "log", "topic has nothing", "topic", topicDetail)
continue
}
res.TopicList = append(res.TopicList, newTopicDetail)
}
return
}
// TopicSearch 话题搜索
func (s *Service) TopicSearch(ctx context.Context, req *v1.TopicSearchReq) (res *v1.TopicSearchResponse, err error) {
res = new(v1.TopicSearchResponse)
if len(req.Keyword) == 0 {
var reply *topic.ListTopicsReply
reply, err = s.topicClient.ListTopics(ctx, &topic.ListTopicsReq{Page: 1})
if err != nil {
log.Warnw(ctx, "log", "get topic list fail", "err", err)
return
}
res.List = reply.List
res.HasMore = reply.HasMore
}
res.HasMore = false
return
}

View File

@@ -0,0 +1,130 @@
package service
import (
interface_video_v1 "go-common/app/interface/bbq/app-bbq/api/http/v1"
"go-common/app/interface/bbq/app-bbq/conf"
"go-common/app/interface/bbq/app-bbq/model"
service_video_v1 "go-common/app/service/bbq/video/api/grpc/v1"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"strconv"
"time"
)
const (
//FILESIZE .
FILESIZE = 2 * 1024 * 1024
)
//Upload ...
func (s *Service) Upload(c *bm.Context, mid int64, t int) (location string, err error) {
var (
fileName string
filePath string
file []byte
)
c.Request.ParseMultipartForm(32 << 20)
f, h, err := c.Request.FormFile("file")
//参数判断
if err != nil {
//log.Errorv(c, log.KV("event", "service/upload/http/FormFile"), log.KV("err", err))
err = ecode.FileNotExists
return
}
defer f.Close()
//文件大小
if h.Size > FILESIZE {
err = ecode.FileTooLarge
return
}
tmp := make([]byte, h.Size)
if _, err = f.Read(tmp); err != nil {
log.Errorv(c, log.KV("event", "service/upload/ioreader/read"), log.KV("err", err))
err = ecode.ServerErr
}
file = tmp
fileName = strconv.FormatInt(mid, 10) + "_" + strconv.FormatInt(time.Now().Unix(), 10)
filePath = "userface"
switch t {
case 1:
filePath = "home_img"
}
if location, err = s.dao.Upload(c, fileName, filePath, file); err != nil {
log.Errorv(c, log.KV("event", "service/upload/dao/upload"), log.KV("err", err))
err = ecode.ServerErr
}
return
}
//PreUpload ..
func (s *Service) PreUpload(c *bm.Context, req *interface_video_v1.PreUploadRequest, mid int64) (rep *service_video_v1.PreUploadResponse, err error) {
p := &service_video_v1.PreUploadRequest{
Title: req.Title,
Mid: mid,
From: model.FromBBQ,
FileExt: req.FileExt,
Entension: req.Extension,
}
if rep, err = s.videoClient.PreUpload(c, p); err != nil {
log.Errorw(c, "event", "s.videoClient.PreUpload err", "err", err)
return
}
return
}
//CallBack ..
func (s *Service) CallBack(c *bm.Context, req *interface_video_v1.CallBackRequest, mid int64) (rep struct{}, err error) {
p := &service_video_v1.CallBackRequest{
Svid: req.Svid,
Mid: mid,
}
if _, err = s.videoClient.CallBack(c, p); err != nil {
log.Errorw(c, "event", "s.videoClient.CallBack", "err", err)
return
}
s.dao.MergeUploadReq(c, req.URL, req.UploadID, req.Profile, req.Svid, req.Auth)
return
}
// VideoUploadCheck 创作中心白名单(估计用不到)
func (s *Service) VideoUploadCheck(c *bm.Context, mid int64) (response interface{}, err error) {
isAllow := true
msg := ""
for _, v := range conf.Filter.MidFilter.White {
if v == mid {
isAllow = true
}
}
for _, v := range conf.Filter.MidFilter.Black {
if v == mid {
isAllow = false
msg = "您不在邀请名单之内,请期待我们的邀请"
}
}
return &interface_video_v1.UploadCheckResponse{
Msg: msg,
IsAllow: isAllow,
}, nil
}
//HomeImg ..
func (s *Service) HomeImg(c *bm.Context, req *interface_video_v1.HomeImgRequest, mid int64) (rep struct{}, err error) {
p := &service_video_v1.HomeImgCreateRequest{
Svid: req.SVID,
Url: req.URL,
Width: req.Width,
Mid: mid,
Height: req.Height,
}
if _, err = s.videoClient.HomeImgCreate(c, p); err != nil {
return
}
return
}

View File

@@ -0,0 +1,268 @@
package service
import (
"context"
"go-common/app/interface/bbq/app-bbq/api/http/v1"
"go-common/app/interface/bbq/app-bbq/model"
user "go-common/app/service/bbq/user/api"
"go-common/library/ecode"
"go-common/library/log"
)
// UserBase 获取用户信息
func (s *Service) UserBase(c context.Context, mid int64) (res *v1.UserInfo, err error) {
res = new(v1.UserInfo)
var userInfos map[int64]*v1.UserInfo
userInfos, err = s.dao.BatchUserInfo(c, mid, []int64{mid}, true, false, false)
if err != nil {
log.Errorv(c, log.KV("event", "user_base failed"), log.KV("err", err))
err = ecode.GetUserBaseErr
return
}
userInfo, exists := userInfos[mid]
if exists && userInfo != nil {
res = userInfo
} else {
log.Errorv(c, log.KV("event", "UserBase empty"), log.KV("err", err))
err = ecode.GetUserBaseErr
}
return
}
//SpaceUserProfile ...
func (s *Service) SpaceUserProfile(c context.Context, mid int64, upMid int64) (res *v1.UserInfo, err error) {
if upMid == 0 {
err = ecode.UPMIDNotExists
return
}
res = &v1.UserInfo{}
userInfos, err := s.dao.BatchUserInfo(c, mid, []int64{upMid}, true, true, true)
if err != nil {
log.Errorv(c, log.KV("event", "batch_user_info"), log.KV("err", err))
return
}
res, exists := userInfos[upMid]
if !exists {
err = ecode.UPMIDNotExists
log.Errorv(c, log.KV("event", "user_info_not_found"), log.KV("up_mid", upMid), log.KV("err", err))
return
}
return
}
// UserEdit 完善用户信息
// 该请求需要保证请求的mid已经存在如果不存在该接口会返回失败
func (s *Service) UserEdit(c context.Context, userBase *user.UserBase) (res *v1.NumResponse, err error) {
res = new(v1.NumResponse)
if err = s.dao.EditUserBase(c, userBase); err != nil {
return
}
return
}
//AddUserLike 点赞
func (s *Service) AddUserLike(c context.Context, mid, svid int64) (res *v1.NumResponse, err error) {
res = new(v1.NumResponse)
var upMid, num int64
videoBase, err := s.dao.VideoBase(c, mid, svid)
if err != nil {
log.Warnw(c, "log", "get video base fail", "svid", svid)
return
}
upMid = videoBase.Mid
likeNum, err := s.dao.AddLike(c, mid, upMid, svid)
if err != nil {
log.Warnv(c, log.KV("log", "add like fail"))
return
}
if likeNum == 0 {
log.Infow(c, "log", "repeated like", "mid", mid, "svid", svid)
return
}
// TODO:考虑消息队列解耦等
tx, err := s.dao.BeginTran(c)
if err != nil {
log.Error("add user like begin tran err(%v) mid(%d) svid(%d)", err, mid, svid)
err = ecode.AddUserLikeErr
return
}
if num, err = s.dao.TxIncrVideoStatisticsLike(tx, svid); err != nil {
err = ecode.AddUserLikeErr
tx.Rollback()
return
}
if num == 0 {
log.Errorw(c, "log", "incr video statistics fail maybe due to the missing in video_statistics table", "svid", svid)
videoStatistics := &model.VideoStatistics{SVID: svid, Like: 1}
if num, err = s.dao.TxAddVideoStatistics(tx, videoStatistics); err != nil || num == 0 {
err = ecode.AddUserLikeErr
tx.Rollback()
return
}
res.Num = 1
}
tx.Commit()
return
}
//CancelUserLike 取消点赞
func (s *Service) CancelUserLike(c context.Context, mid, svid int64) (res *v1.NumResponse, err error) {
res = new(v1.NumResponse)
var upMid, num int64
videoBase, err := s.dao.VideoBase(c, mid, svid)
if ecode.IsCancelSvLikeAvailable(err) {
err = nil
log.Infow(c, "log", "allow cancel like when video unreachable", "svid", svid, "mid", mid)
} else if err != nil {
log.Warnw(c, "log", "get video base fail", "svid", svid)
return
}
upMid = videoBase.Mid
likeNum, err := s.dao.CancelLike(c, mid, upMid, svid)
if err != nil {
log.Warnv(c, log.KV("log", "cancel like fail"))
return
}
if likeNum == 0 {
log.Infow(c, "log", "repeated cancel like", "mid", mid, "svid", svid)
return
}
tx, err := s.dao.BeginTran(c)
if err != nil {
log.Error("cancel user like begin tran err(%v) mid(%d) svid(%d)", err, mid, svid)
err = ecode.CancelUserLikeErr
return
}
if num, err = s.dao.TxDecrVideoStatisticsLike(tx, svid); err != nil {
err = ecode.CancelUserLikeErr
tx.Rollback()
return
}
if num == 0 {
log.Errorw(c, "log", "desc video statistics fail maybe due to the missing in video_statistics table", "svid", svid)
videoStatistics := &model.VideoStatistics{SVID: svid, Like: 0}
if num, err = s.dao.TxAddVideoStatistics(tx, videoStatistics); err != nil || num == 0 {
err = ecode.AddUserLikeErr
tx.Rollback()
return
}
res.Num = 1
}
tx.Commit()
return
}
// UserLikeList 用户点赞列表
// TODO: 这段代码和SpaceSvList基本一样后面抽出共同代码
func (s *Service) UserLikeList(c context.Context, req *v1.SpaceSvListRequest) (res v1.SpaceSvListResponse, err error) {
// 0.前期校验
// 这里就不校验up主是否存在
upMid := req.UpMid
if upMid == 0 {
err = ecode.ReqParamErr
log.Errorv(c, log.KV("event", "req_param"), log.KV("up_mid", 0))
return
}
res.List = make([]*v1.SvDetail, 0)
// 1. 获取svid列表
likeReply, err := s.dao.UserLikeList(c, upMid, req.CursorPrev, req.CursorNext)
if err != nil {
log.Errorv(c, log.KV("event", "user_like_list"), log.KV("error", err))
return
}
res.HasMore = likeReply.HasMore
var svids []int64
// svid, LikeSv
likeSvs := make(map[int64]*user.LikeSv)
for _, likeSv := range likeReply.List {
svids = append(svids, likeSv.Svid)
likeSvs[likeSv.Svid] = likeSv
}
// 2.获取sv详情
detailMap, err := s.getVideoDetail(c, req.MID, req.Qn, req.Device, svids, true)
if err != nil {
log.Warnv(c, log.KV("event", "get video detail fail"))
return
}
for _, svID := range svids {
item, exists := detailMap[svID]
if !exists {
item = new(v1.SvDetail)
item.SVID = svID
// TODO: 操蛋的客户端,这里等版本收敛之后可以删除
item.IsLike = true
item.Play.SVID = svID
item.Play.FileInfo = []*v1.FileInfo{
{},
}
item.Play.SupportQuality = make([]int64, 0)
}
if likeSv, exists := likeSvs[svID]; exists {
item.CursorValue = likeSv.CursorValue
}
res.List = append(res.List, item)
}
return
}
// ModifyRelation 关注、取关、拉黑、取消拉黑
func (s *Service) ModifyRelation(c context.Context, mid, upMid int64, action int32) (res *user.ModifyRelationReply, err error) {
res, err = s.dao.ModifyRelation(c, mid, upMid, action)
if err != nil {
log.Warnv(c, log.KV("log", "modify reltaion fail"))
}
return
}
// UserFollowList 关注列表
// 注意这里出现3种midvisitor/host/host关注的mid按顺序缩写为V、H、HF其中根据H获取HF然后计算V和HF的关注关系
func (s *Service) UserFollowList(c context.Context, req *user.ListRelationUserInfoReq) (res *v1.UserRelationListResponse, err error) {
res, err = s.dao.UserRelationList(c, req, user.Follow)
return
}
// UserFanList 粉丝列表
func (s *Service) UserFanList(c context.Context, req *user.ListRelationUserInfoReq) (res *v1.UserRelationListResponse, err error) {
res, err = s.dao.UserRelationList(c, req, user.Fan)
return
}
// UserBlackList 黑名单列表
func (s *Service) UserBlackList(c context.Context, req *user.ListRelationUserInfoReq) (res *v1.UserRelationListResponse, err error) {
// 强制只能访问自己的拉黑名单
req.UpMid = req.Mid
res, err = s.dao.UserRelationList(c, req, user.Black)
return
}
//Login .
func (s *Service) Login(c context.Context, req *user.UserBase) (res *user.UserBase, err error) {
res, err = s.dao.Login(c, req)
return
}
//PhoneCheck ..
func (s *Service) PhoneCheck(c context.Context, mid int64) (err error) {
telStatus, err := s.dao.PhoneCheck(c, mid)
if err != nil {
return
}
if telStatus == 0 {
err = ecode.BBQNoBindPhone
return
}
return
}