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,71 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"history_test.go",
"service_test.go",
"toview_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/history/conf:go_default_library",
"//app/interface/main/history/model:go_default_library",
"//library/cache/redis:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"history.go",
"history_service.go",
"rpc.go",
"service.go",
"toview.go",
],
importpath = "go-common/app/interface/main/history/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/history/conf:go_default_library",
"//app/interface/main/history/dao/history:go_default_library",
"//app/interface/main/history/dao/toview:go_default_library",
"//app/interface/main/history/model: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/history/api/grpc:go_default_library",
"//app/service/main/history/model:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/queue/databus/report:go_default_library",
"//library/sync/pipeline/fanout:go_default_library",
"//vendor/github.com/pkg/errors: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,841 @@
package service
import (
"context"
"fmt"
"sort"
"strconv"
"time"
"go-common/app/interface/main/history/model"
arcmdl "go-common/app/service/main/archive/model/archive"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
)
var (
_countLimit = 50
_emptyVideos = []*model.Video{}
_emptyHis = []*model.History{}
_emptyHismap = make(map[int64]*model.History)
)
func (s *Service) key(aid int64, typ int8) string {
if typ < model.TypeArticle {
return strconv.FormatInt(aid, 10)
}
return fmt.Sprintf("%d_%d", aid, typ)
}
// AddHistories batch update history.
// +wd:ignore
func (s *Service) AddHistories(c context.Context, mid int64, typ int8, ip string, hs []*model.History) (err error) {
var (
ok bool
his, h *model.History
hm2 map[string]*model.History
hmc, res map[int64]*model.History
expire = time.Now().Unix() - 60*60*24*90
)
if len(hs) > _countLimit {
return ecode.TargetNumberLimit
}
s.serviceAdds(mid, hs)
if hm2, err = s.historyDao.Map(c, mid); err != nil {
return
}
if typ < model.TypeArticle {
if hmc, err = s.historyDao.CacheMap(c, mid); err != nil {
return err
}
}
res = make(map[int64]*model.History)
for _, his = range hs {
if his.Unix < expire {
continue
}
if len(hm2) > 0 {
if h, ok = hm2[s.key(his.Aid, his.TP)]; ok && his.Unix < h.Unix {
continue
}
}
if len(hmc) > 0 {
if h, ok = hmc[his.Aid]; ok && his.Unix < h.Unix {
continue
}
}
// TODO comment && merge
res[his.Aid] = his
}
if err = s.historyDao.AddMap(c, mid, res); err == nil {
return
}
if typ < model.TypeArticle {
s.historyDao.AddCacheMap(c, mid, res)
}
return nil
}
// AddHistory add hisotry progress into hbase.
func (s *Service) AddHistory(c context.Context, mid, rtime int64, h *model.History) (err error) {
if h.TP < model.TypeUnknown || h.TP > model.TypeComic {
err = ecode.RequestErr
return
}
if h.Aid == 0 {
return ecode.RequestErr
}
if h.TP == model.TypeBangumi || h.TP == model.TypeMovie || h.TP == model.TypePGC {
msg := playPro{
Type: h.TP,
SubType: h.STP,
Mid: mid,
Sid: h.Sid,
Epid: h.Epid,
Cid: h.Cid,
Progress: h.Pro,
IP: metadata.String(c, metadata.RemoteIP),
Ts: h.Unix,
RealTime: rtime,
}
s.addPlayPro(&msg)
}
// NOTE if login user to save history
if mid == 0 {
return
}
return s.addHistory(c, mid, h)
}
// Progress get view progress from cache/hbase.
func (s *Service) Progress(c context.Context, mid int64, aids []int64) (res map[int64]*model.History, err error) {
if mid == 0 {
res = _emptyHismap
return
}
if s.migration(mid) {
res, err = s.servicePosition(c, mid, model.BusinessByTP(model.TypeUGC), aids)
if err == nil {
return
}
}
if res, _, err = s.historyDao.Cache(c, mid, aids); err != nil {
return
} else if len(res) == 0 {
res = _emptyHismap
}
return
}
// Position get view progress from cache/hbase.
func (s *Service) Position(c context.Context, mid int64, aid int64, typ int8) (res *model.History, err error) {
if mid == 0 {
err = ecode.NothingFound
return
}
if s.migration(mid) {
var hm map[int64]*model.History
hm, err = s.servicePosition(c, mid, model.BusinessByTP(typ), []int64{aid})
if err == nil && hm != nil {
if res = hm[aid]; res == nil {
err = ecode.NothingFound
}
return
}
}
if typ < model.TypeArticle {
var hm map[int64]*model.History
hm, _, err = s.historyDao.Cache(c, mid, []int64{aid})
if err != nil {
return
}
if len(hm) > 0 {
if h, ok := hm[aid]; ok {
res = h
}
}
if res == nil {
err = ecode.NothingFound
}
return
}
var mhis map[string]*model.History
mhis, err = s.historyDao.Map(c, mid)
if err != nil {
return
}
if len(mhis) > 0 {
key := fmt.Sprintf("%d_%d", aid, typ)
if h, ok := mhis[key]; ok {
res = h
}
}
if res == nil {
err = ecode.NothingFound
}
return
}
// addHistory add new history into set.
func (s *Service) addHistory(c context.Context, mid int64, h *model.History) (err error) {
var cmd int64
// note: the type is video to increase experience of user .
if h.TP < model.TypeArticle {
s.historyDao.PushFirstQueue(c, mid, h.Aid, h.Unix)
}
if cmd, err = s.Shadow(c, mid); err != nil {
return
}
if cmd == model.ShadowOn {
return
}
h.Mid = mid
s.serviceAdd(h)
// note: the type is video to redis`cache .
if h.TP >= model.TypeArticle {
s.addProPub(h)
if !s.conf.History.Pub {
err = s.historyDao.Add(c, mid, h)
}
return
}
// NOTE first view
if h.Pro < 30 && h.Pro != -1 {
h.Pro = 0
}
// NOTE after 30s
if err = s.historyDao.AddCache(c, mid, h); err != nil {
return
}
s.addMerge(mid, h.Unix)
return
}
// ClearHistory clear user's historys.
func (s *Service) ClearHistory(c context.Context, mid int64, tps []int8) (err error) {
s.serviceClear(mid, tps)
if len(tps) == 0 {
s.historyDao.ClearCache(c, mid)
err = s.historyDao.Clear(c, mid)
s.userActionLog(mid, model.HistoryClear)
return
}
tpsMap := make(map[int8]bool)
for _, tp := range tps {
tpsMap[tp] = true
}
var (
histories map[string]*model.History
dels []*model.History
chis map[int64]*model.History
aids []int64
)
if histories, err = s.historyDao.Map(c, mid); err != nil {
return
}
chis, _ = s.historyDao.CacheMap(c, mid)
for _, v := range chis {
histories[s.key(v.Aid, v.TP)] = v
}
logMap := make(map[int8]struct{})
for _, h := range histories {
oldType := h.TP
h.ConvertType()
if tpsMap[h.TP] {
if (h.TP == model.TypeUGC) || (h.TP == model.TypePGC) {
aids = append(aids, h.Aid)
}
h.TP = oldType
dels = append(dels, h)
logMap[oldType] = struct{}{}
}
}
if len(dels) == 0 {
return
}
if len(aids) > 0 {
s.historyDao.DelCache(c, mid, aids)
}
if err = s.historyDao.Delete(c, mid, dels); err != nil {
return
}
for k := range logMap {
s.userActionLog(mid, fmt.Sprintf(model.HistoryClearTyp, model.BusinessByTP(k)))
}
return
}
// DelHistory delete user's history del archive .
// +wd:ignore
func (s *Service) DelHistory(ctx context.Context, mid int64, aids []int64, typ int8) (err error) {
if err = s.serviceDels(ctx, mid, aids, typ); err != nil {
return
}
if err = s.historyDao.DelAids(ctx, mid, aids); err != nil {
return
}
if typ >= model.TypeArticle {
return
}
return s.historyDao.DelCache(ctx, mid, aids)
}
// Videos get videos of user view history.
// +wd:ignore
func (s *Service) Videos(c context.Context, mid int64, pn, ps int, typ int8) (res []*model.Video, err error) {
var (
arc *arcmdl.View3
ok bool
arcs map[int64]*arcmdl.View3
video *model.Video
history []*model.History
his *model.History
aids, epids []int64
aidFavs map[int64]bool
epban map[int64]*model.Bangumi
)
res = _emptyVideos
mOK := s.migration(mid)
if mOK {
businesses := []string{model.BusinessByTP(model.TypeUGC), model.BusinessByTP(model.TypePGC)}
history, epids, err = s.servicePnPsCursor(c, mid, businesses, pn, ps)
}
if !mOK || err != nil {
if history, epids, err = s.histories(c, mid, pn, ps, true); err != nil {
return
}
}
if len(history) == 0 {
return
}
for _, his = range history {
aids = append(aids, his.Aid)
}
if typ >= model.TypeArticle {
// TODO Article info .
return
}
// bangumi info
if len(epids) > 0 {
epban = s.bangumi(c, mid, epids)
}
// archive info
arcAids := &arcmdl.ArgAids2{Aids: aids}
if arcs, err = s.arcRPC.Views3(c, arcAids); err != nil {
return
} else if len(arcs) == 0 {
return
}
// favorite info
aidFavs = s.favoriteds(c, mid, aids)
res = make([]*model.Video, 0, len(aids))
for _, his = range history {
if arc, ok = arcs[his.Aid]; !ok || arc.Archive3 == nil {
continue
}
// NOTE all no pay
arc.Rights.Movie = 0
video = &model.Video{
Archive3: arc.Archive3,
ViewAt: his.Unix,
DT: his.DT,
STP: his.STP,
TP: his.TP,
Progress: his.Pro,
Count: len(arc.Pages),
}
if aidFavs != nil {
video.Favorite = aidFavs[his.Aid]
}
for n, p := range arc.Pages {
if p.Cid == his.Cid {
p.Page = int32(n + 1)
video.Page = p
break
}
}
if video.TP == model.TypeBangumi || video.TP == model.TypeMovie || video.TP == model.TypePGC {
if epban != nil {
video.BangumiInfo = epban[his.Epid]
}
video.Count = 0
}
res = append(res, video)
}
return
}
// AVHistories return the user all av history.
// +wd:ignore
func (s *Service) AVHistories(c context.Context, mid int64) (hs []*model.History, err error) {
if s.migration(mid) {
businesses := []string{model.BusinessByTP(model.TypeUGC), model.BusinessByTP(model.TypePGC)}
hs, _, err = s.servicePnPsCursor(c, mid, businesses, 1, s.conf.History.Max)
if err == nil {
return
}
}
hs, _, err = s.histories(c, mid, 1, s.conf.History.Max, true)
return
}
// Histories return the user all av history.
func (s *Service) Histories(c context.Context, mid int64, typ int8, pn, ps int) (res []*model.Resource, err error) {
var hs []*model.History
mOK := s.migration(mid)
if mOK {
var businesses []string
if typ > 0 {
businesses = []string{model.BusinessByTP(typ)}
}
hs, _, err = s.servicePnPsCursor(c, mid, businesses, pn, ps)
}
if !mOK || err != nil {
if typ >= model.TypeArticle {
hs, err = s.platformHistories(c, mid, typ, pn, ps)
} else {
hs, _, err = s.histories(c, mid, pn, ps, false)
}
}
if err != nil {
return
}
if len(hs) == 0 {
return
}
for _, h := range hs {
h.ConvertType()
r := &model.Resource{
Mid: h.Mid,
Oid: h.Aid,
Sid: h.Sid,
Epid: h.Epid,
Cid: h.Cid,
DT: h.DT,
Pro: h.Pro,
Unix: h.Unix,
TP: h.TP,
STP: h.STP,
}
res = append(res, r)
}
return
}
// histories get aids of user view history
func (s *Service) histories(c context.Context, mid int64, pn, ps int, onlyAV bool) (his []*model.History, epids []int64, err error) {
var (
size int
ok bool
e, h *model.History
mhis map[string]*model.History
chis, ehis map[int64]*model.History
dhis []*model.History
start = (pn - 1) * ps
end = start + ps - 1
total = s.conf.History.Total
)
if mhis, err = s.historyDao.Map(c, mid); err != nil {
err = nil
mhis = make(map[string]*model.History)
}
chis, _ = s.historyDao.CacheMap(c, mid)
for _, v := range chis {
mhis[s.key(v.Aid, v.TP)] = v
}
if len(mhis) == 0 {
his = _emptyHis
return
}
ehis = make(map[int64]*model.History, len(mhis))
for _, h = range mhis {
if onlyAV && h.TP >= model.TypeArticle {
continue
}
if (h.TP == model.TypeBangumi || h.TP == model.TypeMovie || h.TP == model.TypePGC) && h.Sid != 0 {
if e, ok = ehis[h.Sid]; !ok || h.Unix > e.Unix {
ehis[h.Sid] = h
if e != nil {
dhis = append(dhis, e)
}
}
} else {
his = append(his, h)
}
}
for _, h = range ehis {
if h.Epid != 0 {
epids = append(epids, h.Epid)
}
his = append(his, h)
}
sort.Sort(model.Histories(his))
if size = len(his); size > total {
dhis = append(dhis, his[total:]...)
s.delChan.Do(c, func(ctx context.Context) {
s.historyDao.Delete(ctx, mid, dhis)
})
his = his[:total]
size = total
}
switch {
case size > start && size > end:
his = his[start : end+1]
case size > start && size <= end:
his = his[start:]
default:
his = _emptyHis
}
return
}
// platformHistories get aids of user view history
func (s *Service) platformHistories(c context.Context, mid int64, typ int8, pn, ps int) (his []*model.History, err error) {
var (
size int
h *model.History
mhis map[string]*model.History
start = (pn - 1) * ps
end = start + ps - 1
total = s.conf.History.Total
)
if mhis, err = s.historyDao.Map(c, mid); err != nil {
return
}
if len(mhis) == 0 {
his = _emptyHis
return
}
for _, h = range mhis {
if typ != h.TP {
continue
}
his = append(his, h)
}
sort.Sort(model.Histories(his))
if size = len(his); size > total {
his = his[:total]
size = total
}
switch {
case size > start && size > end:
his = his[start : end+1]
case size > start && size <= end:
his = his[start:]
default:
his = _emptyHis
}
return
}
// HistoryType get aids of user view history
// +wd:ignore
func (s *Service) HistoryType(c context.Context, mid int64, typ int8, oids []int64, ip string) (his []*model.History, err error) {
var (
mhis map[string]*model.History
)
if s.migration(mid) {
his, err = s.serviceHistoryType(c, mid, model.BusinessByTP(typ), oids)
if err == nil {
return
}
}
if mhis, err = s.historyDao.Map(c, mid); err != nil {
return
}
if len(mhis) == 0 {
his = _emptyHis
return
}
for _, oid := range oids {
key := fmt.Sprintf("%d_%d", oid, typ)
if h, ok := mhis[key]; ok && h != nil {
his = append(his, h)
}
}
return
}
// HistoryCursor return the user all av history.
func (s *Service) HistoryCursor(c context.Context, mid, max, viewAt int64, ps int, tp int8, tps []int8, ip string) (res []*model.Resource, err error) {
var hs []*model.History
if s.migration(mid) {
var businesses []string
for _, b := range tps {
businesses = append(businesses, model.BusinessByTP(b))
}
res, err = s.serviceHistoryCursor(c, mid, max, businesses, model.BusinessByTP(tp), viewAt, ps)
if err == nil {
return
}
}
hs, err = s.historyCursor(c, mid, max, viewAt, ps, tp, tps, ip)
if err != nil {
return
}
if len(hs) == 0 {
return
}
for _, h := range hs {
r := &model.Resource{
TP: h.TP,
STP: h.STP,
Mid: h.Mid,
Oid: h.Aid,
Sid: h.Sid,
Epid: h.Epid,
Cid: h.Cid,
Business: h.Business,
DT: h.DT,
Pro: h.Pro,
Unix: h.Unix,
}
res = append(res, r)
}
return
}
// historyCursor get aids of user view history.
func (s *Service) historyCursor(c context.Context, mid, max, viewAt int64, ps int, tp int8, tps []int8, ip string) (his []*model.History, err error) {
var (
ok bool
e, h *model.History
mhis map[string]*model.History
chis, ehis map[int64]*model.History
dhis []*model.History
total = s.conf.History.Total
tpMap = make(map[int8]bool)
)
for _, tp := range tps {
tpMap[tp] = true
}
if mhis, err = s.historyDao.Map(c, mid); err != nil {
err = nil
mhis = make(map[string]*model.History)
}
for k, v := range mhis {
v.ConvertType()
if (len(tps) > 0) && !tpMap[v.TP] {
delete(mhis, k)
}
}
chis, _ = s.historyDao.CacheMap(c, mid)
for k, v := range chis {
v.ConvertType()
if (len(tps) > 0) && !tpMap[v.TP] {
delete(chis, k)
continue
}
mhis[s.key(v.Aid, v.TP)] = v
}
if len(mhis) == 0 {
his = _emptyHis
return
}
ehis = make(map[int64]*model.History, len(mhis))
for _, h = range mhis {
if (h.TP == model.TypePGC) && h.Sid != 0 {
if e, ok = ehis[h.Sid]; !ok || h.Unix > e.Unix {
ehis[h.Sid] = h
if e != nil {
dhis = append(dhis, e)
}
}
} else {
his = append(his, h)
}
}
for _, h = range ehis {
his = append(his, h)
}
sort.Sort(model.Histories(his))
if len(his) > total {
dhis = append(dhis, his[total:]...)
s.delChan.Do(c, func(ctx context.Context) {
s.historyDao.Delete(ctx, mid, dhis)
})
his = his[:total]
}
if viewAt != 0 || max != 0 && tp != 0 {
for index, h := range his {
if viewAt != 0 && h.Unix <= viewAt || h.Aid == max && h.TP == tp {
index++
if index+ps <= len(his) {
return his[index : index+ps], nil
}
return his[index:], nil
}
}
return _emptyHis, nil
}
if len(his) >= ps {
return his[:ps], nil
}
return
}
// SetShadow set the user switch.
// +wd:ignore
func (s *Service) SetShadow(c context.Context, mid, value int64) (err error) {
s.serviceHide(mid, value == model.ShadowOn)
if err = s.historyDao.SetInfoShadow(c, mid, value); err != nil {
return
}
return s.historyDao.SetShadowCache(c, mid, value)
}
// Delete .
func (s *Service) Delete(ctx context.Context, mid int64, his []*model.History) (err error) {
if err = s.serviceDel(ctx, mid, his); err != nil {
return
}
if err = s.historyDao.Delete(ctx, mid, his); err != nil {
return
}
var aids []int64
for _, h := range his {
if h.TP < model.TypeArticle {
aids = append(aids, h.Aid)
}
}
if len(aids) == 0 {
return
}
return s.historyDao.DelCache(ctx, mid, aids)
}
// Shadow return the user switch by mid.
// +wd:ignore
func (s *Service) Shadow(c context.Context, mid int64) (value int64, err error) {
var (
ok bool
cache = true
)
if s.migration(mid) {
value, err = s.serviceHideState(c, mid)
if err == nil {
return
}
}
if value, err = s.historyDao.ShadowCache(c, mid); err != nil {
err = nil
cache = false
} else if value != model.ShadowUnknown {
return
}
if value, err = s.historyDao.InfoShadow(c, mid); err != nil {
ok = true
}
if cache {
s.cache.Do(c, func(ctx context.Context) {
s.historyDao.SetShadowCache(ctx, mid, value)
if ok && value == model.ShadowOn {
s.historyDao.SetInfoShadow(ctx, mid, value)
}
})
}
return
}
// FlushHistory flush to hbase from cache.
func (s *Service) FlushHistory(c context.Context, mids []int64, stime int64) (err error) {
var (
aids, miss []int64
res map[int64]*model.History
limit = s.conf.History.Cache
)
for _, mid := range mids {
if aids, err = s.historyDao.IndexCacheByTime(c, mid, stime); err != nil {
log.Error("s.historyDao.IndexCacheByTime(%d,%v) error(%v)", mid, stime, err)
err = nil
continue
}
if len(aids) == 0 {
continue
}
if res, miss, err = s.historyDao.Cache(c, mid, aids); err != nil {
log.Error("historyDao.Cache(%d,%v) miss:%v error(%v)", mid, aids, miss, err)
err = nil
continue
}
// * typ < model.TypeArticle all can .
if err = s.historyDao.AddMap(c, mid, res); err != nil {
log.Error("historyDao.AddMap(%d,%+v) error(%v)", mid, res, err)
err = nil
}
if err = s.historyDao.TrimCache(c, mid, limit); err != nil {
log.Error("historyDao.TrimCache(%d,%d) error(%v)", mid, limit, err)
err = nil
}
}
return
}
func (s *Service) merge(hmap map[int64]int64) {
var (
size = int64(s.conf.History.Size)
merges = make(map[int64][]*model.Merge, size)
)
for k, v := range hmap {
merges[k%size] = append(merges[k%size], &model.Merge{Mid: k, Now: v})
}
for k, v := range merges {
s.historyDao.Merge(context.TODO(), int64(k), v)
}
}
func (s *Service) bangumi(c context.Context, mid int64, epids []int64) (bangumiMap map[int64]*model.Bangumi) {
var n = 50
bangumiMap = make(map[int64]*model.Bangumi, len(epids))
for len(epids) > 0 {
if n > len(epids) {
n = len(epids)
}
epban, _ := s.historyDao.Bangumis(c, mid, epids[:n])
epids = epids[n:]
for k, v := range epban {
bangumiMap[k] = v
}
}
return
}
// ManagerHistory ManagerHistory.
// +wd:ignore
func (s *Service) ManagerHistory(c context.Context, onlyAV bool, mid int64) (his []*model.History, err error) {
var (
mhis map[string]*model.History
chis, ehis map[int64]*model.History
)
if mhis, err = s.historyDao.Map(c, mid); err != nil {
err = nil
mhis = make(map[string]*model.History)
}
chis, _ = s.historyDao.CacheMap(c, mid)
for _, v := range chis {
mhis[s.key(v.Aid, v.TP)] = v
}
if len(mhis) == 0 {
his = _emptyHis
return
}
ehis = make(map[int64]*model.History, len(mhis))
for _, h := range mhis {
if onlyAV && h.TP >= model.TypeArticle {
continue
}
if (h.TP == model.TypeBangumi || h.TP == model.TypeMovie || h.TP == model.TypePGC) && h.Sid != 0 {
if e, ok := ehis[h.Sid]; !ok || h.Unix > e.Unix {
ehis[h.Sid] = h
}
} else {
his = append(his, h)
}
}
for _, h := range ehis {
his = append(his, h)
}
sort.Sort(model.Histories(his))
return
}

View File

@@ -0,0 +1,339 @@
package service
import (
"context"
"time"
"go-common/app/interface/main/history/model"
hisapi "go-common/app/service/main/history/api/grpc"
history "go-common/app/service/main/history/model"
"go-common/library/log"
"go-common/library/net/metadata"
"github.com/pkg/errors"
)
func (s *Service) serviceRun(f func()) {
select {
case s.serviceChan <- f:
default:
log.Error("serviceChan full")
}
}
func (s *Service) serviceproc() {
for {
f := <-s.serviceChan
f()
}
}
func (s *Service) serviceAdd(arg *model.History) {
s.serviceRun(func() {
h := arg.ConvertServiceType()
arg := &hisapi.AddHistoryReq{
Mid: h.Mid,
Business: h.Business,
Kid: h.Kid,
Aid: h.Aid,
Sid: h.Sid,
Epid: h.Epid,
Cid: h.Cid,
SubType: h.SubType,
Device: h.Device,
Progress: h.Progress,
ViewAt: h.ViewAt,
}
s.hisRPC.AddHistory(context.Background(), arg)
})
}
func (s *Service) serviceAdds(mid int64, hs []*model.History) {
s.serviceRun(func() {
arg := &hisapi.AddHistoriesReq{}
for _, a := range hs {
h := a.ConvertServiceType()
arg.Histories = append(arg.Histories, &hisapi.AddHistoryReq{
Mid: mid,
Business: h.Business,
Kid: h.Kid,
Aid: h.Aid,
Sid: h.Sid,
Epid: h.Epid,
Cid: h.Cid,
SubType: h.SubType,
Device: h.Device,
Progress: h.Progress,
ViewAt: h.ViewAt,
})
}
s.hisRPC.AddHistories(context.Background(), arg)
})
}
func (s *Service) serviceDel(ctx context.Context, mid int64, his []*model.History) error {
arg := &hisapi.DelHistoriesReq{
Mid: mid,
Records: []*hisapi.DelHistoriesReq_Record{},
}
var aids []int64
for _, v := range his {
if v.TP == model.TypePGC || v.TP == model.TypeUGC {
aids = append(aids, v.Aid)
}
}
seasonMap := make(map[int64]*model.BangumiSeason)
if len(aids) > 0 {
seasonMap, _ = s.season(ctx, mid, aids, metadata.String(ctx, metadata.RemoteIP))
}
for _, h := range his {
if value, ok := seasonMap[h.Aid]; ok && value != nil {
arg.Records = append(arg.Records, &hisapi.DelHistoriesReq_Record{
Business: model.BusinessByTP(model.TypePGC),
ID: value.ID,
})
log.Warn("seasonMap(%d,%v)season:%d", mid, h.Aid, value.ID)
}
arg.Records = append(arg.Records, &hisapi.DelHistoriesReq_Record{
Business: model.BusinessByTP(h.TP),
ID: h.Aid,
})
}
if _, err := s.hisRPC.DelHistories(ctx, arg); err != nil {
log.Error("s.hisRPC.DelHistories(%+v %+v) err:%+v", his, arg, errors.WithStack(err))
return err
}
return nil
}
func (s *Service) serviceClear(mid int64, tps []int8) {
s.serviceRun(func() {
arg := &hisapi.ClearHistoryReq{
Mid: mid,
}
for _, t := range tps {
arg.Businesses = append(arg.Businesses, model.BusinessByTP(t))
}
s.hisRPC.ClearHistory(context.Background(), arg)
})
}
func (s *Service) serviceDels(ctx context.Context, mid int64, aids []int64, typ int8) error {
arg := &hisapi.DelHistoriesReq{
Mid: mid,
Records: []*hisapi.DelHistoriesReq_Record{},
}
seasonMap := make(map[int64]*model.BangumiSeason)
if typ == 0 {
seasonMap, _ = s.season(ctx, mid, aids, metadata.String(ctx, metadata.RemoteIP))
}
b := model.BusinessByTP(typ)
for _, aid := range aids {
if value, ok := seasonMap[aid]; ok && value != nil {
arg.Records = append(arg.Records, &hisapi.DelHistoriesReq_Record{
Business: model.BusinessByTP(model.TypePGC),
ID: value.ID,
})
log.Warn("seasonMap(%d,%v)season:%d", mid, aid, value.ID)
}
arg.Records = append(arg.Records, &hisapi.DelHistoriesReq_Record{
Business: b,
ID: aid,
})
}
if _, err := s.hisRPC.DelHistories(ctx, arg); err != nil {
log.Error("s.hisRPC.DelHistories(%v %+v) err:%+v", mid, arg, errors.WithStack(err))
return err
}
return nil
}
func (s *Service) serviceHide(mid int64, hide bool) {
s.serviceRun(func() {
arg := &hisapi.UpdateUserHideReq{
Mid: mid,
Hide: hide,
}
s.hisRPC.UpdateUserHide(context.Background(), arg)
})
}
func (s *Service) serviceHistoryCursor(c context.Context, mid int64, kid int64, businesses []string, business string, viewAt int64, ps int) ([]*model.Resource, error) {
if viewAt == 0 {
viewAt = time.Now().Unix()
}
arg := &hisapi.UserHistoriesReq{
Mid: mid,
Businesses: businesses,
Business: business,
Kid: kid,
ViewAt: viewAt,
Ps: int64(ps),
}
reply, err := s.hisRPC.UserHistories(c, arg)
if err != nil {
log.Error("s.hisRPC.UserHistories(%+v) err:%+v", arg, err)
return nil, err
}
if reply == nil {
return nil, err
}
his := make([]*model.Resource, 0)
for _, v := range reply.Histories {
tp, _ := model.CheckBusiness(v.Business)
his = append(his, &model.Resource{
Mid: v.Mid,
Oid: v.Aid,
Sid: v.Sid,
Epid: v.Epid,
TP: tp,
Business: v.Business,
STP: int8(v.SubType),
Cid: v.Cid,
DT: int8(v.Device),
Pro: int64(v.Progress),
Unix: v.ViewAt,
})
}
return his, nil
}
func (s *Service) servicePnPsCursor(c context.Context, mid int64, businesses []string, pn, ps int) ([]*model.History, []int64, error) {
if pn*ps > 1000 {
return nil, nil, nil
}
arg := &hisapi.UserHistoriesReq{
Mid: mid,
Businesses: businesses,
Ps: int64(pn * ps),
ViewAt: time.Now().Unix(),
}
reply, err := s.hisRPC.UserHistories(c, arg)
if err != nil {
log.Error("s.hisRPC.UserHistories(%+v) err:%+v", arg, err)
return nil, nil, err
}
if reply == nil {
return nil, nil, err
}
size := len(reply.Histories)
start := (pn - 1) * ps
end := start + ps - 1
switch {
case size > start && size > end:
reply.Histories = reply.Histories[start : end+1]
case size > start && size <= end:
reply.Histories = reply.Histories[start:]
default:
reply.Histories = make([]*history.History, 0)
}
var epids []int64
his := make([]*model.History, 0)
for _, v := range reply.Histories {
tp, _ := model.CheckBusiness(v.Business)
if tp == model.TypePGC {
epids = append(epids, v.Epid)
}
his = append(his, &model.History{
Mid: v.Mid,
Aid: v.Aid,
Sid: v.Sid,
Epid: v.Epid,
TP: tp,
Business: v.Business,
STP: int8(v.SubType),
Cid: v.Cid,
DT: int8(v.Device),
Pro: int64(v.Progress),
Unix: v.ViewAt,
})
}
return his, epids, nil
}
func (s *Service) servicePosition(c context.Context, mid int64, business string, kids []int64) (map[int64]*model.History, error) {
arg := &hisapi.HistoriesReq{
Mid: mid,
Business: business,
Kids: kids,
}
reply, err := s.hisRPC.Histories(c, arg)
if err != nil {
log.Error("s.hisRPC.Histories(%+v) err:%+v", arg, err)
return nil, err
}
if reply == nil {
return nil, err
}
now := time.Now().Unix() - 8*60*60
his := make(map[int64]*model.History)
for _, v := range reply.Histories {
if business == model.BusinessByTP(model.TypeUGC) && v.ViewAt < now {
continue
}
tp, _ := model.CheckBusiness(v.Business)
his[v.Aid] = &model.History{
Mid: v.Mid,
Aid: v.Aid,
Sid: v.Sid,
Epid: v.Epid,
TP: tp,
Business: v.Business,
STP: int8(v.SubType),
Cid: v.Cid,
DT: int8(v.Device),
Pro: int64(v.Progress),
Unix: v.ViewAt,
}
}
return his, nil
}
func (s *Service) serviceHistoryType(c context.Context, mid int64, business string, kids []int64) ([]*model.History, error) {
arg := &hisapi.HistoriesReq{
Mid: mid,
Business: business,
Kids: kids,
}
reply, err := s.hisRPC.Histories(c, arg)
if err != nil {
log.Error("s.hisRPC.Histories(%+v) err:%+v", arg, err)
return nil, err
}
if reply == nil {
return nil, err
}
his := make([]*model.History, 0)
for _, v := range reply.Histories {
tp, _ := model.CheckBusiness(v.Business)
his = append(his, &model.History{
Mid: v.Mid,
Aid: v.Aid,
Sid: v.Sid,
Epid: v.Epid,
TP: tp,
Business: v.Business,
STP: int8(v.SubType),
Cid: v.Cid,
DT: int8(v.Device),
Pro: int64(v.Progress),
Unix: v.ViewAt,
})
}
return his, nil
}
func (s *Service) serviceHideState(c context.Context, mid int64) (int64, error) {
arg := &hisapi.UserHideReq{
Mid: mid,
}
reply, err := s.hisRPC.UserHide(c, arg)
if err != nil {
log.Error("s.hisRPC.UserHide(%d) err:%+v", mid, err)
return 0, err
}
if !reply.Hide {
return 0, nil
}
return 1, nil
}

View File

@@ -0,0 +1,97 @@
package service
import (
"context"
"testing"
"time"
"go-common/app/interface/main/history/model"
. "github.com/smartystreets/goconvey/convey"
)
// TestService_History
func TestService_History(t *testing.T) {
var (
c = context.TODO()
mid int64 = 14771787
aid int64 = 5463823
aids = []int64{5463823}
sid int64 = 5730
cid int64 = 97791
epid int64 = 97922
pro int64 = 155
rtime int64 = 1490958549
tp int8 = 1
dt int8 = 2
pn = 1
ps = 100
now = time.Now().Unix()
h = &model.History{Aid: aid, Unix: now, Sid: sid, Epid: epid, Cid: cid, Pro: pro, TP: int8(tp), DT: int8(dt)}
)
Convey("history ", t, WithService(func(s *Service) {
Convey("history AddHistory ", func() {
err := s.AddHistory(c, mid, rtime, h)
So(err, ShouldBeNil)
})
Convey("history Progress", func() {
_, err := s.Progress(c, mid, aids)
So(err, ShouldBeNil)
})
Convey("history DelHistory", func() {
err := s.DelHistory(c, mid, aids, 3)
So(err, ShouldBeNil)
})
Convey("history ClearHistory", func() {
err := s.ClearHistory(c, mid, []int8{3})
So(err, ShouldBeNil)
})
Convey("history Videos", func() {
_, err := s.Videos(c, mid, pn, ps, 3)
So(err, ShouldBeNil)
})
Convey("history AVHistories", func() {
_, err := s.AVHistories(c, mid)
So(err, ShouldBeNil)
})
Convey("history Histories", func() {
_, err := s.Histories(c, mid, 1, 2, 3)
So(err, ShouldBeNil)
})
Convey("history SetShadow", func() {
err := s.SetShadow(c, mid, 1)
So(err, ShouldBeNil)
})
Convey("history Shadow", func() {
_, err := s.Shadow(c, mid)
So(err, ShouldBeNil)
})
Convey("history Manager", func() {
_, err := s.ManagerHistory(c, false, mid)
So(err, ShouldBeNil)
})
}))
}
func TestService_AddHistory(t *testing.T) {
var (
c = context.TODO()
mid int64 = 14771787
aid int64 = 5463823
sid int64 = 5730
cid int64 = 97791
epid int64 = 97922
pro int64 = 155
rtime int64 = 1490958549
tp int8 = 1
dt int8 = 2
now = time.Now().Unix()
h = &model.History{Aid: aid, Unix: now, Sid: sid, Epid: epid, Cid: cid, Pro: pro, TP: int8(tp), DT: int8(dt)}
)
Convey("history ", t, WithService(func(s *Service) {
Convey("history AddHistory ", func() {
err := s.AddHistory(c, mid, rtime, h)
So(err, ShouldBeNil)
})
}))
}

View File

@@ -0,0 +1,36 @@
package service
import (
"context"
favmdl "go-common/app/service/main/favorite/model"
"go-common/library/log"
"go-common/library/net/metadata"
)
// favoriteds return aids is favs.
func (s *Service) favoriteds(c context.Context, mid int64, aids []int64) (res map[int64]bool) {
var n = 50
res = make(map[int64]bool, len(aids))
for len(aids) > 0 {
if n > len(aids) {
n = len(aids)
}
arg := &favmdl.ArgIsFavs{
Type: favmdl.TypeVideo,
Mid: mid,
Oids: aids[:n],
RealIP: metadata.String(c, metadata.RemoteIP),
}
favMap, err := s.favRPC.IsFavs(c, arg)
if err != nil {
log.Error("s.favRPC.IsFavs(%v) error(%v)", arg, err)
return
}
aids = aids[n:]
for k, v := range favMap {
res[k] = v
}
}
return
}

View File

@@ -0,0 +1,239 @@
package service
import (
"context"
"fmt"
"time"
"go-common/app/interface/main/history/conf"
"go-common/app/interface/main/history/dao/history"
"go-common/app/interface/main/history/dao/toview"
"go-common/app/interface/main/history/model"
arcrpc "go-common/app/service/main/archive/api/gorpc"
favrpc "go-common/app/service/main/favorite/api/gorpc"
hisrpc "go-common/app/service/main/history/api/grpc"
"go-common/library/log"
"go-common/library/queue/databus/report"
"go-common/library/sync/pipeline/fanout"
)
type playPro struct {
Type int8 `json:"type"`
SubType int8 `json:"sub_type"`
Mid int64 `json:"mid"`
Sid int64 `json:"sid"`
Epid int64 `json:"epid"`
Cid int64 `json:"cid"`
Progress int64 `json:"progress"`
IP string `json:"ip"`
Ts int64 `json:"ts"`
RealTime int64 `json:"realtime"`
}
// Service is history service.
type Service struct {
conf *conf.Config
historyDao *history.Dao
toviewDao *toview.Dao
delChan *fanout.Fanout
mergeChan chan *model.Merge
msgs chan *playPro
proChan chan *model.History
serviceChan chan func()
favRPC *favrpc.Service
arcRPC *arcrpc.Service2
hisRPC hisrpc.HistoryClient
cache *fanout.Fanout
toviewCache *fanout.Fanout
midMap map[int64]bool
}
// New new a History service.
func New(c *conf.Config) (s *Service) {
s = &Service{
conf: c,
historyDao: history.New(c),
toviewDao: toview.New(c),
mergeChan: make(chan *model.Merge, 1024),
msgs: make(chan *playPro, 1024),
proChan: make(chan *model.History, 1024),
serviceChan: make(chan func(), 10240),
delChan: fanout.New("cache", fanout.Worker(1), fanout.Buffer(1024)),
arcRPC: arcrpc.New2(c.RPCClient2.Archive),
favRPC: favrpc.New2(c.RPCClient2.Favorite),
cache: fanout.New("cache", fanout.Worker(1), fanout.Buffer(1024)),
toviewCache: fanout.New("cache", fanout.Worker(1), fanout.Buffer(1024)),
midMap: make(map[int64]bool),
}
for _, v := range s.conf.History.Mids {
s.midMap[v] = true
}
var err error
if s.hisRPC, err = hisrpc.NewClient(c.RPCClient2.History); err != nil {
panic(err)
}
go s.playProproc()
go s.mergeproc()
go s.proPubproc()
for i := 0; i < s.conf.History.ConsumeSize; i++ {
go s.serviceproc()
}
return
}
func (s *Service) addMerge(mid, now int64) {
select {
case s.mergeChan <- &model.Merge{Mid: mid, Now: now}:
default:
log.Warn("mergeChan chan is full")
}
}
func (s *Service) addPlayPro(p *playPro) {
select {
case s.msgs <- p:
default:
log.Warn("s.msgs chan is full")
}
}
func (s *Service) addProPub(p *model.History) {
select {
case s.proChan <- p:
default:
log.Warn("s.proChan chan is full")
}
}
func (s *Service) mergeproc() {
var (
m *model.Merge
ticker = time.NewTicker(time.Duration(s.conf.History.Ticker))
mergeMap = make(map[int64]int64)
)
for {
select {
case m = <-s.mergeChan:
if m == nil {
s.merge(mergeMap)
return
}
if _, ok := mergeMap[m.Mid]; !ok {
mergeMap[m.Mid] = m.Now
}
if len(mergeMap) < s.conf.History.Page {
continue
}
case <-ticker.C:
}
s.merge(mergeMap)
mergeMap = make(map[int64]int64)
}
}
// playProproc send history to databus.
func (s *Service) playProproc() {
var (
msg *playPro
ms []*playPro
ticker = time.NewTicker(time.Second)
)
for {
select {
case msg = <-s.msgs:
if msg == nil {
if len(ms) > 0 {
s.pushPlayPro(ms)
}
return
}
ms = append(ms, msg)
if len(ms) < 100 {
continue
}
case <-ticker.C:
}
if len(ms) == 0 {
continue
}
s.pushPlayPro(ms)
ms = make([]*playPro, 0, 100)
}
}
func (s *Service) pushPlayPro(ms []*playPro) {
key := fmt.Sprintf("%d%d", ms[0].Mid, ms[0].Sid)
for j := 0; j < 3; j++ {
if err := s.historyDao.PlayPro(context.Background(), key, ms); err == nil {
return
}
}
}
// proPubroc send history to databus.
func (s *Service) proPubproc() {
for {
msg := <-s.proChan
if msg == nil {
return
}
s.proPub(msg)
}
}
func (s *Service) proPub(msg *model.History) {
key := fmt.Sprintf("%d%d", msg.Mid, msg.Aid)
for j := 0; j < 3; j++ {
if err := s.historyDao.ProPub(context.Background(), key, msg); err == nil {
break
}
}
}
func (s *Service) userActionLog(mid int64, action string) {
report.User(&report.UserInfo{
Mid: mid,
Business: model.HistoryLog,
Action: action,
Ctime: time.Now(),
})
}
func (s *Service) migration(mid int64) bool {
if !s.conf.History.Migration || mid == 0 {
return false
}
if _, ok := s.midMap[mid]; ok {
return true
}
if s.conf.History.Rate != 0 && mid%s.conf.History.Rate == 0 {
return true
}
return false
}
// Ping ping service.
// +wd:ignore
func (s *Service) Ping(c context.Context) (err error) {
if s.historyDao != nil {
err = s.historyDao.Ping(c)
}
if s.toviewDao != nil {
err = s.toviewDao.Ping(c)
}
return
}
// Close close resource.
// +wd:ignore
func (s *Service) Close() {
s.mergeChan <- nil
s.msgs <- nil
s.proChan <- nil
if s.historyDao != nil {
s.historyDao.Close()
}
if s.toviewDao != nil {
s.toviewDao.Close()
}
}

View File

@@ -0,0 +1,37 @@
package service
import (
"context"
"flag"
"path/filepath"
"time"
"go-common/app/interface/main/history/conf"
"go-common/library/cache/redis"
. "github.com/smartystreets/goconvey/convey"
)
var (
s *Service
)
func CleanCache() {
c := context.TODO()
pool := redis.NewPool(conf.Conf.Redis.Config)
pool.Get(c).Do("FLUSHDB")
}
func WithService(f func(s *Service)) func() {
return func() {
Reset(func() { CleanCache() })
f(s)
}
}
func init() {
dir, _ := filepath.Abs("../cmd/history-interface-test.toml")
flag.Set("conf", dir)
conf.Init()
s = New(conf.Conf)
time.Sleep(time.Second)
}

View File

@@ -0,0 +1,464 @@
package service
import (
"context"
"sort"
"time"
"go-common/app/interface/main/history/model"
arcmdl "go-common/app/service/main/archive/model/archive"
"go-common/library/ecode"
"go-common/library/log"
)
const maxToView = 100
var (
_empToView = []*model.ToView{}
_empArcToView = []*model.ArcToView{}
_empWebArcToView = []*model.WebArcToView{}
)
// AddToView add the user a toview item.
// +wd:ignore
func (s *Service) AddToView(c context.Context, mid, aid int64, ip string) (err error) {
var (
ok bool
count int
arc *arcmdl.View3
now = time.Now().Unix()
)
arcAid := &arcmdl.ArgAid2{Aid: aid}
if arc, err = s.arcRPC.View3(c, arcAid); err != nil {
return
}
if arc == nil {
return
}
if arc.Rights.UGCPay == 1 {
return ecode.ToViewPayUGC
}
if ok, err = s.toviewDao.Expire(c, mid); err != nil {
return
}
if ok {
if count, err = s.toviewDao.CntCache(c, mid); err != nil {
return
}
} else {
if _, count, err = s.toView(c, mid, 1, maxToView, ip); err != nil {
return
}
}
if count >= maxToView {
err = ecode.ToViewOverMax
return
}
if err = s.toviewDao.Add(c, mid, aid, now); err != nil {
return
}
s.toviewCache.Do(c, func(ctx context.Context) {
if errCache := s.toviewDao.AddCache(ctx, mid, aid, now); errCache != nil {
log.Warn("s.toviewDao.AddCache(%d,%d,%d) err:%v", mid, aid, now, errCache)
}
})
return
}
// AddMultiToView add toview items to user.
// +wd:ignore
func (s *Service) AddMultiToView(c context.Context, mid int64, aids []int64, ip string) (err error) {
var (
ok bool
count int
expectedAids []int64
arcs map[int64]*arcmdl.View3
viewMap map[int64]*model.ToView
views []*model.ToView
now = time.Now().Unix()
)
arcAids := &arcmdl.ArgAids2{Aids: aids}
if arcs, err = s.arcRPC.Views3(c, arcAids); err != nil {
return
} else if len(arcs) == 0 {
return
}
for _, v := range arcs {
if v.Rights.UGCPay == 1 {
return ecode.ToViewPayUGC
}
}
if ok, err = s.toviewDao.Expire(c, mid); err != nil {
return
}
if ok {
if viewMap, err = s.toviewDao.CacheMap(c, mid); err != nil {
return
}
} else {
var list []*model.ToView
list, _, err = s.toView(c, mid, 1, maxToView, ip)
if err != nil {
return
}
viewMap = make(map[int64]*model.ToView)
for _, v := range list {
if v == nil {
continue
}
viewMap[v.Aid] = v
}
}
count = len(viewMap)
if count >= maxToView {
err = ecode.ToViewOverMax
return
}
expectedLen := maxToView - count
for _, aid := range aids {
if _, exist := viewMap[aid]; !exist {
expectedAids = append(expectedAids, aid)
expectedLen--
if expectedLen == 0 {
break
}
}
}
if err = s.toviewDao.Adds(c, mid, expectedAids, now); err != nil {
return
}
if ok {
s.toviewCache.Do(c, func(ctx context.Context) {
for _, aid := range expectedAids {
views = append(views, &model.ToView{Aid: aid, Unix: now})
}
if errCache := s.toviewDao.AddCacheList(ctx, mid, views); errCache != nil {
log.Warn("s.toviewDao.AddCacheList(%d,%v) err:%v", mid, views, errCache)
}
})
}
return
}
// RemainingToView add toview items to user.
// +wd:ignore
func (s *Service) RemainingToView(c context.Context, mid int64, ip string) (remaining int, err error) {
var (
count int
)
if _, count, err = s.toView(c, mid, 1, maxToView, ip); err != nil {
return
}
remaining = maxToView - count
return
}
// ClearToView clear the user toview items.
// +wd:ignore
func (s *Service) ClearToView(c context.Context, mid int64) (err error) {
if err = s.toviewDao.ClearCache(c, mid); err != nil {
return
}
s.userActionLog(mid, model.ToviewClear)
return s.toviewDao.Clear(c, mid)
}
// DelToView delete the user to videos.
// +wd:ignore
func (s *Service) DelToView(c context.Context, mid int64, aids []int64, viewed bool, ip string) (err error) {
var (
delAids []int64
list []*model.ToView
rhs, hs map[int64]*model.History
rs *model.History
)
// viewed del all viewed
if viewed {
if list, _, err = s.toView(c, mid, 1, maxToView, ip); err != nil {
return
}
for _, l := range list {
aids = append(aids, l.Aid)
}
if len(aids) == 0 {
return
}
if hs, err = s.historyDao.AidsMap(c, mid, aids); err != nil {
return
}
rhs, _ = s.historyDao.CacheMap(c, mid)
for _, rs = range rhs {
hs[rs.Aid] = rs
}
for k, v := range hs {
if v.Pro >= 30 || v.Pro == -1 {
delAids = append(delAids, k)
}
}
if len(delAids) == 0 {
return
}
if err = s.toviewDao.Del(c, mid, delAids); err != nil {
return
}
s.toviewCache.Do(c, func(ctx context.Context) {
s.toviewDao.DelCaches(ctx, mid, delAids)
})
return
}
if err = s.toviewDao.Del(c, mid, aids); err != nil {
return
}
s.toviewCache.Do(c, func(ctx context.Context) {
s.toviewDao.DelCaches(ctx, mid, aids)
})
return
}
// WebToView get videos of user view history.
// +wd:ignore
func (s *Service) WebToView(c context.Context, mid int64, pn, ps int, ip string) (res []*model.WebArcToView, count int, err error) {
var (
ok bool
aids, epids []int64
avs map[int64]*arcmdl.View3
views []*model.ToView
v *model.ToView
hm map[int64]*model.History
av *arcmdl.View3
epban = make(map[int64]*model.Bangumi)
seasonMap = make(map[int64]*model.BangumiSeason)
)
if views, count, err = s.toView(c, mid, pn, ps, ip); err != nil {
return
}
if len(views) == 0 {
return
}
for _, v = range views {
if v != nil {
aids = append(aids, v.Aid)
}
}
argAids := &arcmdl.ArgAids2{Aids: aids}
if avs, err = s.arcRPC.Views3(c, argAids); err != nil {
log.Error("s.arcRPC.Views3(arcAids:(%v), arcs) error(%v)", aids, err)
return
}
if len(avs) == 0 {
return
}
seasonMap, epids = s.season(c, mid, aids, ip)
// bangumi info
if len(epids) > 0 {
epban = s.bangumi(c, mid, epids)
}
if hm, err = s.toViewPro(c, mid, aids); err != nil {
err = nil
}
res = make([]*model.WebArcToView, 0, len(aids))
for _, v = range views {
if v == nil {
count--
continue
}
// NOTE compat android
if av, ok = avs[v.Aid]; !ok || av == nil {
count--
continue
}
// NOTE all no pay
av.Rights.Movie = 0
at := &model.WebArcToView{View3: av}
at.AddTime = v.Unix
if hm[v.Aid] != nil {
at.Cid = hm[v.Aid].Cid
at.Progress = hm[v.Aid].Pro
}
if season, ok := seasonMap[v.Aid]; ok && season != nil {
if bangumi, ok := epban[season.Epid]; ok && bangumi != nil {
at.BangumiInfo = bangumi
}
}
res = append(res, at)
}
if len(res) == 0 {
res = _empWebArcToView
}
return
}
// ToView get videos of user view history.
// +wd:ignore
func (s *Service) ToView(c context.Context, mid int64, pn, ps int, ip string) (res []*model.ArcToView, count int, err error) {
var (
ok bool
aids []int64
avs map[int64]*arcmdl.View3
views []*model.ToView
v *model.ToView
hm map[int64]*model.History
av *arcmdl.View3
)
res = _empArcToView
if views, count, err = s.toView(c, mid, pn, ps, ip); err != nil {
return
}
if len(views) == 0 {
return
}
for _, v = range views {
if v != nil {
aids = append(aids, v.Aid)
}
}
argAids := &arcmdl.ArgAids2{Aids: aids}
if avs, err = s.arcRPC.Views3(c, argAids); err != nil {
log.Error("s.arcRPC.Views3(%v) error(%v)", aids, err)
return
}
if len(avs) == 0 {
return
}
if hm, err = s.toViewPro(c, mid, aids); err != nil {
err = nil
}
res = make([]*model.ArcToView, 0, len(aids))
for _, v = range views {
if v == nil {
count--
continue
}
// NOTE compat android
if av, ok = avs[v.Aid]; !ok || av.Archive3 == nil {
count--
continue
}
// NOTE all no pay
av.Rights.Movie = 0
at := &model.ArcToView{
Archive3: av.Archive3,
Count: len(av.Pages),
}
at.AddTime = v.Unix
// get cid and progress
if hm[v.Aid] != nil {
at.Cid = hm[v.Aid].Cid
at.Progress = hm[v.Aid].Pro
}
for n, p := range av.Pages {
if p.Cid == at.Cid {
p.Page = int32(n + 1)
at.Page = p
break
}
}
res = append(res, at)
}
return
}
// toView return ToSee of After the paging data.
func (s *Service) toView(c context.Context, mid int64, pn, ps int, ip string) (res []*model.ToView, count int, err error) {
var (
ok bool
start = (pn - 1) * ps
end = start + ps - 1
)
if ok, err = s.toviewDao.Expire(c, mid); err != nil {
return
}
if ok {
if end > maxToView {
end = maxToView
}
if res, err = s.toviewDao.Cache(c, mid, start, end); err != nil {
return
}
count, err = s.toviewDao.CntCache(c, mid)
if count > maxToView {
count = maxToView
}
return
}
var views []*model.ToView
var viewMap = make(map[int64]*model.ToView)
if viewMap, err = s.toviewDao.MapInfo(c, mid, nil); err != nil {
return
}
if len(viewMap) == 0 {
res = _empToView
return
}
for _, v := range viewMap {
views = append(views, v)
}
sort.Sort(model.ToViews(views))
if count = len(views); count > maxToView {
count = maxToView
views = views[:count]
}
switch {
case count > start && count > end:
res = views[start : end+1]
case count > start && count <= end:
res = views[start:]
default:
res = _empToView
}
s.toviewCache.Do(c, func(ctx context.Context) {
if errCache := s.toviewDao.AddCacheList(ctx, mid, views); errCache != nil {
log.Warn("s.toviewDao.AddCacheList(%d,%v) err:%v", mid, views, errCache)
}
})
return
}
func (s *Service) toViewPro(c context.Context, mid int64, aids []int64) (res map[int64]*model.History, err error) {
var (
miss []int64
hm map[int64]*model.History
)
if res, miss, err = s.historyDao.Cache(c, mid, aids); err != nil {
err = nil
} else if len(res) == len(aids) {
return
}
if len(res) == 0 {
res = make(map[int64]*model.History)
miss = aids
}
if hm, err = s.historyDao.AidsMap(c, mid, miss); err != nil {
err = nil
}
for k, v := range hm {
res[k] = v
}
return
}
func (s *Service) season(c context.Context, mid int64, aids []int64, ip string) (seasonMap map[int64]*model.BangumiSeason, epids []int64) {
var (
n = 50
seasonM = make(map[int64]*model.BangumiSeason, n)
)
seasonMap = make(map[int64]*model.BangumiSeason, n)
for len(aids) > 0 {
if n > len(aids) {
n = len(aids)
}
seasonM, _ = s.historyDao.BangumisByAids(c, mid, aids[:n], ip)
aids = aids[n:]
for k, v := range seasonM {
epids = append(epids, v.Epid)
seasonMap[k] = v
}
}
return
}
// ManagerToView manager get mid toview list.
// +wd:ignore
func (s *Service) ManagerToView(c context.Context, mid int64, ip string) ([]*model.ToView, error) {
return s.toviewDao.ListInfo(c, mid, nil)
}

View File

@@ -0,0 +1,46 @@
package service
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
// TestService_AddHistory
func TestService_Toview(t *testing.T) {
var (
c = context.TODO()
mid int64 = 27515316
aids = []int64{27515316, 27515316}
ip = ""
pn = 1
ps = 10000
)
Convey("toview ", t, WithService(func(s *Service) {
Convey("toview AddMultiToView", func() {
err := s.AddMultiToView(c, mid, aids, ip)
So(err, ShouldBeNil)
})
Convey("toview RemainingToView", func() {
_, err := s.RemainingToView(c, mid, "")
So(err, ShouldBeNil)
})
Convey("toview ClearToView", func() {
err := s.ClearToView(c, mid)
So(err, ShouldBeNil)
})
Convey("toview DelToView", func() {
err := s.DelToView(c, mid, aids, true, "")
So(err, ShouldBeNil)
})
Convey("toview cache del", func() {
_, _, err := s.ToView(c, mid, pn, ps, ip)
So(err, ShouldBeNil)
})
Convey("toview Manager", func() {
_, err := s.ManagerToView(c, mid, ip)
So(err, ShouldBeNil)
})
}))
}