You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
561 lines
13 KiB
561 lines
13 KiB
package academy |
|
|
|
import ( |
|
"context" |
|
"sort" |
|
"strconv" |
|
"time" |
|
|
|
"go-common/app/interface/main/creative/dao/tool" |
|
"go-common/app/interface/main/creative/model/academy" |
|
"go-common/library/ecode" |
|
"go-common/library/log" |
|
"go-common/library/sync/errgroup" |
|
|
|
"go-common/app/service/main/archive/api" |
|
|
|
"github.com/davecgh/go-spew/spew" |
|
) |
|
|
|
// Tags get all h5 tags. |
|
func (s *Service) Tags(c context.Context) (res []*academy.Tag) { |
|
if v, ok := s.TagsCache[academy.TagClassMap(academy.H5)]; ok { |
|
res = v |
|
} |
|
return |
|
} |
|
|
|
// Archives get all h5 archive. |
|
func (s *Service) Archives(c context.Context, aca *academy.EsParam) (res *academy.ArchiveList, err error) { |
|
tids, err := s.webTags(aca.Tid) |
|
if err != nil { |
|
return |
|
} |
|
aca.Tid = tids |
|
|
|
res, err = s.ArchivesWithES(c, aca) |
|
return |
|
} |
|
|
|
func (s *Service) webTags(tids []int64) (webTIDs []int64, err error) { |
|
var ( |
|
lts []*academy.LinkTag |
|
h5TIDs []int64 |
|
) |
|
|
|
if len(tids) > 0 { |
|
h5TIDs = tids |
|
} else { |
|
tgs := s.Tags(context.Background()) |
|
for _, v := range tgs { |
|
h5TIDs = append(h5TIDs, v.ID) |
|
} |
|
} |
|
|
|
lts, err = s.aca.LinkTags(context.Background(), h5TIDs) |
|
if err != nil { |
|
return |
|
} |
|
|
|
for _, v := range lts { |
|
webTIDs = append(webTIDs, v.LinkID) |
|
} |
|
return |
|
} |
|
|
|
// RecommendV2 get recommend archive. |
|
func (s *Service) RecommendV2(c context.Context, mid int64) (res []*academy.RecArcList, err error) { |
|
mainIDMap := make(map[int64]struct{}) //主题课程aid map for rm dup |
|
tgList, err := s.getRecTag(c, mid) |
|
if err != nil { |
|
return |
|
} |
|
log.Info("Recommend mid(%d)|tgList(%s)", mid, spew.Sdump(tgList)) |
|
s.setSeed() //init Seed. |
|
res = make([]*academy.RecArcList, 0) |
|
var ( |
|
g, _ = errgroup.WithContext(c) |
|
hotItems []*academy.RecArchive |
|
) |
|
|
|
g.Go(func() error { |
|
// get hot archives |
|
hotItems, err = s.hotArchives(c) |
|
if err != nil { |
|
log.Error("Recommend s.hotArchives mid(%d)", mid) |
|
return err |
|
} |
|
return nil |
|
}) |
|
|
|
for _, t := range tgList { |
|
if t == nil { |
|
continue |
|
} |
|
pid, v := t.PID, t.TIDs |
|
|
|
if pid == 0 { //主题课程 |
|
var ocid int64 |
|
if len(v) > 0 { |
|
ocid = v[0] |
|
} |
|
rec := &academy.RecArcList{ |
|
TID: ocid, |
|
Items: []*academy.RecArchive{}, |
|
} |
|
tg, o := s.OccMapCache[ocid] |
|
if !o || tg == nil { |
|
log.Error("s.OccMapCache ocid(%d) not exist", ocid) |
|
continue |
|
} |
|
rec.Name = tg.Name |
|
items, themeCourseErr := s.themeCourse(c, v) |
|
if themeCourseErr != nil { |
|
return nil, themeCourseErr |
|
} |
|
for _, v := range items { |
|
mainIDMap[v.OID] = struct{}{} |
|
} |
|
rec.Items = items |
|
res = append(res, rec) |
|
} else if tg, ok := s.TagMapCache[pid]; ok { //标签教程 |
|
rec := &academy.RecArcList{ |
|
TID: pid, |
|
Name: tg.Name, |
|
Items: []*academy.RecArchive{}, |
|
} |
|
|
|
items, tagCourseErr := s.tagCourse(c, pid, v, mainIDMap) |
|
if tagCourseErr != nil { |
|
return nil, tagCourseErr |
|
} |
|
rec.Items = items |
|
res = append(res, rec) |
|
} |
|
} |
|
|
|
if g.Wait() != nil { |
|
log.Error("Recommend s.hotArchives g.Wait() error(%v)", err) |
|
return |
|
} |
|
// add host archives |
|
hotRec := &academy.RecArcList{ |
|
TID: 0, |
|
Name: "热门推荐", |
|
Items: hotItems, |
|
} |
|
res = append(res, hotRec) |
|
|
|
return |
|
} |
|
|
|
func (s *Service) tagCourse(c context.Context, pid int64, v []int64, aidMap map[int64]struct{}) (res []*academy.RecArchive, err error) { |
|
res = make([]*academy.RecArchive, 0) |
|
aca := &academy.EsParam{ |
|
Tid: v, |
|
Pn: 1, |
|
Ps: 10, |
|
} |
|
|
|
if s.Seed > 0 { //取材创意/视频制作/个人运营 每日0点请求搜索更换时间种子 |
|
aca.Seed = s.Seed |
|
} |
|
|
|
arcs, err := s.Archives(c, aca) |
|
if err != nil { |
|
log.Error("Recommend s.Archives EsParam(%+v)|error(%v)", aca, err) |
|
return nil, err |
|
} |
|
|
|
if arcs == nil { |
|
err = ecode.CreativeAcademyH5RecommendErr |
|
return nil, err |
|
} |
|
|
|
var aids []int64 |
|
for _, i := range arcs.Items { |
|
// ignore if exist in resource service |
|
if _, exist := s.ResourceMapCache[i.OID]; exist { |
|
continue |
|
} |
|
// ignore if exist in main topic |
|
if _, exist := aidMap[i.OID]; exist { |
|
continue |
|
} |
|
ra := &academy.RecArchive{ |
|
OID: i.OID, |
|
MID: i.MID, |
|
Cover: i.Cover, |
|
Title: i.Title, |
|
Business: i.Business, |
|
Duration: i.Duration, |
|
ArcStat: i.ArcStat, |
|
ArtStat: i.ArtStat, |
|
} |
|
res = append(res, ra) |
|
aids = append(aids, i.OID) |
|
} |
|
|
|
// add tags |
|
tags, err := s.getTags(c, aids) |
|
if err != nil { |
|
log.Error("tagCourse s.getTags err(%v)", err) |
|
return |
|
} |
|
s.setTags(res, tags) |
|
return |
|
} |
|
|
|
func (s *Service) themeCourse(c context.Context, v []int64) (res []*academy.RecArchive, err error) { |
|
res = make([]*academy.RecArchive, 0) |
|
arcs, err := s.ThemeCourse(c, v, []int64{}, []int64{}, 1, 10, false) |
|
if err != nil { |
|
log.Error("Recommend s.ThemeCourse v(%+v)|error(%v)", v, err) |
|
return nil, err |
|
} |
|
|
|
if arcs == nil { |
|
err = ecode.CreativeAcademyH5RecommendErr |
|
return nil, err |
|
} |
|
|
|
var aids []int64 |
|
for _, i := range arcs.Items { |
|
// ignore if exist in resource service |
|
if _, exist := s.ResourceMapCache[i.AID]; exist { |
|
continue |
|
} |
|
ra := &academy.RecArchive{ |
|
OID: i.AID, |
|
MID: i.MID, |
|
Cover: i.Cover, |
|
Title: i.Title, |
|
Duration: i.Duration, |
|
ArcStat: i.ArcStat, |
|
Business: academy.BusinessForArchive, |
|
} |
|
res = append(res, ra) |
|
aids = append(aids, i.AID) |
|
} |
|
|
|
// add tags |
|
tags, err := s.getTags(c, aids) |
|
if err != nil { |
|
log.Error("themeCourse s.getTags err(%v)", err) |
|
return |
|
} |
|
s.setTags(res, tags) |
|
|
|
s.randomForMainCourse(res) //每日0点随机随机稿件列表 |
|
if len(s.RecommendArcs) > 0 { |
|
res = s.RecommendArcs |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) getRecTag(c context.Context, mid int64) (res []*academy.RecConf, err error) { |
|
var tyID int64 |
|
if mid > 0 { |
|
tyID, err = s.getFavType(c, mid) //获取推荐分区id |
|
if err != nil { |
|
log.Error("getFavType mid(%d)|error(%v)", mid, err) |
|
} else { |
|
log.Info("getFavType mid(%d)|tyID(%d)", mid, tyID) |
|
} |
|
} |
|
|
|
if s.c == nil || s.c.AcaRecommend == nil { |
|
log.Error("getRecTag get conf error mid(%d)", mid) |
|
return |
|
} |
|
rec := s.c.AcaRecommend.Recommend |
|
|
|
//按 主题课程-取材创意-视频制作-个人运营 排序 |
|
var rec1, rec2, rec3, rec4 *academy.RecConf |
|
res = make([]*academy.RecConf, 0, 4) |
|
//主题课程 |
|
if rec.Course != nil { |
|
course := rec.Course |
|
rec1 = &academy.RecConf{PID: course.ID} |
|
if tyID != 0 { |
|
if tool.ElementInSlice(tyID, course.Shoot.Val) { //如果最近投稿分区命中配置的分区,则设置当前一级分类下面的标签为最近投稿分区对应的二级标签目录 |
|
rec1.TIDs = course.Shoot.Key |
|
|
|
} else if tool.ElementInSlice(tyID, course.Scene.Val) { |
|
rec1.TIDs = course.Scene.Key |
|
|
|
} else if tool.ElementInSlice(tyID, course.Edit.Val) { |
|
rec1.TIDs = course.Edit.Key |
|
|
|
} else if tool.ElementInSlice(tyID, course.Mmd.Val) { |
|
rec1.TIDs = course.Mmd.Key |
|
|
|
} else if tool.ElementInSlice(tyID, course.Sing.Val) { |
|
rec1.TIDs = course.Sing.Key |
|
|
|
} else if tool.ElementInSlice(tyID, course.Bang.Val) { |
|
rec1.TIDs = course.Bang.Key |
|
} |
|
} else { |
|
rec1.TIDs = course.Other.Key |
|
} |
|
res = append(res, rec1) |
|
} else { |
|
log.Error("getRecTag get cousre conf mid(%d)", mid) |
|
} |
|
|
|
//取材创意 |
|
if rec.Drawn != nil { |
|
drawn := rec.Drawn |
|
rec2 = &academy.RecConf{PID: drawn.ID} |
|
if tyID != 0 { |
|
if tool.ElementInSlice(tyID, drawn.MobilePlan.Val) { //如果最近投稿分区命中配置的分区,则设置当前一级分类下面的标签为最近投稿分区对应的二级标签目录 |
|
rec2.TIDs = drawn.MobilePlan.Key |
|
} else if tool.ElementInSlice(tyID, drawn.ScreenPlan.Val) { |
|
rec2.TIDs = drawn.ScreenPlan.Key |
|
} else if tool.ElementInSlice(tyID, drawn.RecordPlan.Val) { |
|
rec2.TIDs = drawn.RecordPlan.Key |
|
} |
|
} else { |
|
rec2.TIDs = drawn.Other.Key |
|
} |
|
res = append(res, rec2) |
|
} else { |
|
log.Error("getRecTag get drawn conf mid(%d)", mid) |
|
} |
|
|
|
//视频制作 |
|
if rec.Video != nil { |
|
video := rec.Video |
|
rec3 = &academy.RecConf{PID: video.ID} |
|
if tyID != 0 { |
|
if tool.ElementInSlice(tyID, video.MobileMake.Val) { //如果最近投稿分区命中配置的分区,则设置当前一级分类下面的标签为最近投稿分区对应的二级标签目录 |
|
rec3.TIDs = video.MobileMake.Key |
|
} else if tool.ElementInSlice(tyID, video.AudioEdit.Val) { |
|
rec3.TIDs = video.AudioEdit.Key |
|
} else if tool.ElementInSlice(tyID, video.EditCompose.Val) { |
|
rec3.TIDs = video.EditCompose.Key |
|
} |
|
} else { |
|
rec3.TIDs = video.Other.Key |
|
} |
|
res = append(res, rec3) |
|
} else { |
|
log.Error("getRecTag get video conf mid(%d)", mid) |
|
} |
|
|
|
//个人运营 |
|
if rec.Person != nil { |
|
person := rec.Person |
|
rec4 = &academy.RecConf{PID: person.ID, TIDs: person.Other.Key} |
|
res = append(res, rec4) |
|
} else { |
|
log.Error("getRecTag get person conf mid(%d)", mid) |
|
} |
|
|
|
return |
|
} |
|
|
|
func (s *Service) getFavType(c context.Context, mid int64) (tyID int64, err error) { //获取最近投稿的一个分区 |
|
tys, err := s.arc.FavTypes(c, mid) |
|
if err != nil { |
|
log.Error("s.arc.FavTypes mid(%d)|error(%v)", mid, err) |
|
return |
|
} |
|
|
|
if len(tys) == 0 { |
|
return 0, nil |
|
} |
|
|
|
type kv struct { |
|
id int64 |
|
ptime int64 |
|
} |
|
var tps []*kv |
|
|
|
for id, t := range tys { |
|
tid, err := strconv.ParseInt(id, 10, 64) |
|
if err != nil { |
|
return 0, err |
|
} |
|
tps = append(tps, &kv{tid, t}) |
|
} |
|
|
|
sort.Slice(tps, func(i, j int) bool { |
|
return tps[i].ptime > tps[j].ptime |
|
}) |
|
if len(tps) > 0 && tps[0] != nil { |
|
tyID = tps[0].id |
|
} |
|
return |
|
} |
|
|
|
//randomForMainCourse 主题课程每日0点重新随机排序 |
|
func (s *Service) randomForMainCourse(arc []*academy.RecArchive) { |
|
count := len(arc) |
|
if count == 0 { |
|
return |
|
} |
|
keys := tool.RandomSliceKeys(0, count, count, s.Seed) |
|
|
|
res := make([]*academy.RecArchive, 0, count) |
|
for _, k := range keys { |
|
res = append(res, arc[k]) |
|
} |
|
|
|
if len(res) > 0 { //获取随机排序的稿件列表 |
|
s.RecommendArcs = res |
|
} |
|
log.Info("randomRecommend s.RecommendArcs (%s)", spew.Sdump(s.RecommendArcs)) |
|
} |
|
|
|
func (s *Service) setSeed() { |
|
now := time.Now() |
|
last := now |
|
next := now.Add(time.Hour * 24) |
|
|
|
last = time.Date(last.Year(), last.Month(), last.Day(), 0, 0, 0, 0, last.Location()) //昨日0点 |
|
next = time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, next.Location()) //明日0点 |
|
|
|
if now.Unix() > last.Unix() && now.Unix() < next.Unix() { |
|
s.Seed = last.Unix() //set last seed |
|
} else { |
|
s.Seed = next.Unix() //set next seed |
|
} |
|
log.Info("setSeed s.Seed (%d)", s.Seed) |
|
} |
|
|
|
func (s *Service) getTags(c context.Context, aids []int64) (res map[int64]map[string][]*academy.Tag, err error) { |
|
if len(aids) == 0 { |
|
log.Error("getTags len(aids) == 0") |
|
return |
|
} |
|
|
|
aidTIDsMap, err := s.aca.ArchiveTagsByOids(c, aids) |
|
if err != nil { |
|
log.Error("getTags s.aca.ArchiveTagsByOids aids(%+v)", aids) |
|
return |
|
} |
|
if len(aidTIDsMap) == 0 { |
|
log.Error("getTags len(aidTIDsMap) == 0 | aids(%+v)", aids) |
|
return |
|
} |
|
res, err = s.bindTags(c, aidTIDsMap) |
|
if err != nil { |
|
log.Error("getTags s.bindTags | err(%v)", err) |
|
return |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) setTags(x interface{}, tags map[int64]map[string][]*academy.Tag) { |
|
switch arcs := x.(type) { |
|
case []*academy.RecArchive: |
|
for _, v := range arcs { |
|
if v != nil { |
|
if tag, ok := tags[v.OID]; ok { |
|
v.Tags = tag |
|
} |
|
} |
|
} |
|
case []*academy.ArcMeta: |
|
for _, v := range arcs { |
|
if v != nil { |
|
if tag, ok := tags[v.AID]; ok { |
|
v.Tags = tag |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// HotArchives get host archives |
|
func (s *Service) HotArchives(c context.Context, oids []int64) (res []*academy.ArchiveMeta, err error) { |
|
res = make([]*academy.ArchiveMeta, 0) |
|
if len(oids) == 0 { |
|
log.Error("HotArchives len(oids) == 0") |
|
return |
|
} |
|
var ( |
|
g, _ = errgroup.WithContext(c) |
|
arcs map[int64]*api.Arc |
|
st map[int64]*api.Stat |
|
) |
|
|
|
g.Go(func() error { |
|
arcs, err = s.arc.Archives(c, oids, "") |
|
if err != nil { |
|
log.Error("HotArchives s.arc.Archives oids(%+v)| error(%v)", oids, err) |
|
return err |
|
} |
|
st, err = s.arc.Stats(c, oids, "") |
|
if err != nil { |
|
log.Error("HotArchives s.arc.Stats oids(%+v)| error(%v)", oids, err) |
|
return err |
|
} |
|
return nil |
|
}) |
|
if g.Wait() != nil { |
|
log.Error("HotArchives g.Wait() error(%v)", err) |
|
return |
|
} |
|
|
|
for _, oid := range oids { |
|
a := &academy.ArchiveMeta{ |
|
OID: oid, |
|
} |
|
a = bindArchiveInfo(oid, arcs, a) |
|
if t, ok := st[oid]; ok { |
|
a.ArcStat = t |
|
} else { |
|
a.ArcStat = &api.Stat{} |
|
} |
|
res = append(res, a) |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) hotArchives(c context.Context) (res []*academy.RecArchive, err error) { |
|
if len(s.ResourceMapCache) == 0 { |
|
log.Error("hotArchives len(oids) == 0 | ResourceMapCache(%+v)", s.ResourceMapCache) |
|
return |
|
} |
|
res = make([]*academy.RecArchive, 0) |
|
var oids []int64 |
|
|
|
for k := range s.ResourceMapCache { |
|
oids = append(oids, k) |
|
} |
|
|
|
arcs, err := s.HotArchives(c, oids) |
|
if err != nil { |
|
log.Error("hotArchives s.HotArchives oids(%+v) | error(%v)", oids, err) |
|
return |
|
} |
|
|
|
var aids []int64 |
|
for _, v := range arcs { |
|
ra := &academy.RecArchive{ |
|
OID: v.OID, |
|
MID: v.MID, |
|
Cover: v.Cover, |
|
Title: v.Title, |
|
Business: 1, //热门推荐默认为视频 |
|
Duration: v.Duration, |
|
ArcStat: v.ArcStat, |
|
ArtStat: v.ArtStat, |
|
} |
|
res = append(res, ra) |
|
aids = append(aids, v.OID) |
|
} |
|
|
|
//add tags |
|
tags, err := s.getTags(c, aids) |
|
if err != nil { |
|
log.Error("hotArchives s.getTags err(%v)", err) |
|
return |
|
} |
|
s.setTags(res, tags) |
|
|
|
return |
|
}
|
|
|