123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602 |
- 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)
- }
|