go-common/app/interface/openplatform/article/service/creation.go
2019-04-22 18:49:16 +08:00

603 lines
16 KiB
Go

package service
import (
"context"
"sort"
"go-common/app/interface/openplatform/article/dao"
"go-common/app/interface/openplatform/article/model"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/sync/errgroup"
)
const (
_sortDefault = 0
_sortByCtime = 1
_sortByLike = 2
_sortByReply = 3
_sortByView = 4
_sortByFavorite = 5
_sortByCoin = 6
//_stateCancel 取消活动
// _stateCancel = -1
//_stateJoin 参加活动
_stateJoin = 0
_editTimes = 1
)
// checkPrivilege check that whether user has permission to write article.
func (s *Service) checkPrivilege(c context.Context, mid int64) (err error) {
if res, _, e := s.IsAuthor(c, mid); !res {
err = ecode.ArtCreationNoPrivilege
if e != nil {
dao.PromError("creation:检查作者权限")
}
}
return
}
func (s *Service) checkArtAuthor(c context.Context, aid, mid int64) (am *model.Meta, err error) {
if am, err = s.creationArticleMeta(c, aid); err != nil {
return
} else if am == nil {
err = ecode.NothingFound
return
}
if am.Author.Mid != mid {
err = ecode.ArtCreationMIDErr
}
return
}
// CreationArticle get creation article
func (s *Service) creationArticleMeta(c context.Context, aid int64) (am *model.Meta, err error) {
if am, err = s.dao.CreationArticleMeta(c, aid); err != nil {
dao.PromError("creation:获取文章meta")
return
} else if am == nil {
return
}
if s.categoriesMap[am.Category.ID] != nil {
am.Category = s.categoriesMap[am.Category.ID]
}
var author *model.Author
if author, _ = s.author(c, am.Author.Mid); author != nil {
am.Author = author
}
return
}
// CreationArticle .
func (s *Service) CreationArticle(c context.Context, aid, mid int64) (a *model.Article, err error) {
if err = s.checkPrivilege(c, mid); err != nil {
return
}
var (
content string
am *model.Meta
)
a = &model.Article{}
if am, err = s.checkArtAuthor(c, aid, mid); err != nil {
return
}
if am.State == model.StateRePending || am.State == model.StateReReject {
if a, err = s.ArticleVersion(c, aid); err != nil {
return
}
a.Author = am.Author
a.Category = s.categoriesMap[a.Category.ID]
a.List, _ = s.dao.ArtList(c, aid)
a.Stats, _ = s.stat(c, aid)
return
}
group, errCtx := errgroup.WithContext(c)
group.Go(func() (err error) {
content, err = s.dao.CreationArticleContent(c, aid)
return
})
group.Go(func() (err error) {
am.Stats, _ = s.stat(errCtx, aid)
return
})
group.Go(func() (err error) {
am.Tags, _ = s.Tags(errCtx, aid, true)
return
})
group.Go(func() (err error) {
am.List, _ = s.dao.ArtList(errCtx, aid)
return
})
if err = group.Wait(); err != nil {
return
}
a.Meta = am
a.Content = content
log.Info("s.CreationArticle() aid(%d) title(%s) content length(%d)", a.ID, a.Title, len(a.Content))
return
}
// AddArticle adds article.
func (s *Service) AddArticle(c context.Context, a *model.Article, actID, listID int64, ip string) (id int64, err error) {
log.Info("s.AddArticle() aid(%d) title(%s) actID(%d) content length(%d)", a.ID, a.Title, actID, len(a.Content))
defer func() {
if err != nil && err != ecode.CreativeArticleCanNotRepeat {
s.dao.DelSubmitCache(c, a.Author.Mid, a.Title)
}
}()
if err = s.checkPrivilege(c, a.Author.Mid); err != nil {
return
}
a.Content = xssFilter(a.Content)
if err = s.preArticleCheck(c, a); err != nil {
return
}
var num int
if num, err = s.ArticleRemainCount(c, a.Author.Mid); err != nil {
return
} else if num <= 0 {
err = ecode.ArtCreationArticleFull
return
}
if s.checkList(c, a.Author.Mid, listID); err != nil {
return
}
a.State = model.StatePending
var tx *sql.Tx
if tx, err = s.dao.BeginTran(c); err != nil {
log.Error("tx.BeginTran() error(%+v)", err)
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback() error(%+v)", err1)
}
return
}
if err = tx.Commit(); err != nil {
dao.PromError("creation:添加文章")
log.Error("tx.Commit() error(%+v)", err)
return
}
// id is article id
if e1 := s.creativeAddArticleList(c, a.Meta.Author.Mid, listID, id, false); e1 != nil {
dao.PromError("creation:添加文章绑定文集")
log.Errorv(c, log.KV("log", "creativeAddArticleList"), log.KV("mid", a.Meta.Author.Mid), log.KV("listID", listID), log.KV("article_id", id), log.KV("error", err))
}
}()
// del draft
if err = s.dao.TxDeleteArticleDraft(c, tx, a.Author.Mid, a.ID); err != nil {
dao.PromError("creation:删除草稿")
return
}
if id, err = s.dao.TxAddArticleMeta(c, tx, a.Meta, actID); err != nil {
dao.PromError("creation:删除文章meta")
return
}
keywords, _ := s.Segment(c, int32(id), a.Content, 1, "article")
if err = s.dao.TxAddArticleContent(c, tx, id, a.Content, keywords); err != nil {
dao.PromError("creation:添加文章content")
return
}
// add version
if err = s.dao.TxAddArticleVersion(c, tx, id, a, actID); err != nil {
dao.PromError("creation:添加历史记录")
return
}
if actID > 0 {
if e := s.dao.HandleActivity(c, a.Author.Mid, id, actID, _stateJoin, ip); e != nil {
log.Error("creation: s.act.HandleActivity mid(%d) aid(%d) actID(%d) ip(%s) error(%+v)", a.Author.Mid, id, actID, ip, e)
}
}
var tags []string
for _, t := range a.Tags {
tags = append(tags, t.Name)
}
if err = s.BindTags(c, a.Meta.Author.Mid, id, tags, ip, actID); err != nil {
dao.PromError("creation:发布文章绑定tag")
}
return
}
// UpdateArticle update article.
func (s *Service) UpdateArticle(c context.Context, a *model.Article, actID, listID int64, ip string) (err error) {
log.Info("s.UpdateArticle() aid(%d) title(%s) content length(%d)", a.ID, a.Title, len(a.Content))
if err = s.checkPrivilege(c, a.Author.Mid); err != nil {
return
}
a.Content = xssFilter(a.Content)
if err = s.preArticleCheck(c, a); err != nil {
return
}
a.State = model.StatePending
if a.ID <= 0 {
err = ecode.ArtCreationIDErr
return
}
if _, err = s.checkArtAuthor(c, a.ID, a.Author.Mid); err != nil {
return
}
//这里转换成 -2 2 几种状态
if err = s.convertState(c, a); err != nil {
return
}
if a.State == model.StateRePending {
err = s.updateArticleVersion(c, a, actID)
return
}
if err = s.updateArticleDB(c, a); err != nil {
return
}
var tags []string
for _, t := range a.Tags {
tags = append(tags, t.Name)
}
s.BindTags(c, a.Meta.Author.Mid, a.Meta.ID, tags, ip, actID)
s.CreativeUpdateArticleList(c, a.Meta.Author.Mid, a.ID, listID, false)
return
}
func (s *Service) updateArticleVersion(c context.Context, a *model.Article, actID int64) (err error) {
var tx *sql.Tx
if tx, err = s.dao.BeginTran(c); err != nil {
log.Error("updateArticleVersion.tx.BeginTran() error(%+v)", err)
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("updateArticleVersion.tx.Rollback() error(%+v)", err1)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("updateArticleVersion.tx.Commit() error(%+v)", err)
return
}
}()
if err = s.dao.TxUpdateArticleStateApplyTime(c, tx, a.ID, a.State); err != nil {
dao.PromError("creation:修改文章状态")
return
}
if x, e := s.ArticleVersion(c, a.ID); e == nil && x.ID != 0 {
if err = s.dao.TxUpdateArticleVersion(c, tx, a.ID, a, actID); err != nil {
dao.PromError("creation:更新版本")
}
} else {
if err = s.dao.TxAddArticleVersion(c, tx, a.ID, a, actID); err != nil {
dao.PromError("creation:创建版本")
}
}
// if err = s.dao.TxUpdateArticleVersion(c, tx, a.ID, a, actID); err != nil {
// dao.PromError("creation:更新版本")
// }
return
}
func (s *Service) updateArticleDB(c context.Context, a *model.Article) (err error) {
var tx *sql.Tx
if tx, err = s.dao.BeginTran(c); err != nil {
log.Error("tx.BeginTran() error(%+v)", err)
return
}
if err = s.dao.TxUpdateArticleMeta(c, tx, a.Meta); err != nil {
if err1 := tx.Rollback(); err1 != nil {
dao.PromError("creation:更新文章meta")
log.Error("tx.Rollback() error(%+v)", err1)
}
return
}
keywords, _ := s.Segment(c, int32(a.ID), a.Content, 1, "article")
if err = s.dao.TxUpdateArticleContent(c, tx, a.ID, a.Content, keywords); err != nil {
if err1 := tx.Rollback(); err1 != nil {
dao.PromError("creation:更新文章content")
log.Error("tx.Rollback() error(%+v)", err1)
}
return
}
// add version
if err = s.dao.TxUpdateArticleVersion(c, tx, a.ID, a, 0); err != nil {
dao.PromError("creation:添加历史记录")
return
}
if err = tx.Commit(); err != nil {
dao.PromError("creation:更新文章")
log.Error("tx.Commit() error(%+v)", err)
}
return
}
// DelArticle drops article.
func (s *Service) DelArticle(c context.Context, aid, mid int64) (err error) {
if aid <= 0 {
err = ecode.ArtCreationIDErr
return
}
if err = s.checkPrivilege(c, mid); err != nil {
return
}
var a *model.Meta
if a, err = s.checkArtAuthor(c, aid, mid); err != nil {
return
}
// can not delelte article which state is pending.
if a.State == model.StatePending || a.State == model.StateOpenPending {
err = ecode.ArtCreationDelPendingErr
return
}
lists, _ := s.dao.RawArtsListID(c, []int64{aid})
if err = s.delArticle(c, aid); err != nil {
return
}
s.dao.DelActivity(c, aid, "")
cache.Save(func() {
c := context.TODO()
s.dao.DelRecommend(c, aid)
s.DelRecommendArtCache(c, aid, a.Category.ID)
s.DelArticleCache(c, mid, aid)
s.deleteArtsListCache(c, aid)
if lists[aid] > 0 {
s.updateListInfo(c, lists[aid])
s.RebuildListCache(c, lists[aid])
}
})
return
}
func (s *Service) delArticle(c context.Context, aid int64) (err error) {
var tx *sql.Tx
if tx, err = s.dao.BeginTran(c); err != nil {
log.Error("tx.BeginTran() error(%+v)", err)
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback() error(%+v)", err1)
}
return
}
if err = tx.Commit(); err != nil {
dao.PromError("creation:删除文章")
log.Error("tx.Commit() error(%+v)", err)
}
}()
if err = s.dao.TxDeleteArticleMeta(c, tx, aid); err != nil {
dao.PromError("creation:删除文章meta")
return
}
if err = s.dao.TxDeleteArticleContent(c, tx, aid); err != nil {
dao.PromError("creation:delete文章content")
return
}
if err = s.dao.TxDelFilteredArtMeta(c, tx, aid); err != nil {
dao.PromError("creation:删除过滤文章meta")
return
}
if err = s.dao.TxDelFilteredArtContent(c, tx, aid); err != nil {
dao.PromError("creation:删除过滤文章content")
return
}
if err = s.dao.TxDelArticleList(tx, aid); err != nil {
return
}
if err = s.dao.TxDelArticleVersion(c, tx, aid); err != nil {
return
}
return
}
// CreationUpperArticlesMeta gets article list by mid.
func (s *Service) CreationUpperArticlesMeta(c context.Context, mid int64, group, category, sortType, pn, ps int, ip string) (res *model.CreationArts, err error) {
res = &model.CreationArts{}
if err = s.checkPrivilege(c, mid); err != nil {
return
}
var (
aids []int64
ams, as []*model.Meta
stats = make(map[int64]*model.Stats)
start = (pn - 1) * ps
end = start + ps - 1
total int
)
res.Page = &model.ArtPage{
Pn: pn,
Ps: ps,
}
eg, errCtx := errgroup.WithContext(c)
eg.Go(func() (err error) {
res.Type, _ = s.dao.UpperArticlesTypeCount(errCtx, mid)
return
})
eg.Go(func() (err error) {
if ams, err = s.dao.UpperArticlesMeta(errCtx, mid, group, category); err != nil {
return
}
total = len(ams)
res.Page.Total = total
return
})
if err = eg.Wait(); err != nil {
log.Error("eg.Wait() error(%+v)", err)
return
}
if total == 0 || total < start {
return
}
for _, v := range ams {
aids = append(aids, v.ID)
}
if stats, err = s.stats(c, aids); err != nil {
dao.PromError("creation:获取计数信息")
log.Error("s.stats(%v) err: %+v", aids, err)
err = nil
}
for _, v := range ams {
// stats
if st := stats[v.ID]; st != nil {
v.Stats = st
} else {
v.Stats = new(model.Stats)
}
}
switch sortType {
case _sortDefault, _sortByCtime:
sort.Slice(ams, func(i, j int) bool { return ams[i].Ctime > ams[j].Ctime })
case _sortByLike:
sort.Slice(ams, func(i, j int) bool { return ams[i].Stats.Like > ams[j].Stats.Like })
case _sortByReply:
sort.Slice(ams, func(i, j int) bool { return ams[i].Stats.Reply > ams[j].Stats.Reply })
case _sortByView:
sort.Slice(ams, func(i, j int) bool { return ams[i].Stats.View > ams[j].Stats.View })
case _sortByFavorite:
sort.Slice(ams, func(i, j int) bool { return ams[i].Stats.Favorite > ams[j].Stats.Favorite })
case _sortByCoin:
sort.Slice(ams, func(i, j int) bool { return ams[i].Stats.Coin > ams[j].Stats.Coin })
}
if total > end {
as = ams[start : end+1]
} else {
as = ams[start:]
}
for _, v := range as {
var pid int64
if pid, err = s.CategoryToRoot(v.Category.ID); err != nil {
dao.PromError("creation:获取根分区")
log.Error("s.CategoryToRoot(%d) error(%+v)", v.Category.ID, err)
continue
}
v.Category = s.categoriesMap[pid]
v.List, _ = s.dao.ArtList(c, v.ID)
}
res.Articles = as
return
}
// convertState converts -1 to 1, -2 to 2 if the article had been published.
func (s *Service) convertState(c context.Context, a *model.Article) (err error) {
var am *model.Meta
if am, err = s.dao.CreationArticleMeta(c, a.ID); err != nil {
return
}
if am.State == model.StateOpen || am.State == model.StateRePass || am.State == model.StateReReject {
if s.EditTimes(c, a.ID) <= 0 {
err = ecode.ArtUpdateFullErr
return
}
a.State = model.StateRePending
return
}
if (am.State != model.StateReject) && (am.State != model.StateOpenReject) {
err = ecode.ArtCannotEditErr
return
}
if am.State > 0 && a.State < 0 {
a.State = -a.State
}
return
}
// CreationWithdrawArticle recall the article and add it to draft.
func (s *Service) CreationWithdrawArticle(c context.Context, mid, aid int64) (err error) {
if err = s.checkPrivilege(c, mid); err != nil {
return
}
var am *model.Meta
if am, err = s.checkArtAuthor(c, aid, mid); err != nil {
return
}
// 只有初次提交待审的文章才可以撤回,发布后的修改待审不允许撤回
if am.State != model.StatePending {
err = ecode.ArtCreationStateErr
return
}
var content string
if content, err = s.dao.CreationArticleContent(c, aid); err != nil {
return
}
// add draft
var tags []string
if ts, e := s.Tags(c, aid, false); e == nil && len(ts) > 0 {
for _, v := range ts {
tags = append(tags, v.Name)
}
}
am.ID = 0
draft := &model.Draft{Article: &model.Article{Meta: am, Content: content}, Tags: tags}
if _, err = s.AddArtDraft(c, draft); err != nil {
return
}
// delete article
err = s.delArticle(c, aid)
// err = s.dao.UpdateArticleState(c, aid, model.StateOpen)
return
}
// UpStat up stat
func (s *Service) UpStat(c context.Context, mid int64) (res model.UpStat, err error) {
return s.dao.UpStat(c, mid)
}
// UpThirtyDayStat for 30 days stat.
func (s *Service) UpThirtyDayStat(c context.Context, mid int64) (res []*model.ThirtyDayArticle, err error) {
res, err = s.dao.ThirtyDayArticle(c, mid)
return
}
// ArticleUpCover article upload cover.
func (s *Service) ArticleUpCover(c context.Context, fileType string, body []byte) (url string, err error) {
if len(body) == 0 {
err = ecode.FileNotExists
return
}
if len(body) > s.c.BFS.MaxFileSize {
err = ecode.FileTooLarge
return
}
url, err = s.dao.UploadImage(c, fileType, body)
if err != nil {
log.Error("creation: s.bfs.Upload error(%v)", err)
}
return
}
// ArticleVersion .
func (s *Service) ArticleVersion(c context.Context, aid int64) (a *model.Article, err error) {
if a, err = s.dao.ArticleVersion(c, aid); err != nil {
return
}
a.Category = s.categoriesMap[a.Category.ID]
return
}
// EditTimes .
func (s *Service) EditTimes(c context.Context, id int64) (res int) {
var (
et = _editTimes
count = et
err error
)
if s.c.Article.EditTimes != 0 {
et = s.c.Article.EditTimes
}
if count, err = s.dao.EditTimes(c, id); err != nil {
return
}
res = et - count
if res < 0 {
res = 0
}
return
}
func (s *Service) lastReason(c context.Context, id int64, state int32) (res string, err error) {
return s.dao.LastReason(c, id, state)
}