Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"favorite_test.go",
"service_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/playlist/conf:go_default_library",
"//app/interface/main/playlist/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"favorite.go",
"service.go",
"stat.go",
"video.go",
],
importpath = "go-common/app/interface/main/playlist/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/playlist/conf:go_default_library",
"//app/interface/main/playlist/dao:go_default_library",
"//app/interface/main/playlist/model:go_default_library",
"//app/service/main/account/api:go_default_library",
"//app/service/main/account/rpc/client:go_default_library",
"//app/service/main/archive/api:go_default_library",
"//app/service/main/archive/api/gorpc:go_default_library",
"//app/service/main/archive/model/archive:go_default_library",
"//app/service/main/favorite/api/gorpc:go_default_library",
"//app/service/main/favorite/model:go_default_library",
"//app/service/main/filter/rpc/client:go_default_library",
"//library/cache:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/time:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,320 @@
package service
import (
"context"
"sort"
"strconv"
"time"
"go-common/app/interface/main/playlist/dao"
"go-common/app/interface/main/playlist/model"
accwarden "go-common/app/service/main/account/api"
arcmdl "go-common/app/service/main/archive/api"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
xtime "go-common/library/time"
)
const (
_first = 1
_sortDefault = 0
_sortByMTime = 1
_sortByView = 2
)
var _empPlaylists = make([]*model.Playlist, 0)
// White playlist white list.
func (s *Service) White(c context.Context, mid int64) (res map[string]bool, err error) {
_, power := s.allowMids[mid]
res = make(map[string]bool, 1)
res["power"] = power
return
}
// Add add playlist.
func (s *Service) Add(c context.Context, mid int64, public int8, name, description, cover, cookie, accessKey string) (pid int64, err error) {
var (
fid int64
ts = time.Now()
ip = metadata.String(c, metadata.RemoteIP)
)
if _, ok := s.allowMids[mid]; !ok {
err = ecode.PlDenied
return
}
arg := &favmdl.ArgAddFolder{Type: favmdl.TypePlayVideo, Mid: mid, Name: name, Description: description, Cover: cover, Public: public, Cookie: cookie, AccessKey: accessKey, RealIP: ip}
if fid, err = s.fav.AddFolder(c, arg); err != nil {
dao.PromError("添加播单rpc错误", "s.fav.AddFolder(%v) error(%v)", arg, err)
return
}
if pid, err = s.dao.Add(c, mid, fid); err != nil {
log.Error("s.dao.Add(%d,%d) error(%v)", mid, fid, err)
} else if pid > 0 {
s.cache.Save(func() {
stat := &model.PlStat{ID: pid, Mid: mid, Fid: fid, MTime: xtime.Time(ts.Unix())}
s.dao.SetPlStatCache(context.Background(), mid, pid, stat)
})
if err = s.dao.RegReply(c, pid, mid); err != nil {
err = nil
}
}
return
}
// Del delete playlist.
func (s *Service) Del(c context.Context, mid, pid int64) (err error) {
var (
affected int64
stat *model.PlStat
ip = metadata.String(c, metadata.RemoteIP)
)
if stat, err = s.plByPid(c, pid); err != nil {
log.Error("s.plByPid(%d,%d) error(%v)", mid, pid, err)
return
}
arg := &favmdl.ArgDelFolder{Type: favmdl.TypePlayVideo, Mid: mid, Fid: stat.Fid, RealIP: ip}
if err = s.fav.DelFolder(c, arg); err != nil {
dao.PromError("删除播单rpc错误", "s.fav.DelFolder(%+v) error(%v)", arg, err)
return
}
if affected, err = s.dao.Del(c, pid); err != nil {
log.Error("s.dao.Del(%d) error(%v)", pid, err)
return
} else if affected > 0 {
s.dao.DelPlCache(c, mid, pid)
}
return
}
// Update update playlist.
func (s *Service) Update(c context.Context, mid, pid int64, public int8, name, description, cover, cookie, accessKey string) (err error) {
var (
stat *model.PlStat
ip = metadata.String(c, metadata.RemoteIP)
)
if stat, err = s.plByPid(c, pid); err != nil {
log.Error("s.plByPid(%d) error(%v)", pid, err)
return
}
arg := &favmdl.ArgUpdateFolder{Type: favmdl.TypePlayVideo, Fid: stat.Fid, Mid: mid, Name: name, Description: description, Cover: cover, Public: public, Cookie: cookie, AccessKey: accessKey, RealIP: ip}
if err = s.fav.UpdateFolder(c, arg); err != nil {
dao.PromError("更新播单rpc错误", "s.fav.UpdateFolder(%+v) error(%v)", arg, err)
return
}
s.updatePlTime(c, mid, pid)
return
}
func (s *Service) updatePlTime(c context.Context, mid, pid int64) (err error) {
var (
affected int64
ts = time.Now()
stat *model.PlStat
)
if affected, err = s.dao.Update(c, pid); err != nil {
err = nil
log.Error("s.dao.Update(%d) error(%v)", pid, err)
return
} else if affected > 0 {
s.cache.Save(func() {
if stat, err = s.plByPid(context.Background(), pid); err != nil {
err = nil
} else {
stat.MTime = xtime.Time(ts.Unix())
s.dao.SetPlStatCache(context.Background(), mid, pid, stat)
}
})
}
return
}
// Info playlist stat info.
func (s *Service) Info(c context.Context, mid, pid int64) (res *model.Playlist, err error) {
var (
fav *favmdl.Folder
stat *model.PlStat
infoReply *accwarden.InfoReply
isFav bool
ip = metadata.String(c, metadata.RemoteIP)
)
if stat, err = s.plByPid(c, pid); err != nil {
return
}
if stat == nil || stat.ID == 0 {
err = ecode.PlNotExist
dao.PromError("Info:播单不存在", "s.plByPid(%d) error(%v)", pid, stat)
return
}
arg := &favmdl.ArgFolder{Type: favmdl.TypePlayVideo, Fid: stat.Fid, Mid: stat.Mid, RealIP: ip}
if fav, err = s.fav.Folder(c, arg); err != nil || fav == nil {
dao.PromError("Info Forder:rpc错误", "s.fav.Folder(%+v) error(%v)", arg, err)
return
}
if fav.State == favmdl.StateIsDel {
err = ecode.PlNotExist
dao.PromError("InfoFav:播单不存在", "s.fav.Folder(%d) error(%v)", pid, err)
return
}
// author
if infoReply, err = s.accClient.Info3(c, &accwarden.MidReq{Mid: fav.Mid, RealIp: ip}); err != nil {
dao.PromError("账号Info:grpc错误", "s.accClient.Info3 error(%v)", err)
return
}
if mid > 0 {
if isFav, err = s.fav.IsFav(c, &favmdl.ArgIsFav{Type: favmdl.TypePlayList, Mid: mid, Oid: pid, RealIP: ip}); err != nil {
log.Error("s.fav.IsFav(%d,%d) error(%d)", mid, pid, err)
err = nil
}
}
owner := &arcmdl.Author{Mid: fav.Mid, Name: infoReply.Info.Name, Face: infoReply.Info.Face}
fav.MTime = stat.MTime
res = &model.Playlist{Pid: pid, Folder: fav, Stat: &model.Stat{Pid: stat.ID, View: stat.View, Reply: stat.Reply, Fav: stat.Fav, Share: stat.Share}, Author: owner, IsFavorite: isFav}
return
}
func (s *Service) plInfo(c context.Context, mid, pid int64, ip string) (res *model.PlStat, err error) {
var fav *favmdl.Folder
if res, err = s.plByPid(c, pid); err != nil {
return
}
if res == nil || res.ID == 0 {
err = ecode.PlNotExist
log.Error("s.plByPid(%d) res(%v)", pid, res)
return
}
arg := &favmdl.ArgFolder{Type: favmdl.TypePlayVideo, Fid: res.Fid, Mid: res.Mid, RealIP: ip}
if fav, err = s.fav.Folder(c, arg); err != nil || fav == nil {
log.Error("s.fav.Folder(%+v) error(%v)", arg, err)
return
}
if fav.State == favmdl.StateIsDel {
err = ecode.PlNotExist
log.Error("s.fav.Folder(%d) state(%d)", pid, fav.State)
return
}
if mid > 0 && mid != res.Mid {
err = ecode.PlNotUser
}
return
}
// List playlist.
func (s *Service) List(c context.Context, mid int64, pn, ps, sortType int) (res []*model.Playlist, count int, err error) {
var (
start = (pn - 1) * ps
end = start + ps - 1
plStats []*model.PlStat
ip = metadata.String(c, metadata.RemoteIP)
)
if plStats, err = s.plsByMid(c, mid); err != nil {
return
}
count = len(plStats)
if count == 0 || count < start {
res = _empPlaylists
return
}
switch sortType {
case _sortDefault, _sortByMTime:
sort.Slice(plStats, func(i, j int) bool { return plStats[i].MTime > plStats[j].MTime })
case _sortByView:
sort.Slice(plStats, func(i, j int) bool { return plStats[i].View > plStats[j].View })
}
if count > end {
plStats = plStats[start : end+1]
} else {
plStats = plStats[start:]
}
res, err = s.batchFav(c, mid, plStats, ip)
return
}
//AddFavorite add playlist to favorite.
func (s *Service) AddFavorite(c context.Context, mid, pid int64) (err error) {
ip := metadata.String(c, metadata.RemoteIP)
if _, err = s.Info(c, 0, pid); err != nil {
return
}
arg := &favmdl.ArgAdd{Type: favmdl.TypePlayList, Mid: mid, Oid: pid, Fid: 0, RealIP: ip}
if err = s.fav.Add(c, arg); err != nil {
dao.PromError("rpc:添加播单收藏", "s.fav.Add(%+v) error(%v)", arg, err)
}
return
}
// DelFavorite del playlist from favorite.
func (s *Service) DelFavorite(c context.Context, mid, pid int64) (err error) {
ip := metadata.String(c, metadata.RemoteIP)
arg := &favmdl.ArgDel{Type: favmdl.TypePlayList, Mid: mid, Oid: pid, Fid: 0, RealIP: ip}
if err = s.fav.Del(c, arg); err != nil {
dao.PromError("rpc:删除播单收藏", "s.fav.Del(%+v) error(%v)", arg, err)
}
return
}
//ListFavorite playlist list.
func (s *Service) ListFavorite(c context.Context, mid, vmid int64, pn, ps, sortType int) (res []*model.Playlist, count int, err error) {
var (
plStats []*model.PlStat
favRes *favmdl.Favorites
pids []int64
tmpFavs map[int64]*favmdl.Favorite
tmpRs []*model.Playlist
ip = metadata.String(c, metadata.RemoteIP)
)
arg := &favmdl.ArgFavs{Type: favmdl.TypePlayList, Mid: mid, Vmid: vmid, Fid: 0, Pn: pn, Ps: ps, RealIP: ip}
if favRes, err = s.fav.Favorites(c, arg); err != nil {
dao.PromError("rpc:播单收藏列表", "s.fav.Favorites(%+v) error(%v)", arg, err)
return
}
if favRes == nil || len(favRes.List) == 0 {
res = _empPlaylists
return
}
tmpFavs = make(map[int64]*favmdl.Favorite)
for _, fav := range favRes.List {
pids = append(pids, fav.Oid)
tmpFavs[fav.Oid] = fav
}
if plStats, err = s.plsByPid(c, pids); err != nil {
return
}
count = favRes.Page.Count
tmpRs, err = s.batchFav(c, mid, plStats, ip)
for _, v := range tmpRs {
v.FavoriteTime = tmpFavs[v.Pid].MTime
res = append(res, v)
}
return
}
func (s *Service) batchFav(c context.Context, uid int64, plStats []*model.PlStat, ip string) (res []*model.Playlist, err error) {
var (
fVMids []*favmdl.ArgFVmid
tmpStats map[string]*model.PlStat
favRes []*favmdl.Folder
stat *model.Stat
)
tmpStats = make(map[string]*model.PlStat)
for _, v := range plStats {
statKey := strconv.FormatInt(v.Mid, 10) + "_" + strconv.FormatInt(v.Fid, 10)
tmpStats[statKey] = &model.PlStat{ID: v.ID, Mid: v.Mid, Fid: v.Fid, View: v.View, Reply: v.Reply, Fav: v.Fav, Share: v.Share, MTime: v.MTime}
fVMids = append(fVMids, &favmdl.ArgFVmid{Fid: v.Fid, Vmid: v.Mid})
}
arg := &favmdl.ArgFolders{Type: favmdl.TypePlayVideo, Mid: uid, FVmids: fVMids, RealIP: ip}
if favRes, err = s.fav.Folders(c, arg); err != nil {
dao.PromError("rpc:批量获取播单列表", "s.fav.Folders(%+v) error(%v)", arg, err)
return
}
for _, fav := range favRes {
statKey := strconv.FormatInt(fav.Mid, 10) + "_" + strconv.FormatInt(fav.ID, 10)
plStat := tmpStats[statKey]
stat = &model.Stat{Pid: plStat.ID, View: plStat.View, Fav: plStat.Fav, Reply: plStat.Reply, Share: plStat.Share}
fav.MTime = plStat.MTime
res = append(res, &model.Playlist{Pid: plStat.ID, Folder: fav, Stat: stat})
}
return
}

View File

@@ -0,0 +1,148 @@
package service
import (
"context"
"testing"
"go-common/app/interface/main/playlist/conf"
"go-common/app/interface/main/playlist/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestService_Add(t *testing.T) {
var (
mid = int64(88888929)
plPublic = int8(0)
plName = "播单名称1"
plDescripton = "播单描述1"
cover = "http://image1.jpg"
)
Convey("Add", t, WithService(func(s *Service) {
res, err := s.Add(context.Background(), mid, plPublic, plName, plDescripton, cover, "", "")
So(err, ShouldBeNil)
So(res, ShouldBeGreaterThan, 0)
}))
}
func TestService_Del(t *testing.T) {
var (
mid = int64(88888929)
pid = int64(1)
)
Convey("Add", t, WithService(func(s *Service) {
err := s.Del(context.Background(), mid, pid)
So(err, ShouldBeNil)
}))
}
func TestService_Update(t *testing.T) {
var (
mid = int64(88888929)
pid = int64(1)
plPublic = int8(0)
plName = "播单名称1"
plDescripton = "播单描述1"
cover = "http://image1.jpg"
)
Convey("Add", t, WithService(func(s *Service) {
err := s.Update(context.Background(), mid, pid, plPublic, plName, plDescripton, cover, "", "")
So(err, ShouldBeNil)
}))
}
func TestService_AddVideo(t *testing.T) {
var (
mid = int64(88888929)
pid = int64(1)
aids = []int64{11, 22, 33, 44, 55}
)
Convey("Add", t, WithService(func(s *Service) {
videos, err := s.AddVideo(context.Background(), mid, pid, aids)
So(videos, ShouldNotBeNil)
So(err, ShouldBeNil)
}))
}
func TestService_DelVideo(t *testing.T) {
var (
mid = int64(88888929)
pid = int64(1)
aids = []int64{11, 22, 33, 44, 55}
)
Convey("Add", t, WithService(func(s *Service) {
err := s.DelVideo(context.Background(), mid, pid, aids)
So(err, ShouldBeNil)
}))
}
func TestService_SortVideo(t *testing.T) {
Convey("sort", t, func() {
var (
arcs []*model.ArcSort
aidSort, preSort, afSort, orderNum int64
start, end int
top, bottom bool
)
aid := int64(6)
sort := int64(1)
arcs = []*model.ArcSort{
{Aid: 1, Sort: 100},
{Aid: 2, Sort: 200},
{Aid: 3, Sort: 300},
{Aid: 4, Sort: 400},
{Aid: 5, Sort: 500},
{Aid: 6, Sort: 600},
}
if sort == _first {
top = true
} else if sort == int64(len(arcs)) {
bottom = true
}
for k, v := range arcs {
if k == 0 && top {
afSort = v.Sort
}
if k == len(arcs)-1 && bottom {
preSort = v.Sort
}
if aid == v.Aid {
if sort == int64(k+1) {
return
}
aidSort = v.Sort
}
if sort == int64(k+1) {
if !top && !bottom {
if aidSort > sort {
preSort = arcs[k].Sort
afSort = arcs[k+1].Sort
} else {
preSort = arcs[k-1].Sort
afSort = arcs[k].Sort
}
}
}
}
if top {
println("top")
orderNum = afSort / 2
} else if bottom {
println("bottom")
orderNum = preSort + int64(conf.Conf.Rule.SortStep)
} else {
println("else")
orderNum = preSort + (afSort-preSort)/2
}
println(start, end, preSort, afSort)
for _, v := range arcs {
if v.Aid == aid {
v.Sort = orderNum
}
}
for _, v := range arcs {
Printf("%+v", v)
}
})
}

View File

@@ -0,0 +1,75 @@
package service
import (
"context"
"go-common/app/interface/main/playlist/conf"
"go-common/app/interface/main/playlist/dao"
accclient "go-common/app/service/main/account/api"
accwarden "go-common/app/service/main/account/api"
accrpc "go-common/app/service/main/account/rpc/client"
arcrpc "go-common/app/service/main/archive/api/gorpc"
arcclient "go-common/app/service/main/archive/api"
favrpc "go-common/app/service/main/favorite/api/gorpc"
"go-common/app/service/main/filter/rpc/client"
"go-common/library/cache"
"go-common/library/log"
)
// Service service struct.
type Service struct {
c *conf.Config
dao *dao.Dao
// rpc
fav *favrpc.Service
arc *arcrpc.Service2
acc *accrpc.Service3
filter *filter.Service
// cache proc
cache *cache.Cache
// playlist power mids
allowMids map[int64]struct{}
maxSort int64
arcClient arcclient.ArchiveClient
accClient accwarden.AccountClient
}
// New new service.
func New(c *conf.Config) *Service {
s := &Service{
c: c,
dao: dao.New(c),
fav: favrpc.New2(c.FavoriteRPC),
arc: arcrpc.New2(c.ArchiveRPC),
acc: accrpc.New3(c.AccountRPC),
filter: filter.New(c.FilterRPC),
cache: cache.New(1, 1024),
maxSort: c.Rule.MinSort + 4*c.Rule.SortStep*int64(c.Rule.MaxVideoCnt),
}
var err error
if s.arcClient, err = arcclient.NewClient(c.ArcClient); err != nil {
panic(err)
}
if s.accClient, err = accclient.NewClient(c.AccClient); err != nil {
panic(err)
}
s.initMids()
return s
}
func (s *Service) initMids() {
tmp := make(map[int64]struct{}, len(s.c.Rule.PowerMids))
for _, id := range s.c.Rule.PowerMids {
tmp[id] = struct{}{}
}
s.allowMids = tmp
}
// Ping ping service.
func (s *Service) Ping(c context.Context) (err error) {
if err = s.dao.Ping(c); err != nil {
log.Error("s.dao.Ping error(%v)", err)
}
return
}

View File

@@ -0,0 +1,28 @@
package service
import (
"flag"
"path/filepath"
"time"
"go-common/app/interface/main/playlist/conf"
)
var (
svr *Service
)
func init() {
dir, _ := filepath.Abs("../cmd/playlist-test.toml")
flag.Set("conf", dir)
conf.Init()
svr = New(conf.Conf)
time.Sleep(time.Second)
}
func WithService(f func(s *Service)) func() {
return func() {
f(svr)
}
}

View File

@@ -0,0 +1,118 @@
package service
import (
"context"
"go-common/app/interface/main/playlist/model"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/log"
"go-common/library/net/metadata"
)
func (s *Service) plsByMid(c context.Context, mid int64) (res []*model.PlStat, err error) {
if res, err = s.dao.StatsCache(c, mid); err != nil || len(res) == 0 {
err = nil
if res, err = s.dao.PlsByMid(c, mid); err != nil {
log.Error("s.dao.PlsByMid(%d) error(%v)", mid, err)
return
}
if len(res) > 0 {
s.cache.Save(func() {
s.dao.SetStatsCache(context.Background(), mid, res)
})
}
}
return
}
func (s *Service) plByPid(c context.Context, pid int64) (res *model.PlStat, err error) {
var pls []*model.PlStat
pids := []int64{pid}
if pls, err = s.dao.PlsCache(c, pids); err != nil {
err = nil
} else if len(pls) != 0 {
res = pls[0]
return
}
if res, err = s.dao.PlByPid(c, pid); err != nil {
log.Error("s.dao.PlByPid(%d) error(%v)", pid, err)
}
return
}
func (s *Service) plsByPid(c context.Context, pids []int64) (res []*model.PlStat, err error) {
var (
tmpRs []*model.PlStat
rsMap map[int64]*model.PlStat
)
if tmpRs, err = s.dao.PlsCache(c, pids); err != nil || len(pids) != len(tmpRs) || len(tmpRs) == 0 {
err = nil
if tmpRs, err = s.dao.PlsByPid(c, pids); err != nil {
log.Error("s.dao.PlsByPid(%+v) error(%v)", pids, err)
}
if len(tmpRs) > 0 {
s.cache.Save(func() {
s.dao.SetPlCache(context.Background(), tmpRs)
})
}
}
rsMap = make(map[int64]*model.PlStat)
for _, v := range tmpRs {
rsMap[v.ID] = v
}
for _, v := range pids {
if rsMap[v] == nil {
continue
}
res = append(res, rsMap[v])
}
return
}
// SetStat set playlist stat cache.
func (s *Service) SetStat(c context.Context, arg *model.PlStat) (err error) {
var fav *favmdl.Folder
argFav := &favmdl.ArgFolder{Type: favmdl.TypePlayVideo, Fid: arg.Fid, Mid: arg.Mid, RealIP: ""}
if fav, err = s.fav.Folder(c, argFav); err != nil || fav == nil {
log.Error("SetStat s.fav.Folder(%+v) error(%v)", argFav, err)
return
}
log.Info("service SetStat(%v) favState(%d)", arg, fav.State)
if fav.State == favmdl.StateNormal {
if err = s.dao.SetPlStatCache(c, arg.Mid, arg.ID, arg); err != nil {
log.Error("SetStat s.dao.SetPlStatCache(%d,%d) error(%v)", arg.Mid, arg.ID, err)
}
}
return
}
// PubView pub playlist view.
func (s *Service) PubView(c context.Context, pid, aid int64) (err error) {
var (
pls *model.PlStat
ip = metadata.String(c, metadata.RemoteIP)
)
if pls, err = s.plInfo(c, 0, pid, ip); err != nil {
return
}
//TODO aid in playlist
err = s.cache.Save(func() {
s.dao.PubView(context.Background(), pid, aid, pls.View)
})
return
}
// PubShare pub playlist share.
func (s *Service) PubShare(c context.Context, pid, aid int64) (err error) {
var (
pls *model.PlStat
ip = metadata.String(c, metadata.RemoteIP)
)
if pls, err = s.plInfo(c, 0, pid, ip); err != nil {
return
}
//TODO aid in playlist
err = s.cache.Save(func() {
s.dao.PubShare(context.Background(), pid, aid, pls.Share)
})
return
}

View File

@@ -0,0 +1,529 @@
package service
import (
"context"
"html/template"
"sync"
"go-common/app/interface/main/playlist/conf"
"go-common/app/interface/main/playlist/dao"
"go-common/app/interface/main/playlist/model"
arcmdl "go-common/app/service/main/archive/api"
"go-common/app/service/main/archive/model/archive"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/sync/errgroup"
)
const (
_vUpload = "vupload"
_aidBulkSize = 50
)
var _empAids = make([]int64, 0)
// Videos get playlist video list by pid.
func (s *Service) Videos(c context.Context, pid int64, pn, ps int) (res *model.ArcList, err error) {
var (
aids []int64
arcSorts []*model.ArcSort
arcs map[int64]*arcmdl.ViewReply
stat *model.PlStat
ip = metadata.String(c, metadata.RemoteIP)
)
res = &model.ArcList{List: make([]*model.PlView, 0)}
if stat, err = s.plByPid(c, pid); err != nil || stat == nil {
return
}
if _, err = s.fav.Folder(c, &favmdl.ArgFolder{Type: favmdl.TypePlayVideo, Mid: stat.Mid, Fid: stat.Fid, RealIP: ip}); err != nil {
dao.PromError("Folder接口错误", "s.fav.Folder(%d,%d) error(%v)", stat.Mid, stat.Fid, err)
return
}
start := (pn - 1) * ps
end := start + ps - 1
if arcSorts, err = s.videos(c, pid, start, end); err != nil {
return
}
//TODO check aids from fav
for _, v := range arcSorts {
aids = append(aids, v.Aid)
}
if arcs, err = s.views(c, aids, ip); err != nil {
log.Error("s.arc.Views3(%v) error(%v)", aids, err)
return
}
for _, v := range arcSorts {
if arc, ok := arcs[v.Aid]; ok {
view := &model.PlView{View: &model.View{Arc: arc.Arc, Pages: arc.Pages}, PlayDesc: template.HTMLEscapeString(v.Desc)}
res.List = append(res.List, view)
}
}
return
}
// ToView get playlist view page data.
func (s *Service) ToView(c context.Context, mid, pid int64) (res *model.ToView, err error) {
var (
aids []int64
arcSorts []*model.ArcSort
views map[int64]*arcmdl.ViewReply
info *model.Playlist
ip = metadata.String(c, metadata.RemoteIP)
)
if info, err = s.Info(c, mid, pid); err != nil {
return
}
res = &model.ToView{Playlist: info}
if arcSorts, err = s.videos(c, pid, 0, info.Count-1); err != nil {
return
}
//TODO check aids from fav
for _, v := range arcSorts {
aids = append(aids, v.Aid)
}
if views, err = s.views(c, aids, ip); err != nil {
log.Error("s.views(%v) error(%v)", aids, err)
return
}
res.List = make([]*model.View, 0)
for _, v := range arcSorts {
if arc, ok := views[v.Aid]; ok {
view := &model.View{Arc: arc.Arc, Pages: arc.Pages}
res.List = append(res.List, view)
}
}
return
}
// CheckVideo add video to playlist.
func (s *Service) CheckVideo(c context.Context, mid, pid int64, aids []int64) (videos model.Videos, err error) {
var (
stat *model.PlStat
fav *favmdl.Folder
ip = metadata.String(c, metadata.RemoteIP)
)
if stat, err = s.plByPid(c, pid); err != nil {
return
}
if stat == nil {
err = ecode.PlNotExist
dao.PromError("CheckVideo:播单不存在", "s.fav.Folder(%d) error(%v)", pid, err)
return
}
argFolder := &favmdl.ArgFolder{Type: favmdl.TypePlayVideo, Fid: stat.Fid, Mid: stat.Mid, Vmid: 0, RealIP: ip}
if fav, err = s.fav.Folder(c, argFolder); err != nil || fav == nil {
dao.PromError("CheckVideo收藏Forder:rpc错误", "s.fav.Folder(%+v) error(%v)", argFolder, err)
return
}
if videos, _, _, err = s.filterArc(c, mid, pid, aids, ip); err != nil {
log.Error("s.filterArc(%v) error(%v)", aids, err)
}
return
}
// AddVideo add video to playlist.
func (s *Service) AddVideo(c context.Context, mid, pid int64, aids []int64) (videos model.Videos, err error) {
var (
lastID, sort, fid int64
arcSorts []*model.ArcSort
ip = metadata.String(c, metadata.RemoteIP)
)
if videos, sort, fid, err = s.filterArc(c, mid, pid, aids, ip); err != nil {
log.Error("s.filterArc(%v) error(%v)", aids, err)
return
}
if len(videos.RightAids) == 0 {
return
}
for _, aid := range videos.RightAids {
sort += conf.Conf.Rule.SortStep
arcSorts = append(arcSorts, &model.ArcSort{Aid: aid, Sort: sort, Desc: ""})
}
arg := &favmdl.ArgMultiAdd{Type: favmdl.TypePlayVideo, Mid: mid, Oids: videos.RightAids, Fid: fid, RealIP: ip}
if err = s.fav.MultiAdd(c, arg); err != nil {
dao.PromError("添加播单视频rpc错误", "s.fav.MultiAdd(%+v) error(%v)", arg, err)
return
}
if lastID, err = s.dao.BatchAddArc(c, pid, arcSorts); err != nil || lastID == 0 {
log.Error("s.dao.BatchAddArc(%d,%+v) error(%v)", pid, arcSorts, err)
return
}
if lastID > 0 {
s.cache.Save(func() {
s.dao.SetArcsCache(context.Background(), pid, arcSorts)
})
}
s.updatePlTime(c, mid, pid)
return
}
// DelVideo del video from playlist.
func (s *Service) DelVideo(c context.Context, mid, pid int64, aids []int64) (err error) {
var (
affected int64
stat *model.PlStat
ip = metadata.String(c, metadata.RemoteIP)
)
if stat, err = s.plByPid(c, pid); err != nil {
return
}
if stat == nil {
err = ecode.PlNotExist
dao.PromError("DelVideo:播单不存在", "s.fav.Folder(%d) error(%v)", pid, err)
return
}
arg := &favmdl.ArgMultiDel{Type: favmdl.TypePlayVideo, Mid: mid, Oids: aids, Fid: stat.Fid, RealIP: ip}
if err = s.fav.MultiDel(c, arg); err != nil {
dao.PromError("删除播单视频rpc错误", "s.fav.MultiDel(%+v) error(%v)", arg, err)
return
}
if affected, err = s.dao.BatchDelArc(c, pid, aids); err != nil || affected == 0 {
log.Error("s.dao.BatchDelArc(%d,%v) error(%v)", mid, aids, err)
return
}
if affected > 0 {
s.cache.Save(func() {
s.dao.DelArcsCache(context.Background(), pid, aids)
})
}
s.updatePlTime(c, mid, pid)
return
}
// SortVideo sort playlist video.
func (s *Service) SortVideo(c context.Context, mid, pid, aid, sort int64) (err error) {
var (
info *favmdl.Favorites
aidSort, preSort, afSort, orderNum, affected int64
desc string
arcs []*model.ArcSort
plStat *model.PlStat
top, bottom, isPlaylist, reset bool
ip = metadata.String(c, metadata.RemoteIP)
)
if plStat, err = s.plByPid(c, pid); err != nil {
return
}
if plStat == nil {
err = ecode.PlNotExist
dao.PromError("SortVideo:播单不存在", "s.fav.Folder(%d) error(%v)", pid, err)
return
}
if plStat.ID == 0 {
err = ecode.PlNotExist
return
} else if mid != plStat.Mid {
err = ecode.PlNotUser
return
}
if info, err = s.fav.Favorites(c, &favmdl.ArgFavs{Type: favmdl.TypePlayVideo, Mid: mid, Fid: plStat.Fid, Pn: 1, Ps: 1, RealIP: ip}); err != nil {
dao.PromError("获取播单信息rpc错误", "s.fav.Favorites(%d,%d) error(%v)", mid, plStat.Fid, err)
return
} else if sort > int64(info.Page.Count) {
err = ecode.PlSortOverflow
return
}
if isPlaylist, err = s.fav.IsFavedByFid(c, &favmdl.ArgIsFavedByFid{Type: favmdl.TypePlayVideo, Mid: mid, Fid: plStat.Fid, Oid: aid, RealIP: ip}); err != nil {
dao.PromError("播单下视频rpc错误", "s.fav.IsFavedByFid(%d,%d,%d) error(%v)", mid, plStat.Fid, aid, err)
return
} else if !isPlaylist {
err = ecode.PlVideoAlreadyDel
return
}
if arcs, err = s.videos(c, pid, 0, info.Page.Count-1); err != nil {
return
}
if sort == _first {
top = true
} else if sort == int64(info.Page.Count) {
bottom = true
}
for k, v := range arcs {
if k == 0 && top {
afSort = v.Sort
}
if k == len(arcs)-1 && bottom {
preSort = v.Sort
}
if aid == v.Aid {
if sort == int64(k+1) {
return
}
aidSort = v.Sort
desc = v.Desc
}
if sort == int64(k+1) {
if !top && !bottom {
if aidSort > sort {
preSort = arcs[k].Sort
afSort = arcs[k+1].Sort
} else {
preSort = arcs[k-1].Sort
afSort = arcs[k].Sort
}
}
}
}
if top {
orderNum = afSort / 2
} else if bottom {
orderNum = preSort + conf.Conf.Rule.SortStep
} else {
orderNum = preSort + (afSort-preSort)/2
}
if orderNum == preSort || orderNum == afSort || orderNum <= conf.Conf.Rule.MinSort || orderNum > s.maxSort {
reset = true
if affected, err = s.resetArcSort(c, pid); err != nil {
dao.PromError("重置视频排序错误", "s.dao.UpdateArcSort(%d,%d) error(%v)", pid, aid, err)
return
}
} else {
if affected, err = s.dao.UpdateArcSort(c, pid, aid, orderNum); err != nil {
dao.PromError("更新视频排序错误", "s.dao.UpdateArcSort(%d,%d) error(%v)", pid, aid, err)
return
}
}
if affected > 0 {
s.cache.Save(func() {
if reset {
if err = s.dao.DelCache(context.Background(), pid); err != nil {
log.Error("s.dao.DelCache() pid(%d), error(%v)", pid, err)
return
}
s.videos(context.Background(), pid, 0, info.Page.Count-1)
} else {
s.dao.AddArcCache(context.Background(), pid, &model.ArcSort{Aid: aid, Sort: orderNum, Desc: desc})
}
})
}
return
}
// EditVideoDesc edit playlist video desc.
func (s *Service) EditVideoDesc(c context.Context, mid, pid, aid int64, desc string) (err error) {
var (
affected int64
plStat *model.PlStat
isPlaylist bool
ip = metadata.String(c, metadata.RemoteIP)
)
if plStat, err = s.plByPid(c, pid); err != nil {
return
}
if plStat == nil {
err = ecode.PlNotExist
dao.PromError("AddVideo:播单不存在", "s.fav.Folder(%d) error(%v)", pid, err)
return
}
if plStat.ID == 0 {
err = ecode.PlNotExist
return
} else if mid != plStat.Mid {
err = ecode.PlNotUser
return
}
if isPlaylist, err = s.fav.IsFavedByFid(c, &favmdl.ArgIsFavedByFid{Type: favmdl.TypePlayVideo, Mid: mid, Fid: plStat.Fid, Oid: aid, RealIP: ip}); err != nil {
dao.PromError("播单下视频rpc错误", "s.fav.IsFavedByFid(%d,%d,%d) error(%v)", mid, plStat.Fid, aid, err)
return
} else if !isPlaylist {
err = ecode.PlVideoAlreadyDel
return
}
if affected, err = s.dao.UpdateArcDesc(c, pid, aid, desc); err != nil {
log.Error("s.dao.UpdateArcDesc(%d,%d,%s) error(%v)", pid, aid, desc, err)
return
}
if affected > 0 {
s.cache.Save(func() {
s.dao.SetArcDescCache(context.Background(), pid, aid, desc)
})
}
s.updatePlTime(c, mid, pid)
return
}
// SearchVideos search add videos.
func (s *Service) SearchVideos(c context.Context, pn, ps int, query string) (res []*model.SearchArc, count int, err error) {
if res, count, err = s.dao.SearchVideo(c, pn, ps, query); err != nil {
log.Error("s.dao.SearchVideo(%s) error(%v)", query, err)
}
if len(res) == 0 {
res = make([]*model.SearchArc, 0)
}
return
}
func (s *Service) videos(c context.Context, pid int64, start, end int) (res []*model.ArcSort, err error) {
var (
arcs []*model.ArcSort
)
if arcs, err = s.dao.ArcsCache(c, pid, start, end); err != nil || len(arcs) == 0 {
err = nil
if arcs, err = s.dao.Videos(c, pid); err != nil {
log.Error("s.dao.Videos(%d) error(%v)", pid, err)
return
}
length := len(arcs)
if length > 0 {
s.cache.Save(func() {
s.dao.SetArcsCache(context.Background(), pid, arcs)
})
}
if length < start {
res = []*model.ArcSort{}
return
}
if length > end+1 {
res = arcs[start : end+1]
} else {
res = arcs[start:]
}
return
}
res = arcs
return
}
func (s *Service) resetArcSort(c context.Context, pid int64) (affected int64, err error) {
var (
arcs, afArcs []*model.ArcSort
)
if arcs, err = s.dao.Videos(c, pid); err != nil {
log.Error("s.dao.Videos(%d) error(%v)", pid, err)
return
}
sort := conf.Conf.Rule.BeginSort
for _, v := range arcs {
sort += s.c.Rule.SortStep
afArcs = append(afArcs, &model.ArcSort{Aid: v.Aid, Desc: v.Desc, Sort: sort})
}
affected, err = s.dao.BatchUpdateArcSort(c, pid, afArcs)
return
}
func (s *Service) filterArc(c context.Context, mid, pid int64, aids []int64, ip string) (res model.Videos, sort, fid int64, err error) {
var (
mutex = sync.Mutex{}
aidsLen = len(aids)
rightAids, rsRight, wrongAids []int64
group, errCtx = errgroup.WithContext(c)
tmpArc []*model.ArcSort
exists map[int64]bool
stat *model.Playlist
)
sort = conf.Conf.Rule.BeginSort
if stat, err = s.Info(c, 0, pid); err != nil {
return
}
if mid != stat.Mid {
err = ecode.PlNotUser
return
}
fid = stat.ID
exists = make(map[int64]bool, stat.Count)
if stat.Count > 0 {
if stat.Count > conf.Conf.Rule.MaxVideoCnt {
err = ecode.PlVideoOverflow
return
}
if tmpArc, err = s.videos(c, pid, 0, stat.Count-1); err != nil {
return
}
for _, v := range tmpArc {
exists[v.Aid] = true
}
if tmpLen := len(tmpArc); tmpLen < stat.Count {
sort = tmpArc[tmpLen-1].Sort
} else {
sort = tmpArc[stat.Count-1].Sort
}
}
tmpRight := make(map[int64]struct{})
for i := 0; i < aidsLen; i += _aidBulkSize {
var partAids []int64
if i+_aidBulkSize > aidsLen {
partAids = aids[i:]
} else {
partAids = aids[i : i+_aidBulkSize]
}
group.Go(func() (err error) {
var arcs *arcmdl.ViewsReply
arg := &arcmdl.ViewsRequest{Aids: partAids}
if arcs, err = s.arcClient.Views(errCtx, arg); err != nil {
log.Error("s.arcClient.Views(%v) error(%v)", partAids, err)
return
}
mutex.Lock()
for _, aid := range partAids {
if arcReply, ok := arcs.Views[aid]; !ok || arcs.Views[aid] == nil {
wrongAids = append(wrongAids, aid)
} else if !arcReply.Arc.IsNormal() ||
exists[aid] ||
arcReply.Arc.Rights.UGCPay == 1 ||
arcReply.Arc.AttrVal(archive.AttrBitIsBangumi) == archive.AttrYes ||
arcReply.Arc.AttrVal(archive.AttrBitIsMovie) == archive.AttrYes ||
(len(arcReply.Pages) > 0 && arcReply.Pages[0].From != _vUpload) {
wrongAids = append(wrongAids, aid)
} else {
rightAids = append(rightAids, aid)
tmpRight[aid] = struct{}{}
}
}
mutex.Unlock()
return
})
}
err = group.Wait()
if rightAids == nil {
rightAids = _empAids
rsRight = _empAids
} else if wrongAids == nil {
wrongAids = _empAids
}
if stat.Count+len(rightAids) > conf.Conf.Rule.MaxVideoCnt {
err = ecode.PlVideoOverflow
return
}
for _, aid := range aids {
if _, ok := tmpRight[aid]; ok {
rsRight = append(rsRight, aid)
}
}
res = model.Videos{RightAids: rsRight, WrongAids: wrongAids}
return
}
func (s *Service) views(c context.Context, aids []int64, ip string) (views map[int64]*arcmdl.ViewReply, err error) {
var (
mutex = sync.Mutex{}
aidsLen = len(aids)
group, errCtx = errgroup.WithContext(c)
)
views = make(map[int64]*arcmdl.ViewReply, aidsLen)
for i := 0; i < aidsLen; i += _aidBulkSize {
var partAids []int64
if i+_aidBulkSize > aidsLen {
partAids = aids[i:]
} else {
partAids = aids[i : i+_aidBulkSize]
}
group.Go(func() (err error) {
var arcs *arcmdl.ViewsReply
arg := &arcmdl.ViewsRequest{Aids: partAids}
if arcs, err = s.arcClient.Views(errCtx, arg); err != nil {
log.Error("s.arcClient.Views(%v) error(%v)", partAids, err)
return
}
mutex.Lock()
for _, v := range arcs.Views {
views[v.Arc.Aid] = v
}
mutex.Unlock()
return
})
}
err = group.Wait()
return
}