go-common/app/interface/main/history/service/history.go
2019-04-22 18:49:16 +08:00

842 lines
19 KiB
Go

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
}