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,66 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["service_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/creative/conf:go_default_library",
"//app/interface/main/creative/model/academy:go_default_library",
"//app/interface/main/creative/service:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"academy.go",
"h5.go",
"service.go",
"skill.go",
],
importpath = "go-common/app/interface/main/creative/service/academy",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/creative/conf:go_default_library",
"//app/interface/main/creative/dao/academy:go_default_library",
"//app/interface/main/creative/dao/archive:go_default_library",
"//app/interface/main/creative/dao/article:go_default_library",
"//app/interface/main/creative/dao/resource:go_default_library",
"//app/interface/main/creative/dao/tool:go_default_library",
"//app/interface/main/creative/model/academy:go_default_library",
"//app/interface/main/creative/model/newcomer:go_default_library",
"//app/interface/main/creative/service:go_default_library",
"//app/interface/openplatform/article/model:go_default_library",
"//app/service/main/archive/api:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/davecgh/go-spew/spew: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,314 @@
package academy
import (
"context"
"time"
"go-common/app/interface/main/creative/model/academy"
"go-common/app/interface/openplatform/article/model"
"go-common/app/service/main/archive/api"
"go-common/library/log"
"go-common/library/sync/errgroup"
xtime "go-common/library/time"
"github.com/davecgh/go-spew/spew"
)
// TagList get all tag.
func (s *Service) TagList(c context.Context) (res map[string][]*academy.Tag, err error) {
res = s.TagsCache
return
}
// AddFeedBack add feedback.
func (s *Service) AddFeedBack(c context.Context, category, course, suggest string, mid int64) (id int64, err error) {
fb := &academy.FeedBack{
Category: category,
Course: course,
Suggest: suggest,
CTime: xtime.Time(time.Now().Unix()),
MTime: xtime.Time(time.Now().Unix()),
}
if id, err = s.aca.AddFeedBack(c, fb, mid); err != nil {
log.Error("s.aca.AddFeedBack error(%v)", err)
}
return
}
// ArchivesWithES get all archive by es.
func (s *Service) ArchivesWithES(c context.Context, aca *academy.EsParam) (res *academy.ArchiveList, err error) {
var sear *academy.SearchResult
res = &academy.ArchiveList{
Items: []*academy.ArchiveMeta{},
Page: &academy.ArchivePage{},
}
aca.TidsMap = s.filterTIDs(aca.Tid)
if sear, err = s.aca.ArchivesWithES(c, aca); err != nil {
log.Error("s.aca.ArchivesWithES sear(%+v)|param(%+v)|error(%v)", sear, aca, err)
return
}
if sear == nil || len(sear.Result) == 0 {
log.Error("s.aca.ArchivesWithES has no data sear(%+v)|param(%+v)|error(%v)", sear, aca, err)
return
}
res.Page.Total = sear.Page.Total
res.Page.Pn = sear.Page.Num
res.Page.Ps = sear.Page.Size
var searRes []*academy.EsArc
if aca.Keyword != "" { //搜索关键词红点
searRes = make([]*academy.EsArc, 0, len(sear.Result)/2)
for i := 0; i < len(sear.Result)-1; i += 2 {
sear.Result[i].Title = sear.Result[i+1].Title
searRes = append(searRes, sear.Result[i])
}
} else {
searRes = sear.Result
}
oids := make([]int64, 0, len(sear.Result))
aidTIDsMap := make(map[int64][]int64)
busAIDsMap := make(map[int][]int64)
busAidMap := make(map[int64]int)
highTitleMap := make(map[int64][]string)
for _, v := range searRes {
busAIDsMap[v.Business] = append(busAIDsMap[v.Business], v.OID)
oids = append(oids, v.OID)
aidTIDsMap[v.OID] = v.TID
busAidMap[v.OID] = v.Business
if aca.Keyword != "" { //搜索关键词红点
highTitleMap[v.OID] = v.Title
}
}
var (
g, _ = errgroup.WithContext(c)
tagInfo map[int64]map[string][]*academy.Tag
arcs map[int64]*api.Arc
arts map[int64]*model.Meta
st map[int64]*api.Stat
)
g.Go(func() error { //获取各种查询对象信息
switch aca.Business {
case academy.BusinessForAll: //查询所有
if ids, ok := busAIDsMap[academy.BusinessForArchive]; ok { //稿件
g.Go(func() error {
arcs, err = s.arc.Archives(c, ids, aca.IP)
if err != nil {
log.Error("s.arc.Archives oids(%+v)|business(%d)|error(%v)", ids, aca.Business, err)
return err
}
st, err = s.arc.Stats(c, ids, aca.IP)
if err != nil {
log.Error("s.arc.Stats oids(%+v)|business(%d)|error(%v)", ids, aca.Business, err)
}
return err
})
}
if ids, ok := busAIDsMap[academy.BusinessForArticle]; ok { //文章
g.Go(func() error {
arts, err = s.art.ArticleMetas(context.Background(), ids, aca.IP)
if err != nil {
log.Error("s.arc.ArticleMetas oids(%+v)|business(%d)|error(%v)", ids, aca.Business, err)
}
return err
})
}
case academy.BusinessForArchive: //稿件
arcs, err = s.arc.Archives(context.Background(), oids, aca.IP)
if err != nil {
log.Error("s.arc.Archives oids(%+v)|business(%d)|error(%v)", oids, aca.Business, err)
return err
}
st, err = s.arc.Stats(c, oids, aca.IP)
if err != nil {
log.Error("s.arc.Stats oids(%+v)|business(%d)|error(%v)", oids, aca.Business, err)
}
return err
case academy.BusinessForArticle: //文章
arts, err = s.art.ArticleMetas(context.Background(), oids, aca.IP)
if err != nil {
log.Error("s.arc.ArticleMetas oids(%+v)|business(%d)|error(%v)", oids, aca.Business, err)
}
return err
}
return nil
})
g.Go(func() error {
tagInfo, err = s.bindTags(c, aidTIDsMap)
return err
})
if g.Wait() != nil {
log.Error("s.aca.ArchivesWithES g.Wait() error(%v)", err)
return
}
items := make([]*academy.ArchiveMeta, 0, len(oids))
for _, oid := range oids {
a := &academy.ArchiveMeta{
OID: oid,
}
if v, ok := tagInfo[oid]; ok {
a.Tags = v
}
bs, ok := busAidMap[oid]
if !ok {
log.Error("s.aca.ArchivesWithES oid(%d) get invalid business", oid)
return
}
a.Business = bs
switch a.Business {
case academy.BusinessForArchive: //稿件
a = bindArchiveInfo(oid, arcs, a)
if t, ok := st[oid]; ok {
a.ArcStat = t
} else {
a.ArcStat = &api.Stat{}
}
case academy.BusinessForArticle: //文章
a = bindArticleInfo(oid, arts, a)
}
if aca.Keyword != "" {
if ht, ok := highTitleMap[oid]; ok && len(ht) > 0 {
a.HighLightTitle = ht[0]
}
}
items = append(items, a)
}
res.Items = items
return
}
func bindArchiveInfo(oid int64, arcs map[int64]*api.Arc, a *academy.ArchiveMeta) (res *academy.ArchiveMeta) {
if v, ok := arcs[oid]; ok {
a.Title = v.Title
a.State = v.State
a.Type = v.TypeName
a.Cover = v.Pic
a.UName = v.Author.Name
a.Face = v.Author.Face
a.MID = v.Author.Mid
a.Duration = v.Duration
a.Rights = v.Rights
}
res = a
return
}
func bindArticleInfo(oid int64, arts map[int64]*model.Meta, a *academy.ArchiveMeta) (res *academy.ArchiveMeta) {
if v, ok := arts[oid]; ok && v != nil {
a.Title = v.Title
a.State = v.State
a.MID = v.Author.Mid
a.Comment = v.Summary
if v.Category != nil {
a.Type = v.Category.Name
}
if len(v.ImageURLs) > 0 {
a.Cover = v.ImageURLs[0]
}
if v.Author != nil {
a.UName = v.Author.Name
a.Face = v.Author.Face
}
if v.Stats != nil {
a.ArtStat = v.Stats
} else {
a.ArtStat = &model.Stats{}
}
}
res = a
return
}
func (s *Service) bindTags(c context.Context, tidsMap map[int64][]int64) (res map[int64]map[string][]*academy.Tag, err error) {
res = make(map[int64]map[string][]*academy.Tag)
for oid, tids := range tidsMap {
tgs := s.getTagsByTIDs(tids)
if len(tgs) == 0 {
continue
}
oidTag := make(map[string][]*academy.Tag)
ctgs := make(map[int64][]*academy.Tag)
for _, tg := range tgs {
k := academy.TagClassMap(int(tg.Type))
if tg.Type == academy.Classify { //获取多个分类标签
ctgs[tg.ParentID] = append(ctgs[tg.ParentID], tg)
} else {
oidTag[k] = append(oidTag[k], tg)
}
}
for pid, tgs := range ctgs {
if p, ok := s.TagMapCache[pid]; ok {
tp := *p
tp.Children = tgs
oidTag[academy.TagClassMap(academy.Classify)] = append(oidTag[academy.TagClassMap(academy.Classify)], &tp)
}
}
res[oid] = oidTag
}
return
}
func (s *Service) filterTIDs(tids []int64) (res map[int][]int64) {
if len(tids) == 0 {
return
}
log.Info("s.filterTIDs origin tids(%+v)", tids)
res = make(map[int][]int64)
ochs := make([]int64, 0) //原始提交的二级标签
ops := make([]int64, 0) //原始提交的一级标签
qchs := make([]int64, 0) //通过一级标签查询出来的二级标签
for _, id := range tids {
t, ok := s.parentChildMapCache[id]
if !ok || t == nil {
continue
}
if t.Type == academy.Classify {
if t.ParentID != 0 { //原始提交的二级标签
ochs = append(ochs, id)
} else if t.ParentID == 0 && len(t.Children) > 0 { //通过一级标签查询出来的二级标签
for _, v := range t.Children {
qchs = append(qchs, v.ID)
}
} else if t.ParentID == 0 && len(t.Children) == 0 {
ops = append(ops, id)
}
} else {
res[int(t.Type)] = append(res[int(t.Type)], id)
}
}
if len(ochs) > 0 { //如果分类标签中提交了原始的二级标签则认为按该二级标签进行筛选,如果可以查询到二级标签认为筛选全部二级,否则一级参与查询.
res[academy.Classify] = ochs
} else if len(qchs) > 0 {
res[academy.Classify] = qchs
} else if len(ops) > 0 {
res[academy.Classify] = ops
}
log.Info("s.filterTIDs res(%s)", spew.Sdump(res))
return
}
func (s *Service) getTagsByTIDs(tids []int64) (res []*academy.Tag) {
res = make([]*academy.Tag, 0)
if len(tids) == 0 {
return
}
for _, tid := range tids {
tag, ok := s.TagMapCache[tid]
if !ok || tag == nil {
continue
}
res = append(res, tag)
}
return
}

View File

@@ -0,0 +1,561 @@
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
}

View File

@@ -0,0 +1,159 @@
package academy
import (
"context"
"time"
"go-common/app/interface/main/creative/conf"
"go-common/app/interface/main/creative/dao/academy"
"go-common/app/interface/main/creative/dao/archive"
"go-common/app/interface/main/creative/dao/article"
"go-common/app/interface/main/creative/dao/resource"
acaMdl "go-common/app/interface/main/creative/model/academy"
"go-common/app/interface/main/creative/service"
"go-common/library/log"
)
//Service struct
type Service struct {
c *conf.Config
aca *academy.Dao
arc *archive.Dao
art *article.Dao
resource *resource.Dao
TagsCache map[string][]*acaMdl.Tag
TagMapCache map[int64]*acaMdl.Tag
parentChildMapCache map[int64]*acaMdl.Tag
ResourceMapCache map[int64]struct{}
OccCache []*acaMdl.Occupation
OccMapCache map[int64]*acaMdl.Occupation
SkillCache []*acaMdl.Skill
SkillMapCache map[int64]*acaMdl.Skill
OfficialID int64
EditorChoiceID int64
NewbCourseID int64
ResourceID int64
//for recommend
Seed int64
RecommendArcs []*acaMdl.RecArchive
//task
p *service.Public
//keywords
KWsCache []interface{}
}
//New get service
func New(c *conf.Config, rpcdaos *service.RPCDaos, p *service.Public) *Service {
s := &Service{
c: c,
aca: academy.New(c),
arc: rpcdaos.Arc,
art: rpcdaos.Art,
resource: resource.New(c),
OccMapCache: make(map[int64]*acaMdl.Occupation),
SkillMapCache: make(map[int64]*acaMdl.Skill),
OfficialID: c.Academy.OfficialID,
EditorChoiceID: c.Academy.EditorChoiceID,
NewbCourseID: c.Academy.NewbCourseID,
ResourceID: c.Academy.ResourceID,
p: p,
}
s.loadTags()
s.loadOccupations()
s.loadSkills()
s.loadResources()
s.loadKeyWords()
go s.loadProc()
return s
}
// Ping service
func (s *Service) Ping(c context.Context) (err error) {
if err = s.aca.Ping(c); err != nil {
log.Error("s.aca.Ping err(%v)", err)
}
return
}
// Close dao
func (s *Service) Close() {
s.aca.Close()
}
// loadproc
func (s *Service) loadProc() {
for {
time.Sleep(3 * time.Minute)
s.loadTags()
s.loadOccupations()
s.loadSkills()
s.loadResources()
s.loadKeyWords()
}
}
//load tags
func (s *Service) loadTags() {
tgList, tgMap, pcMap, err := s.aca.TagList(context.Background())
if err != nil {
log.Error("s.aca.TagList error(%v)", err)
return
}
s.TagsCache = tgList
s.TagMapCache = tgMap
s.parentChildMapCache = pcMap
}
//load occupations
func (s *Service) loadOccupations() {
ocs, err := s.aca.Occupations(context.Background())
if err != nil {
log.Error("s.aca.Occupations error(%v)", err)
return
}
s.OccCache = ocs
for _, v := range s.OccCache {
s.OccMapCache[v.ID] = v
}
}
//load skills
func (s *Service) loadSkills() {
sks, err := s.aca.Skills(context.Background())
if err != nil {
log.Error("s.aca.Skills error(%v)", err)
return
}
s.SkillCache = sks
for _, v := range s.SkillCache {
s.SkillMapCache[v.ID] = v
}
}
//load skills
func (s *Service) loadResources() {
res, err := s.resource.Resource(context.Background(), int(s.ResourceID))
if err != nil {
log.Error("loadResources ResourceID(%d) error(%v)", int(s.ResourceID), err)
return
}
if res == nil {
return
}
s.ResourceMapCache = res
}
//load keywords
func (s *Service) loadKeyWords() {
res, err := s.aca.Keywords(context.Background())
if err != nil {
return
}
if res == nil {
return
}
s.KWsCache = acaMdl.Trees(res, "ID", "ParentID", "Children")
}

View File

@@ -0,0 +1,95 @@
package academy
import (
"context"
"flag"
"path/filepath"
"testing"
"time"
"go-common/app/interface/main/creative/conf"
"go-common/app/interface/main/creative/model/academy"
"go-common/app/interface/main/creative/service"
. "github.com/smartystreets/goconvey/convey"
)
var (
s *Service
p *service.Public
)
func init() {
dir, _ := filepath.Abs("../../cmd/creative.toml")
flag.Set("conf", dir)
conf.Init()
rpcdaos := service.NewRPCDaos(conf.Conf)
p = service.New(conf.Conf, rpcdaos)
s = New(conf.Conf, rpcdaos, p)
time.Sleep(time.Second)
}
func WithService(f func(s *Service)) func() {
return func() {
Reset(func() {})
f(s)
}
}
func Test_TagList(t *testing.T) {
var (
c = context.TODO()
)
Convey("TagList", t, WithService(func(s *Service) {
res, err := s.TagList(c)
So(err, ShouldBeNil)
So(res, ShouldNotBeNil)
}))
}
func Test_ArchivesWithES(t *testing.T) {
var (
c = context.TODO()
aca = &academy.EsParam{
Tid: []int64{},
Business: 1,
Pn: 1,
Ps: 10,
Keyword: "",
Order: "",
IP: "127.0.0.1",
}
)
Convey("Archives", t, WithService(func(s *Service) {
res, err := s.ArchivesWithES(c, aca)
//spew.Dump(res, err)
So(err, ShouldBeNil)
So(res, ShouldNotBeNil)
}))
}
func Test_AddFeedBack(t *testing.T) {
var (
c = context.TODO()
category = "视频"
course = "图像处理"
suggest = "画质太差"
mid = int64(123)
)
Convey("AddFeedBack", t, WithService(func(s *Service) {
id, err := s.AddFeedBack(c, category, course, suggest, mid)
So(err, ShouldBeNil)
So(id, ShouldBeGreaterThan, 0)
}))
}
func Test_RecommendV2(t *testing.T) {
var (
c = context.TODO()
mid = int64(123)
)
Convey("RecommendV2", t, WithService(func(s *Service) {
_, err := s.RecommendV2(c, mid)
So(err, ShouldBeNil)
}))
}

View File

@@ -0,0 +1,492 @@
package academy
import (
"context"
"sort"
"time"
"go-common/app/interface/main/creative/model/academy"
"go-common/app/interface/main/creative/model/newcomer"
"go-common/app/service/main/archive/api"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/sync/errgroup"
xtime "go-common/library/time"
)
// Occupations get occ.
func (s *Service) Occupations(c context.Context) (res []*academy.Occupation, err error) {
res = s.OccCache
res = make([]*academy.Occupation, 0, len(res))
for _, v := range s.OccCache {
if v.ID == 7 || v.ID == s.NewbCourseID { //7-废弃 NewbCourseID-新人专区
continue
}
res = append(res, v)
}
return
}
// NewbCourse get new up course.
func (s *Service) NewbCourse(c context.Context) (res []*academy.NewbCourseList, err error) {
var (
skids []int64
pids = []int64{}
sids = []int64{}
tcs *academy.ArcList
skMap map[int64]string
aids []int64
)
skMap = make(map[int64]string)
for _, v := range s.SkillCache {
if v.OID == s.NewbCourseID {
skids = append(skids, v.ID)
skMap[v.ID] = v.Name
}
}
tcs, err = s.ThemeCourse(c, pids, skids, sids, 1, 20, true)
if err != nil {
log.Error("NewbCourse s.ThemeCourse pids(%+v)|skids(%+v)|sids(%+v)|error(%v)", pids, skids, sids, err)
return
}
if len(tcs.Items) == 0 {
return
}
// add tags
for _, v := range tcs.Items {
aids = append(aids, v.AID)
}
tags, err := s.getTags(c, aids)
if err != nil {
log.Error("NewbCourse s.getTags err(%v)", err)
return
}
s.setTags(tcs.Items, tags)
newbCourseMap := make(map[int64][]*academy.ArcMeta)
for _, v := range tcs.Items {
if v == nil || v.Skill == nil {
continue
}
newbCourseMap[v.Skill.SkID] = append(newbCourseMap[v.Skill.SkID], v)
}
res = make([]*academy.NewbCourseList, 0)
for _, id := range skids {
l := &academy.NewbCourseList{}
if v, ok := newbCourseMap[id]; ok {
l.Items = v
}
if sname, ok := skMap[id]; ok {
l.Title = sname
l.TID = id
}
res = append(res, l)
}
return
}
// ThemeCourse get theme course.
func (s *Service) ThemeCourse(c context.Context, pids, skids, sids []int64, pn, ps int, isnew bool) (res *academy.ArcList, err error) {
var (
skas []*academy.SkillArc
aids []int64
pidMap map[int64]int64
skidMap map[int64]int64
sidMap map[int64]int64
total int
)
res = &academy.ArcList{
Items: []*academy.ArcMeta{},
Page: &academy.ArchivePage{
Pn: pn,
Ps: ps,
},
}
if !isnew && len(pids) == 0 { //如果不是新人课程并且不传职业课程id则默认全部
for _, v := range s.OccCache {
pids = append(pids, v.ID)
}
}
if !isnew { //不是新人课程,则去掉新人课程
for i := 0; i < len(pids); i++ {
if pids[i] == s.NewbCourseID {
pids = append(pids[:i], pids[i+1:]...)
i--
}
}
}
if skas, err = s.aca.SkillArcs(c, pids, skids, sids, (pn-1)*ps, ps); err != nil {
log.Error("s.aca.SkillArcs pid(%+v)|skid(%+v)|sid(%+v)|error(%v)", pids, skids, sids, err)
return
}
if len(skas) == 0 {
log.Error("s.aca.SkillArcs has no data")
return
}
pidMap = make(map[int64]int64)
skidMap = make(map[int64]int64)
sidMap = make(map[int64]int64)
for _, v := range skas {
aids = append(aids, v.AID)
pidMap[v.AID] = v.PID
skidMap[v.AID] = v.SkID
sidMap[v.AID] = v.SID
}
if total, err = s.aca.SkillArcCount(c, pids, skids, sids); err != nil {
log.Error("s.aca.SkillArcCount pids(%+v)|skids(%+v)|sids(%+v)|error(%v)", pids, skids, sids, err)
return
}
res.Page.Total = total
var (
g, _ = errgroup.WithContext(c)
arcInfo map[int64]*api.Arc
as map[int64]*api.Stat
)
g.Go(func() error {
arcInfo, err = s.arc.Archives(c, aids, "")
if err != nil {
log.Error("s.arc.Archives aids(%+v)|error(%v)", aids, err)
}
return err
})
g.Go(func() error {
as, err = s.arc.Stats(c, aids, "")
if err != nil {
log.Error("s.arc.Stats aids(%+v)|error(%v)", aids, err)
}
return err
})
if g.Wait() != nil {
log.Error("s.aca.ThemeCourse g.Wait() error(%v)", err)
return
}
items := make([]*academy.ArcMeta, 0, len(aids))
for _, aid := range aids {
v, ok := arcInfo[aid]
if !ok || v == nil {
log.Error("ThemeCourse bind ArcInfo aid(%d) error", aid)
return
}
a := &academy.ArcMeta{
AID: aid,
Cover: v.Pic,
Title: v.Title,
Type: v.TypeName,
MID: v.Author.Mid,
Duration: v.Duration,
Skill: &academy.SkillArc{},
Business: 1, //技能树只有视频
}
if st, ok := as[aid]; ok {
a.ArcStat = st
} else {
a.ArcStat = &api.Stat{}
}
if pid, ok := pidMap[aid]; ok {
a.Skill.PID = pid
}
if skid, ok := skidMap[aid]; ok {
a.Skill.SkID = skid
}
if sid, ok := sidMap[aid]; ok {
a.Skill.SID = sid
}
items = append(items, a)
}
res.Items = items
return
}
// ViewPlay view play archive by aid & mid & business.
func (s *Service) ViewPlay(c context.Context, mid, aid int64, bus int8) (play *academy.Play, err error) {
p := &academy.Play{
MID: mid,
AID: aid,
Business: bus,
}
play, err = s.aca.Play(c, p)
if err != nil {
log.Error("ViewPlay s.aca.Play error(%v)", err)
}
return
}
// PlayAdd add play archive by aid & mid.
func (s *Service) PlayAdd(c context.Context, mid, aid int64, bus, watch int8) (id int64, err error) {
py := &academy.Play{
MID: mid,
AID: aid,
Business: bus,
Watch: watch,
CTime: xtime.Time(time.Now().Unix()),
MTime: xtime.Time(time.Now().Unix()),
}
if id, err = s.aca.PlayAdd(c, py); err != nil {
log.Error("s.aca.PlayAdd error(%v)", err)
return
}
s.p.TaskPub(mid, newcomer.MsgForAcademyFavVideo, newcomer.MsgFinishedCount)
return
}
// PlayDel del play archive by aid & mid & business.
func (s *Service) PlayDel(c context.Context, mid, aid int64, bus int8) (id int64, err error) {
p := &academy.Play{
MID: mid,
AID: aid,
Business: bus,
}
play, err := s.aca.Play(c, p)
if err != nil {
log.Error("PlayDel s.aca.Play error(%v)", err)
return
}
if play == nil {
err = ecode.NothingFound
return
}
if id, err = s.aca.PlayDel(c, p); err != nil {
log.Error("s.aca.PlayDel error(%v)", err)
}
return
}
// PlayList get play list.
func (s *Service) PlayList(c context.Context, mid int64, pn, ps int) (res *academy.ArcList, err error) {
var (
pls []*academy.Play
total int
aids, cids []int64
playMap map[int64]*academy.Play
)
res = &academy.ArcList{
Items: []*academy.ArcMeta{},
Page: &academy.ArchivePage{
Pn: pn,
Ps: ps,
},
}
if pls, err = s.aca.Plays(c, mid, (pn-1)*ps, ps); err != nil {
log.Error("s.aca.Plays mid(%d)|error(%v)", mid, err)
return
}
if len(pls) == 0 {
log.Error("s.aca.Plays has no mid(%d)", mid)
return
}
playMap = make(map[int64]*academy.Play)
for _, v := range pls {
if v.Business == 1 {
aids = append(aids, v.AID)
} else if v.Business == 2 {
cids = append(cids, v.AID)
}
playMap[v.AID] = v
}
if total, err = s.aca.PlayCount(c, mid); err != nil {
log.Error("s.aca.PlayCount error(%v)", err)
return
}
res.Page.Total = total
var (
arcs []*academy.ArcMeta
arts []*academy.ArcMeta
g, _ = errgroup.WithContext(c)
)
g.Go(func() error {
arcs, err = s.getArcInfo(c, aids, playMap)
return err
})
g.Go(func() error {
arts, err = s.getArtInfo(c, cids, playMap)
return err
})
if g.Wait() != nil {
log.Error("s.PlayList g.Wait() error(%v)", err)
return
}
tItems := make([]*academy.ArcMeta, 0, len(arcs)+len(arts))
tItems = append(tItems, arcs...)
tItems = append(tItems, arts...)
sort.Slice(tItems, func(i, j int) bool { //按播放时间倒序
return tItems[i].PlayTime > tItems[j].PlayTime
})
unReadItems := make([]*academy.ArcMeta, 0)
readItems := make([]*academy.ArcMeta, 0)
for _, v := range tItems {
if v.Watch == 1 {
unReadItems = append(unReadItems, v)
} else if v.Watch == 2 {
readItems = append(readItems, v)
}
}
res.Items = append(res.Items, unReadItems...) //未观看最先展示
res.Items = append(res.Items, readItems...)
return
}
func (s *Service) getArcInfo(c context.Context, aids []int64, playMap map[int64]*academy.Play) (items []*academy.ArcMeta, err error) {
arcs, err := s.arc.Archives(c, aids, "")
if err != nil {
log.Error("s.arc.Archives aids(%+v)|error(%v)", aids, err)
return
}
items = make([]*academy.ArcMeta, 0, len(aids))
for _, aid := range aids {
v, ok := arcs[aid]
if !ok || v == nil {
log.Error("PlayList bind ArcInfo aid(%d) error", aid)
return
}
a := &academy.ArcMeta{
AID: aid,
Cover: v.Pic,
Title: v.Title,
Type: v.TypeName,
MID: v.Author.Mid,
Duration: v.Duration,
}
if p, ok := playMap[aid]; ok {
a.PlayTime = p.MTime
a.Watch = p.Watch
a.Business = p.Business
}
items = append(items, a)
}
return
}
func (s *Service) getArtInfo(c context.Context, cids []int64, playMap map[int64]*academy.Play) (items []*academy.ArcMeta, err error) {
arts, err := s.art.ArticleMetas(c, cids, "")
if err != nil {
log.Error("s.arc.ArticleMetas cids(%+v) error(%v)", cids, err)
return
}
items = make([]*academy.ArcMeta, 0, len(cids))
for _, cid := range cids {
v, ok := arts[cid]
if !ok || v == nil {
log.Error("PlayList bind ArtInfo cid(%d) error", cid)
return
}
a := &academy.ArcMeta{
AID: cid,
Title: v.Title,
MID: v.Author.Mid,
}
if v.Category != nil {
a.Type = v.Category.Name
}
if len(v.ImageURLs) > 0 {
a.Cover = v.ImageURLs[0]
}
if p, ok := playMap[cid]; ok {
a.PlayTime = p.MTime
a.Watch = p.Watch
a.Business = p.Business
}
items = append(items, a)
}
return
}
// ProfessionSkill get theme course.
func (s *Service) ProfessionSkill(c context.Context, pids, skids, sids []int64, pn, ps int, isnew bool) (res []*academy.NewbCourseList, err error) {
var (
tcs *academy.ArcList
skMap map[int64]string
aids []int64
)
// 取消分页默认最大获取100条
tcs, err = s.ThemeCourse(c, pids, skids, sids, 1, 100, false)
if err != nil {
log.Error("ProfessionSkill s.ThemeCourse pids(%+v)|skids(%+v)|sids(%+v)|error(%v)", pids, skids, sids, err)
return
}
if len(tcs.Items) == 0 {
return
}
skMap = make(map[int64]string)
for _, v := range s.SkillCache {
skids = append(skids, v.ID)
skMap[v.ID] = v.Name
}
// add tags
for _, v := range tcs.Items {
if v == nil {
continue
}
aids = append(aids, v.AID)
}
tags, err := s.getTags(c, aids)
if err != nil {
log.Error("ProfessionSkill s.getTags err(%v)", err)
return
}
s.setTags(tcs.Items, tags)
newbCourseMap := make(map[int64][]*academy.ArcMeta)
for _, v := range tcs.Items {
if v == nil || v.Skill == nil {
continue
}
newbCourseMap[v.Skill.SkID] = append(newbCourseMap[v.Skill.SkID], v)
}
res = make([]*academy.NewbCourseList, 0)
for _, id := range skids {
l := &academy.NewbCourseList{}
if v, ok := newbCourseMap[id]; ok {
l.Items = v
}
if sname, ok := skMap[id]; ok {
l.Title = sname
l.TID = id
}
if len(l.Items) == 0 {
continue
}
res = append(res, l)
}
return
}
// Keywords get keywords.
func (s *Service) Keywords(c context.Context) (res []interface{}) {
res = s.KWsCache
return
}