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.
1156 lines
28 KiB
1156 lines
28 KiB
package service |
|
|
|
import ( |
|
"context" |
|
"errors" |
|
"sort" |
|
"strconv" |
|
"sync" |
|
|
|
"go-common/app/interface/openplatform/article/dao" |
|
artmdl "go-common/app/interface/openplatform/article/model" |
|
account "go-common/app/service/main/account/model" |
|
"go-common/library/ecode" |
|
"go-common/library/log" |
|
|
|
"go-common/library/sync/errgroup" |
|
) |
|
|
|
var _moreNum = 3 |
|
|
|
// AddArticleCache adds artmdl. |
|
func (s *Service) AddArticleCache(c context.Context, aid int64) (err error) { |
|
var a *artmdl.Article |
|
if a, err = s.dao.Article(c, aid); err != nil { |
|
dao.PromError("article:新增文章缓存获取文章") |
|
return |
|
} |
|
if a == nil { |
|
dao.PromError("article:新增文章缓存文章未找到") |
|
log.Error("s.Article(%d) is blank", aid) |
|
return |
|
} |
|
group, errCtx := errgroup.WithContext(c) |
|
group.Go(func() error { |
|
return s.dao.AddArticlesMetaCache(errCtx, a.Meta) |
|
}) |
|
group.Go(func() error { |
|
return s.dao.AddArticleContentCache(errCtx, aid, a.Content) |
|
}) |
|
group.Go(func() error { |
|
if a.Keywords == "" { |
|
return s.dao.AddArticleKeywordsCache(errCtx, aid, a.Summary) |
|
} |
|
return s.dao.AddArticleKeywordsCache(errCtx, aid, a.Keywords) |
|
}) |
|
group.Go(func() (err error) { |
|
return s.addUpperCache(errCtx, a.Author.Mid, aid, int64(a.PublishTime)) |
|
}) |
|
group.Go(func() (err error) { |
|
return s.addArtSortCache(errCtx, a.Meta) |
|
}) |
|
group.Go(func() (err error) { |
|
return s.AddCacheHotspotArt(c, s.metaToSearch(c, a.Meta)) |
|
}) |
|
group.Go(func() (err error) { |
|
if lid, _ := s.rebuildArticleListCache(c, aid); lid > 0 { |
|
s.updateListInfo(c, lid) |
|
return s.RebuildListCache(c, lid) |
|
} |
|
return |
|
}) |
|
if err = group.Wait(); err != nil { |
|
log.Errorv(c, log.KV("log", "AddArticleCache"), log.KV("error", err), log.KV("msg", "group.Wait()")) |
|
dao.PromError("article:添加文章缓存") |
|
} |
|
return |
|
} |
|
|
|
// UpdateArticleCache adds artmdl. |
|
func (s *Service) UpdateArticleCache(c context.Context, aid, oldCid int64) (err error) { |
|
var a *artmdl.Article |
|
if a, err = s.dao.Article(c, aid); err != nil { |
|
dao.PromError("article:更新文章缓存获取文章") |
|
return |
|
} |
|
if a == nil { |
|
return |
|
} |
|
group, errCtx := errgroup.WithContext(c) |
|
group.Go(func() error { |
|
return s.dao.AddArticlesMetaCache(errCtx, a.Meta) |
|
}) |
|
group.Go(func() error { |
|
return s.dao.AddArticleContentCache(errCtx, aid, a.Content) |
|
}) |
|
group.Go(func() error { |
|
if a.Keywords == "" { |
|
return s.dao.AddArticleKeywordsCache(errCtx, aid, a.Summary) |
|
} |
|
return s.dao.AddArticleKeywordsCache(errCtx, aid, a.Keywords) |
|
}) |
|
group.Go(func() error { |
|
if a.AttrVal(artmdl.AttrBitNoDistribute) { |
|
return s.dao.DelUpperCache(errCtx, a.Meta.Author.Mid, aid) |
|
} |
|
return s.addUpperCache(errCtx, a.Meta.Author.Mid, aid, int64(a.Meta.PublishTime)) |
|
}) |
|
group.Go(func() (err error) { |
|
if err = s.DelCacheHotspotArt(c, aid); err != nil { |
|
return |
|
} |
|
if !a.AttrVal(artmdl.AttrBitNoDistribute) { |
|
return s.AddCacheHotspotArt(c, s.metaToSearch(c, a.Meta)) |
|
} |
|
return |
|
}) |
|
group.Go(func() (err error) { |
|
var lid int64 |
|
if lid, err = s.rebuildArticleListCache(c, aid); lid > 0 { |
|
s.updateListInfo(c, lid) |
|
err = s.RebuildListCache(c, lid) |
|
} |
|
return |
|
}) |
|
group.Go(func() (err error) { |
|
var root, oldRoot int64 |
|
if root, err = s.CategoryToRoot(a.Category.ID); err != nil { |
|
dao.PromError("article:更新文章缓存查找分类") |
|
log.Error("s.CategoryToRoot(%d,%d) error(%+v)", aid, a.Category.ID, err) |
|
return |
|
} |
|
if oldCid == a.Category.ID { |
|
return nil |
|
} |
|
if oldRoot, err = s.CategoryToRoot(oldCid); err != nil { |
|
dao.PromError("article:更新文章缓存查找分类") |
|
log.Error("s.CategoryToRoot(%d,%d) error(%+v)", aid, oldCid, err) |
|
return |
|
} |
|
cids := []int64{oldCid} |
|
if root != oldRoot { |
|
cids = append(cids, oldRoot) |
|
} |
|
if err = s.delArtSortCacheFromCid(errCtx, aid, cids...); err != nil { |
|
return |
|
} |
|
return s.addArtSortCache(errCtx, a.Meta) |
|
}) |
|
if err = group.Wait(); err != nil { |
|
log.Errorv(c, log.KV("log", "UpdateArticleCache"), log.KV("error", err), log.KV("msg", "group.Wait()")) |
|
dao.PromError("article:更新文章缓存") |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) addUpperCache(c context.Context, mid, aid, ptime int64) (err error) { |
|
var ( |
|
exists map[int64]bool |
|
arts map[int64][][2]int64 |
|
) |
|
if exists, err = s.dao.ExpireUppersCache(c, []int64{mid}); err != nil { |
|
return |
|
} |
|
if exists[mid] { |
|
return s.dao.AddUpperCache(c, mid, aid, ptime) |
|
} |
|
if arts, err = s.dao.UppersPassed(c, []int64{mid}); err != nil { |
|
dao.PromError("article:新增文章缓存获取up过审") |
|
return |
|
} |
|
return s.dao.AddUpperCaches(c, arts) |
|
} |
|
|
|
//RootCategory 找到一级分区 |
|
func (s *Service) RootCategory(c context.Context, aid int64) (root int64, cid int64, err error) { |
|
var art *artmdl.Meta |
|
if art, err = s.dao.AllArticleMeta(c, aid); err != nil { |
|
return |
|
} |
|
if art == nil { |
|
err = ecode.NothingFound |
|
return |
|
} |
|
cid = art.Category.ID |
|
root, err = s.CategoryToRoot(art.Category.ID) |
|
return |
|
} |
|
|
|
// CategoryToRoot 找到一级分区 |
|
func (s *Service) CategoryToRoot(cid int64) (res int64, err error) { |
|
for (s.categoriesMap[cid] != nil) && (s.categoriesMap[cid].ParentID != _recommendCategory) { |
|
cid = s.categoriesMap[cid].ParentID |
|
} |
|
if (s.categoriesMap[cid] == nil) || (s.categoriesMap[cid].ParentID != _recommendCategory) { |
|
err = ecode.ArtNoCategory |
|
return |
|
} |
|
res = cid |
|
return |
|
} |
|
|
|
// DelArticleCache deletes artmdl. |
|
func (s *Service) DelArticleCache(c context.Context, mid, aid int64) (err error) { |
|
group, errCtx := errgroup.WithContext(c) |
|
group.Go(func() error { |
|
return s.dao.DelUpperCache(errCtx, mid, aid) |
|
}) |
|
group.Go(func() error { |
|
return s.dao.DelArticleMetaCache(errCtx, aid) |
|
}) |
|
group.Go(func() error { |
|
return s.dao.DelArticleContentCache(errCtx, aid) |
|
}) |
|
group.Go(func() error { |
|
return s.DelCacheHotspotArt(c, aid) |
|
}) |
|
group.Go(func() error { |
|
return s.dao.DelArticleStatsCache(errCtx, aid) |
|
}) |
|
group.Go(func() (err error) { |
|
err = s.delArtSortCache(errCtx, aid) |
|
return |
|
}) |
|
group.Go(func() (err error) { |
|
var lid int64 |
|
if lid, err = s.rebuildArticleListCache(c, aid); lid > 0 { |
|
s.updateListInfo(c, lid) |
|
err = s.RebuildListCache(c, lid) |
|
} |
|
return |
|
}) |
|
if err = group.Wait(); err != nil { |
|
log.Errorv(c, log.KV("log", "DelArticleCache"), log.KV("error", err), log.KV("msg", "group.Wait()")) |
|
dao.PromError("article:删除文章缓存") |
|
} |
|
return |
|
} |
|
|
|
// Article get article |
|
func (s *Service) Article(c context.Context, id int64) (res *artmdl.Article, err error) { |
|
var am *artmdl.Meta |
|
if am, err = s.ArticleMeta(c, id); err != nil || am == nil { |
|
return |
|
} |
|
res = &artmdl.Article{Meta: am} |
|
if res.Content, err = s.content(c, id); err != nil { |
|
return |
|
} |
|
res.Keywords = s.keywords(c, id, res.Summary) |
|
if res.Content == "" { |
|
dao.PromError("article:文章内容为空") |
|
log.Error("s.Article(%v) content is blank", id) |
|
} |
|
s.media(c, am) |
|
log.Info("s.Article() aid(%d) title(%s) content length(%d)", res.ID, res.Title, len(res.Content)) |
|
return |
|
} |
|
|
|
// MediaCategory . |
|
func (s *Service) MediaCategory(c context.Context, mediaID int64, mid int64) (cg *artmdl.Category, err error) { |
|
var ( |
|
res *artmdl.MediaResult |
|
cid int64 |
|
) |
|
if mediaID == 0 { |
|
return |
|
} |
|
if res, err = s.dao.Media(c, mediaID, mid); err != nil { |
|
log.Error("s.MediaCategory(%d) get media info failed: %v", mediaID, err) |
|
dao.PromError("article:番剧信息获取失败") |
|
return |
|
} |
|
if res.Media.TypeID > 4 || res.Media.TypeID == 0 || len(s.c.Article.Media) < 5 { |
|
err = errors.New("番剧类型错误或者未配置番剧类别") |
|
return |
|
} |
|
cid = s.c.Article.Media[res.Media.TypeID] |
|
cg = s.categoriesMap[cid] |
|
return |
|
} |
|
|
|
func (s *Service) media(c context.Context, am *artmdl.Meta) { |
|
var ( |
|
res *artmdl.MediaResult |
|
err error |
|
) |
|
if am.Media == nil || am.Media.MediaID == 0 { |
|
return |
|
} |
|
if res, err = s.dao.Media(c, am.Media.MediaID, am.Author.Mid); err != nil { |
|
log.Error("s.media(%d) get media info failed: %v", am.Media.MediaID, err) |
|
dao.PromError("article:番剧信息获取失败") |
|
return |
|
} |
|
am.Media.MediaID = res.Media.MediaID |
|
am.Media.Score = res.Score |
|
am.Media.Title = res.Media.Title |
|
am.Media.Cover = res.Media.Cover |
|
am.Media.Area = res.Media.Area |
|
am.Media.TypeID = res.Media.TypeID |
|
am.Media.TypeName = res.Media.TypeName |
|
return |
|
} |
|
|
|
// ArticleMeta gets article's meta. |
|
func (s *Service) ArticleMeta(c context.Context, aid int64) (res *artmdl.Meta, err error) { |
|
var addCache = true |
|
if res, err = s.dao.ArticleMetaCache(c, aid); err != nil { |
|
addCache = false |
|
err = nil |
|
} |
|
if res == nil { |
|
if res, err = s.dao.ArticleMeta(c, aid); err != nil || res == nil { |
|
return |
|
} |
|
} |
|
if s.categoriesMap[res.Category.ID] != nil { |
|
res.Category = s.categoriesMap[res.Category.ID] |
|
res.Categories = s.categoryParents[res.Category.ID] |
|
} |
|
group := &errgroup.Group{} |
|
// get author |
|
group.Go(func() error { |
|
var author *artmdl.Author |
|
if author, _ = s.author(c, res.Author.Mid); author != nil { |
|
res.Author = author |
|
} |
|
return nil |
|
}) |
|
// get stats |
|
group.Go(func() error { |
|
var stat *artmdl.Stats |
|
if stat, _ = s.stat(c, aid); stat != nil { |
|
res.Stats = stat |
|
return nil |
|
} |
|
if res.Stats == nil { |
|
res.Stats = new(artmdl.Stats) |
|
} |
|
return nil |
|
}) |
|
// get tag |
|
group.Go(func() error { |
|
var tags []*artmdl.Tag |
|
if tags, _ = s.Tags(c, aid, false); len(tags) > 0 { |
|
res.Tags = tags |
|
return nil |
|
} |
|
if len(res.Tags) == 0 { |
|
res.Tags = []*artmdl.Tag{} |
|
} |
|
return nil |
|
}) |
|
// get list |
|
group.Go(func() (err error) { |
|
lists, _ := s.dao.ArtsList(c, []int64{aid}) |
|
res.List = lists[aid] |
|
return |
|
}) |
|
group.Wait() |
|
if addCache { |
|
cache.Save(func() { s.dao.AddArticlesMetaCache(context.TODO(), res) }) |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) accountInfo(c context.Context, mid int64) (info *account.Card, err error) { |
|
var ( |
|
arg = &account.ArgMid{Mid: mid} |
|
) |
|
if info, err = s.accountRPC.Card3(c, arg); err != nil { |
|
dao.PromError("article:获取作者信息") |
|
log.Error("s.accountRPC.Card3(%+v) error(%+v)", arg, err) |
|
return |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) author(c context.Context, mid int64) (res *artmdl.Author, err error) { |
|
var ( |
|
card *account.Card |
|
arg = &account.ArgMid{Mid: mid} |
|
) |
|
if card, err = s.accountRPC.Card3(c, arg); err != nil { |
|
dao.PromError("article:获取作者信息") |
|
log.Error("s.accountRPC.Info(%+v) error(%+v)", arg, err) |
|
return |
|
} |
|
res = &artmdl.Author{ |
|
Mid: mid, |
|
Name: card.Name, |
|
Face: card.Face, |
|
Pendant: artmdl.Pendant{ |
|
Pid: int32(card.Pendant.Pid), |
|
Name: card.Pendant.Name, |
|
Image: card.Pendant.Image, |
|
Expire: int32(card.Pendant.Expire), |
|
}, |
|
Nameplate: artmdl.Nameplate{ |
|
Nid: card.Nameplate.Nid, |
|
Name: card.Nameplate.Name, |
|
Image: card.Nameplate.Image, |
|
ImageSmall: card.Nameplate.ImageSmall, |
|
Level: card.Nameplate.Level, |
|
Condition: card.Nameplate.Condition, |
|
}, |
|
Vip: artmdl.VipInfo{ |
|
Type: card.Vip.Type, |
|
Status: card.Vip.Status, |
|
}, |
|
} |
|
if card.Official.Role == 0 { |
|
res.OfficialVerify.Type = -1 |
|
} else { |
|
if card.Official.Role <= 2 { |
|
res.OfficialVerify.Type = 0 |
|
} else { |
|
res.OfficialVerify.Type = 1 |
|
} |
|
res.OfficialVerify.Desc = card.Official.Title |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) authors(c context.Context, mids []int64) (res map[int64]*artmdl.Author, err error) { |
|
res = make(map[int64]*artmdl.Author) |
|
if len(mids) == 0 { |
|
return |
|
} |
|
var ( |
|
cards map[int64]*account.Card |
|
arg = &account.ArgMids{Mids: mids} |
|
) |
|
if cards, err = s.accountRPC.Cards3(c, arg); err != nil { |
|
dao.PromError("article:批量获取作者信息") |
|
log.Error("s.accountRPC.Infos(%+v) error(%+v)", arg, err) |
|
return |
|
} |
|
for mid, card := range cards { |
|
au := &artmdl.Author{ |
|
Mid: mid, |
|
Name: card.Name, |
|
Face: card.Face, |
|
Pendant: artmdl.Pendant{ |
|
Pid: int32(card.Pendant.Pid), |
|
Name: card.Pendant.Name, |
|
Image: card.Pendant.Image, |
|
Expire: int32(card.Pendant.Expire), |
|
}, |
|
Nameplate: artmdl.Nameplate{ |
|
Nid: card.Nameplate.Nid, |
|
Name: card.Nameplate.Name, |
|
Image: card.Nameplate.Image, |
|
ImageSmall: card.Nameplate.ImageSmall, |
|
Level: card.Nameplate.Level, |
|
Condition: card.Nameplate.Condition, |
|
}, |
|
Vip: artmdl.VipInfo{ |
|
Type: card.Vip.Type, |
|
Status: card.Vip.Status, |
|
}, |
|
} |
|
if card.Official.Role == 0 { |
|
au.OfficialVerify.Type = -1 |
|
} else { |
|
if card.Official.Role <= 2 { |
|
au.OfficialVerify.Type = 0 |
|
} else { |
|
au.OfficialVerify.Type = 1 |
|
} |
|
au.OfficialVerify.Desc = card.Official.Title |
|
} |
|
res[mid] = au |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) authorDetail(c context.Context, mid int64) (res *artmdl.Author, err error) { |
|
var ( |
|
card *account.Card |
|
arg = &account.ArgMid{Mid: mid} |
|
) |
|
if card, err = s.accountRPC.Card3(c, arg); err != nil { |
|
dao.PromError("article:单个获取作者信息") |
|
log.Error("s.accountRPC.Info(%+v) error(%+v)", arg, err) |
|
return |
|
} |
|
if card == nil { |
|
return |
|
} |
|
res = &artmdl.Author{ |
|
Mid: mid, |
|
Name: card.Name, |
|
Face: card.Face, |
|
Pendant: artmdl.Pendant{ |
|
Pid: int32(card.Pendant.Pid), |
|
Name: card.Pendant.Name, |
|
Image: card.Pendant.Image, |
|
Expire: int32(card.Pendant.Expire), |
|
}, |
|
Nameplate: artmdl.Nameplate{ |
|
Nid: card.Nameplate.Nid, |
|
Name: card.Nameplate.Name, |
|
Image: card.Nameplate.Image, |
|
ImageSmall: card.Nameplate.ImageSmall, |
|
Level: card.Nameplate.Level, |
|
Condition: card.Nameplate.Condition, |
|
}, |
|
} |
|
if card.Official.Role == 0 { |
|
res.OfficialVerify.Type = -1 |
|
} else { |
|
if card.Official.Role <= 2 { |
|
res.OfficialVerify.Type = 0 |
|
} else { |
|
res.OfficialVerify.Type = 1 |
|
} |
|
res.OfficialVerify.Desc = card.Official.Title |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) content(c context.Context, aid int64) (res string, err error) { |
|
var addCache = true |
|
if res, err = s.dao.ArticleContentCache(c, aid); err != nil { |
|
addCache = false |
|
err = nil |
|
} else if res != "" { |
|
return |
|
} |
|
if res, err = s.dao.ArticleContent(c, aid); err != nil { |
|
dao.PromError("article:稿件内容") |
|
return |
|
} |
|
if addCache && res != "" { |
|
cache.Save(func() { |
|
s.dao.AddArticleContentCache(context.TODO(), aid, res) |
|
}) |
|
} |
|
return |
|
} |
|
|
|
// ListCategories list categories |
|
func (s *Service) ListCategories(c context.Context, ip string) (res artmdl.Categories, err error) { |
|
if len(s.Categories) == 0 { |
|
err = ecode.NothingFound |
|
return |
|
} |
|
res = s.Categories |
|
return |
|
} |
|
|
|
// ListCategoriesMap list category map |
|
func (s *Service) ListCategoriesMap(c context.Context, ip string) (res map[int64]*artmdl.Category, err error) { |
|
if len(s.Categories) == 0 { |
|
err = ecode.NothingFound |
|
return |
|
} |
|
res = s.categoriesMap |
|
return |
|
} |
|
|
|
// ArticleMetas get article meta |
|
func (s *Service) ArticleMetas(c context.Context, ids []int64) (res map[int64]*artmdl.Meta, err error) { |
|
var ( |
|
addCache = true |
|
group *errgroup.Group |
|
cachedMetas, missedMetas map[int64]*artmdl.Meta |
|
missedMetaIDs, resIDs []int64 |
|
mutex = &sync.Mutex{} |
|
) |
|
res = make(map[int64]*artmdl.Meta) |
|
// get meta |
|
if cachedMetas, missedMetaIDs, err = s.dao.ArticlesMetaCache(c, ids); err != nil { |
|
addCache = false |
|
err = nil |
|
} |
|
if len(missedMetaIDs) > 0 { |
|
missedMetas, _ = s.dao.ArticleMetas(c, missedMetaIDs) |
|
} |
|
// 合并缓存和回源的数据 |
|
for id, artm := range cachedMetas { |
|
res[id] = artm |
|
resIDs = append(resIDs, id) |
|
} |
|
for id, artm := range missedMetas { |
|
res[id] = artm |
|
resIDs = append(resIDs, id) |
|
} |
|
// 更新分类 |
|
for id, art := range res { |
|
if art.Category == nil { |
|
continue |
|
} |
|
if s.categoriesMap[art.Category.ID] != nil { |
|
res[id].Category = s.categoriesMap[art.Category.ID] |
|
res[id].Categories = s.categoryParents[art.Category.ID] |
|
} |
|
} |
|
group = &errgroup.Group{} |
|
// get author |
|
group.Go(func() (err error) { |
|
var ( |
|
mids []int64 |
|
authors map[int64]*artmdl.Author |
|
authorsMap = make(map[int64]bool) |
|
) |
|
for _, art := range res { |
|
authorsMap[art.Author.Mid] = true |
|
} |
|
for id := range authorsMap { |
|
mids = append(mids, id) |
|
} |
|
if authors, err = s.authors(c, mids); err != nil { |
|
dao.PromError("article:稿件获取作者信息") |
|
err = nil |
|
return |
|
} |
|
mutex.Lock() |
|
for _, art := range res { |
|
author := authors[art.Author.Mid] |
|
if author != nil { |
|
art.Author = author |
|
} |
|
} |
|
mutex.Unlock() |
|
return |
|
}) |
|
//get stats |
|
group.Go(func() (err error) { |
|
stats, _ := s.stats(c, resIDs) |
|
mutex.Lock() |
|
for id := range res { |
|
s := stats[id] |
|
if s == nil { |
|
s = new(artmdl.Stats) |
|
} |
|
res[id].Stats = s |
|
} |
|
mutex.Unlock() |
|
return |
|
}) |
|
// get list |
|
group.Go(func() (err error) { |
|
lists, _ := s.dao.ArtsList(c, resIDs) |
|
mutex.Lock() |
|
for id := range res { |
|
res[id].List = lists[id] |
|
} |
|
mutex.Unlock() |
|
return |
|
}) |
|
group.Wait() |
|
if addCache && len(missedMetas) > 0 { |
|
cache.Save(func() { |
|
for _, art := range missedMetas { |
|
s.dao.AddArticlesMetaCache(context.TODO(), art) |
|
} |
|
}) |
|
} |
|
return |
|
} |
|
|
|
func filterNoDistributeArts(as []*artmdl.Meta) (res []*artmdl.Meta) { |
|
for _, a := range as { |
|
if (a != nil) && !a.AttrVal(artmdl.AttrBitNoDistribute) { |
|
res = append(res, a) |
|
} |
|
} |
|
return |
|
} |
|
|
|
func filterNoDistributeArtsMap(as map[int64]*artmdl.Meta) { |
|
for id, a := range as { |
|
if (a != nil) && a.AttrVal(artmdl.AttrBitNoDistribute) { |
|
delete(as, id) |
|
} |
|
} |
|
} |
|
|
|
// AddArtContentCache add article content cache |
|
func (s *Service) AddArtContentCache(c context.Context, aid int64, content string) (err error) { |
|
if content == "" { |
|
return |
|
} |
|
err = s.dao.AddArticleContentCache(c, aid, content) |
|
return |
|
} |
|
|
|
// ArticleRemainCount returns the number that user could be use to posting new articles. |
|
func (s *Service) ArticleRemainCount(c context.Context, mid int64) (num int, err error) { |
|
if mid <= 0 { |
|
return |
|
} |
|
var count, limit int |
|
if count, err = s.dao.ArticleRemainCount(c, mid); err != nil { |
|
return |
|
} |
|
author, _ := s.dao.Author(c, mid) |
|
if author != nil { |
|
limit = author.Limit |
|
} |
|
if limit == 0 { |
|
limit = s.c.Article.UpperArticleLimit |
|
} |
|
if count > limit { |
|
return |
|
} |
|
num = limit - count |
|
return |
|
} |
|
|
|
// AddComplaint add complaint. |
|
func (s *Service) AddComplaint(c context.Context, aid, mid, ctype int64, reason, imageUrls, ip string) (err error) { |
|
var exist, protected bool |
|
if exist, err = s.dao.ComplaintExist(c, aid, mid); (err != nil) || exist { |
|
return |
|
} |
|
if err = s.dao.AddComplaint(c, aid, mid, ctype, reason, imageUrls); err != nil { |
|
return |
|
} |
|
if protected, err = s.dao.ComplaintProtected(c, aid); err != nil || protected { |
|
return |
|
} |
|
err = s.dao.AddComplaintCount(c, aid) |
|
return |
|
} |
|
|
|
// MoreArts get author's more articles. |
|
func (s *Service) MoreArts(c context.Context, aid int64) (res []*artmdl.Meta, err error) { |
|
var am *artmdl.Meta |
|
if am, err = s.ArticleMeta(c, aid); err != nil { |
|
dao.PromError("article:获取文章meta") |
|
return |
|
} |
|
if am == nil || am.Author == nil { |
|
return |
|
} |
|
var ( |
|
exist bool |
|
beforeAids, afterAids, tmpAids []int64 |
|
aidTimes [][2]int64 |
|
addCache = true |
|
tmpRes map[int64]*artmdl.Meta |
|
mid = am.Author.Mid |
|
) |
|
if exist, err = s.dao.ExpireUpperCache(c, mid); err != nil { |
|
addCache = false |
|
err = nil |
|
} else if exist { |
|
if beforeAids, afterAids, err = s.dao.MoreArtsCaches(c, mid, int64(am.PublishTime), _moreNum+4); err != nil { |
|
addCache = false |
|
exist = false |
|
} |
|
} |
|
if !exist { |
|
if aidTimes, err = s.dao.UpperPassed(c, mid); err != nil { |
|
return |
|
} |
|
if addCache { |
|
cache.Save(func() { |
|
s.dao.AddUpperCaches(context.TODO(), map[int64][][2]int64{mid: aidTimes}) |
|
}) |
|
} |
|
for _, aidTime := range aidTimes { |
|
tmpAids = append(tmpAids, aidTime[0]) |
|
} |
|
beforeAids, afterAids = splitAids(tmpAids, aid) |
|
} |
|
if len(beforeAids)+len(afterAids) == 0 { |
|
return |
|
} |
|
tmpAids = append([]int64{}, beforeAids...) |
|
tmpAids = append(tmpAids, afterAids...) |
|
if tmpRes, err = s.ArticleMetas(c, tmpAids); err != nil { |
|
return |
|
} |
|
filterNoDistributeArtsMap(tmpRes) |
|
res = fmtMoreArts(beforeAids, afterAids, tmpRes) |
|
return |
|
} |
|
|
|
func splitAids(aids []int64, aid int64) (beforeAids []int64, afterAids []int64) { |
|
position := -1 |
|
for i, a := range aids { |
|
if a == aid { |
|
position = i |
|
break |
|
} |
|
} |
|
if position == -1 { |
|
return |
|
} |
|
l := len(aids) |
|
if position+_moreNum > l { |
|
beforeAids = aids[position+1 : l] |
|
} else { |
|
beforeAids = aids[position+1 : position+_moreNum] |
|
} |
|
if position-_moreNum < 0 { |
|
afterAids = aids[0:position] |
|
} else { |
|
afterAids = aids[position-_moreNum : position] |
|
} |
|
return |
|
} |
|
|
|
// 位置说明: 按照ptime逆序: afterAids -> (aid) -> beforeAids, after取一个 before取2个 |
|
func fmtMoreArts(beforeAids, afterAids []int64, tmpRes map[int64]*artmdl.Meta) (res []*artmdl.Meta) { |
|
var before, after []*artmdl.Meta |
|
for _, aid := range beforeAids { |
|
if v := tmpRes[aid]; v != nil { |
|
before = append(before, v) |
|
} |
|
} |
|
sort.Sort(artmdl.Metas(before)) |
|
for _, aid := range afterAids { |
|
if v := tmpRes[aid]; v != nil { |
|
after = append(after, v) |
|
} |
|
} |
|
sort.Sort(artmdl.Metas(after)) |
|
if len(after) > _moreNum { |
|
after = after[len(after)-_moreNum:] |
|
} |
|
if len(before) > _moreNum { |
|
before = before[:_moreNum] |
|
} |
|
lenAfter := len(after) |
|
lenBefore := len(before) |
|
// 正常逻辑: 前一个后三个 |
|
if (lenAfter > 0) && (lenBefore > 1) { |
|
res = []*artmdl.Meta{after[lenAfter-1]} |
|
res = append(res, before[:2]...) |
|
} else if lenAfter == 0 { |
|
// 前面不够 |
|
res = before |
|
} else if lenBefore == 0 { |
|
// 后面不够 |
|
res = after |
|
} else { |
|
//前面补全后面缺失的 |
|
if lenAfter-(_moreNum-lenBefore) > 0 { |
|
res = append(res, after[lenAfter-(_moreNum-lenBefore):]...) |
|
} else { |
|
res = append(res, after...) |
|
} |
|
res = append(res, before...) |
|
} |
|
return |
|
} |
|
|
|
// NewArticleCount get new article count |
|
func (s *Service) NewArticleCount(c context.Context, ptime int64) (res int64, err error) { |
|
res, err = s.dao.NewArticleCount(c, ptime) |
|
return |
|
} |
|
|
|
// Mores get more articles |
|
func (s *Service) Mores(c context.Context, aid, loginMID int64) (res artmdl.MoreArts, err error) { |
|
var art *artmdl.Meta |
|
group := &errgroup.Group{} |
|
if art, err = s.ArticleMeta(c, aid); (err != nil) || (art == nil) || (!art.IsNormal()) { |
|
err = ecode.NothingFound |
|
return |
|
} |
|
res.Author = &artmdl.AccountCard{Mid: strconv.FormatInt(art.Author.Mid, 10), Face: art.Author.Face, Name: art.Author.Name} |
|
mid := art.Author.Mid |
|
group.Go(func() (e error) { |
|
var profile *account.ProfileStat |
|
if profile, e = s.accountRPC.ProfileWithStat3(c, &account.ArgMid{Mid: mid}); e != nil { |
|
dao.PromError("article:Card3") |
|
log.Error("s.acc.Card3(%d) error %v", mid, e) |
|
return |
|
} |
|
if profile != nil { |
|
res.Author.FromProfileStat(profile) |
|
} |
|
return |
|
}) |
|
group.Go(func() (e error) { |
|
if res.Articles, e = s.similars(c, aid, art.Category.ID); e != nil { |
|
dao.PromError("article:similars") |
|
} |
|
if res.Articles != nil && len(res.Articles) > 0 { |
|
return |
|
} |
|
if res.Articles, e = s.MoreArts(c, aid); e != nil { |
|
dao.PromError("article:MoreArts") |
|
} |
|
if res.Articles == nil { |
|
res.Articles = []*artmdl.Meta{} |
|
} |
|
return |
|
}) |
|
group.Go(func() (e error) { |
|
if res.Total, e = s.UpperArtsCount(c, mid); e != nil { |
|
dao.PromError("article:获取作者文章数") |
|
} |
|
return |
|
}) |
|
group.Go(func() error { |
|
// read count |
|
if stat, e := s.dao.CacheUpStatDaily(c, mid); e != nil { |
|
dao.PromError("article:CacheUpStatDaily") |
|
} else if stat != nil { |
|
res.ReadCount = stat.View |
|
return nil |
|
} |
|
if stat, e := s.dao.UpStat(c, mid); e != nil { |
|
dao.PromError("article:获取作者文章数") |
|
} else { |
|
res.ReadCount = stat.View |
|
} |
|
return nil |
|
}) |
|
group.Go(func() (e error) { |
|
if loginMID == 0 { |
|
return |
|
} |
|
if res.Attention, e = s.isAttention(c, loginMID, mid); e != nil { |
|
dao.PromError("article:获取作者文章数") |
|
} |
|
return |
|
}) |
|
group.Wait() |
|
return |
|
} |
|
|
|
// FeedArticleMetas . |
|
func (s *Service) FeedArticleMetas(c context.Context, ids []int64) (res map[int64]*artmdl.Meta, err error) { |
|
var ( |
|
addCache = true |
|
group *errgroup.Group |
|
cachedMetas, missedMetas map[int64]*artmdl.Meta |
|
missedMetaIDs, resIDs []int64 |
|
mutex = &sync.Mutex{} |
|
) |
|
res = make(map[int64]*artmdl.Meta) |
|
// get meta |
|
if cachedMetas, missedMetaIDs, err = s.dao.ArticlesMetaCache(c, ids); err != nil { |
|
addCache = false |
|
err = nil |
|
} |
|
if len(missedMetaIDs) > 0 { |
|
missedMetas, _ = s.dao.ArticleMetas(c, missedMetaIDs) |
|
} |
|
// 合并缓存和回源的数据 |
|
for id, artm := range cachedMetas { |
|
res[id] = artm |
|
resIDs = append(resIDs, id) |
|
} |
|
for id, artm := range missedMetas { |
|
res[id] = artm |
|
resIDs = append(resIDs, id) |
|
} |
|
// 更新分类 |
|
for id, art := range res { |
|
if art.Category == nil { |
|
continue |
|
} |
|
if s.categoriesMap[art.Category.ID] != nil { |
|
res[id].Category = s.categoriesMap[art.Category.ID] |
|
res[id].Categories = s.categoryParents[art.Category.ID] |
|
} |
|
} |
|
group = &errgroup.Group{} |
|
// get author |
|
group.Go(func() (err error) { |
|
var ( |
|
mids []int64 |
|
authors map[int64]*artmdl.Author |
|
authorsMap = make(map[int64]bool) |
|
) |
|
for _, art := range missedMetas { |
|
authorsMap[art.Author.Mid] = true |
|
} |
|
for id := range authorsMap { |
|
mids = append(mids, id) |
|
} |
|
if authors, err = s.authors(c, mids); err != nil { |
|
dao.PromError("article:稿件获取作者信息") |
|
err = nil |
|
return |
|
} |
|
mutex.Lock() |
|
for _, art := range missedMetas { |
|
author := authors[art.Author.Mid] |
|
if author != nil { |
|
art.Author = author |
|
} |
|
} |
|
mutex.Unlock() |
|
return |
|
}) |
|
//get stats |
|
group.Go(func() (err error) { |
|
stats, _ := s.stats(c, resIDs) |
|
mutex.Lock() |
|
for id := range res { |
|
s := stats[id] |
|
if s == nil { |
|
s = new(artmdl.Stats) |
|
} |
|
res[id].Stats = s |
|
} |
|
mutex.Unlock() |
|
return |
|
}) |
|
group.Wait() |
|
if addCache && len(missedMetas) > 0 { |
|
cache.Save(func() { |
|
for _, art := range missedMetas { |
|
s.dao.AddArticlesMetaCache(context.TODO(), art) |
|
} |
|
}) |
|
} |
|
return |
|
} |
|
|
|
// AddCheatFilter . |
|
func (s *Service) AddCheatFilter(c context.Context, aid int64, lv int) (err error) { |
|
return s.dao.AddCheatFilter(c, aid, lv) |
|
} |
|
|
|
// DelCheatFilter . |
|
func (s *Service) DelCheatFilter(c context.Context, aid int64) (err error) { |
|
return s.dao.DelCheatFilter(c, aid) |
|
} |
|
|
|
// similars . |
|
func (s *Service) similars(c context.Context, aid int64, cid int64) (res []*artmdl.Meta, err error) { |
|
var ( |
|
tags []*artmdl.Tag |
|
aidst = make(map[int64]bool) |
|
aidsr []int64 |
|
aidsa = make(map[int64]bool) |
|
tmps []int64 |
|
aids []int64 |
|
id int64 |
|
meta *artmdl.Meta |
|
metas map[int64]*artmdl.Meta |
|
nils []int64 |
|
) |
|
if tags, err = s.Tags(c, aid, false); err != nil { |
|
return |
|
} |
|
for _, tag := range tags { |
|
var tagArts *artmdl.TagArts |
|
if tagArts, err = s.dao.CacheAidsByTag(c, tag.Tid); err != nil { |
|
return |
|
} |
|
if tagArts == nil { |
|
nils = append(nils, tag.Tid) |
|
continue |
|
} |
|
for _, id = range tagArts.Aids { |
|
aidst[id] = true |
|
} |
|
} |
|
if len(nils) > 0 { |
|
if tmps, err = s.dao.TagArticles(c, nils); err != nil { |
|
return |
|
} |
|
for _, id = range tmps { |
|
aidst[id] = true |
|
} |
|
} |
|
delete(aidst, aid) |
|
aidsr = s.getRecommentsGroups(c, cid, aid) |
|
if len(aidsr) == 0 && len(aidst) == 0 { |
|
return |
|
} |
|
if len(aidsr) == 0 { |
|
for id = range aidst { |
|
aids = append(aids, id) |
|
if len(aids) > 10 { |
|
break |
|
} |
|
} |
|
if metas, err = s.ArticleMetas(c, aids); err != nil { |
|
return |
|
} |
|
for _, meta := range metas { |
|
if artmdl.NoDistributeAttr(meta.Attributes) || artmdl.NoRegionAttr(meta.Attributes) { |
|
continue |
|
} |
|
res = append(res, meta) |
|
if len(res) == 3 { |
|
break |
|
} |
|
} |
|
return |
|
} |
|
for _, id = range aidsr { |
|
aids = append(aids, id) |
|
aidsa[id] = true |
|
} |
|
for id = range aidst { |
|
aidsa[id] = true |
|
} |
|
if metas, err = s.ArticleMetas(c, aids); err != nil { |
|
return |
|
} |
|
for _, meta = range metas { |
|
res = append(res, meta) |
|
delete(aidsa, meta.ID) |
|
break |
|
} |
|
tmps = []int64{} |
|
for id = range aidsa { |
|
tmps = append(tmps, id) |
|
|
|
if len(tmps) > 10 { |
|
break |
|
} |
|
} |
|
if metas, err = s.ArticleMetas(c, tmps); err != nil { |
|
return |
|
} |
|
for _, meta := range metas { |
|
if artmdl.NoDistributeAttr(meta.Attributes) || artmdl.NoRegionAttr(meta.Attributes) { |
|
continue |
|
} |
|
res = append(res, meta) |
|
if len(res) == 3 { |
|
break |
|
} |
|
} |
|
return |
|
} |
|
|
|
// MediaArticle . |
|
func (s *Service) MediaArticle(c context.Context, mediaID, mid int64) (id int64, err error) { |
|
return s.dao.MediaArticle(c, mediaID, mid) |
|
} |
|
|
|
// MediaIDByID . |
|
func (s *Service) MediaIDByID(c context.Context, aid int64) (id int64, err error) { |
|
return s.dao.MediaIDByID(c, aid) |
|
} |
|
|
|
func (s *Service) keywords(c context.Context, id int64, summary string) (keywords string) { |
|
var ( |
|
err error |
|
addCache = true |
|
) |
|
if keywords, err = s.dao.ArticleKeywordsCache(c, id); keywords != "" { |
|
return |
|
} |
|
if err != nil { |
|
addCache = false |
|
} |
|
if keywords, err = s.dao.ArticleKeywords(c, id); err != nil { |
|
dao.PromError("article:文章关键词") |
|
} |
|
if keywords == "" { |
|
keywords = summary |
|
} |
|
if addCache { |
|
cache.Save(func() { |
|
s.dao.AddArticleKeywordsCache(context.TODO(), id, keywords) |
|
}) |
|
} |
|
return |
|
}
|
|
|