creation.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. package service
  2. import (
  3. "context"
  4. "sort"
  5. "go-common/app/interface/openplatform/article/dao"
  6. "go-common/app/interface/openplatform/article/model"
  7. "go-common/library/database/sql"
  8. "go-common/library/ecode"
  9. "go-common/library/log"
  10. "go-common/library/sync/errgroup"
  11. )
  12. const (
  13. _sortDefault = 0
  14. _sortByCtime = 1
  15. _sortByLike = 2
  16. _sortByReply = 3
  17. _sortByView = 4
  18. _sortByFavorite = 5
  19. _sortByCoin = 6
  20. //_stateCancel 取消活动
  21. // _stateCancel = -1
  22. //_stateJoin 参加活动
  23. _stateJoin = 0
  24. _editTimes = 1
  25. )
  26. // checkPrivilege check that whether user has permission to write article.
  27. func (s *Service) checkPrivilege(c context.Context, mid int64) (err error) {
  28. if res, _, e := s.IsAuthor(c, mid); !res {
  29. err = ecode.ArtCreationNoPrivilege
  30. if e != nil {
  31. dao.PromError("creation:检查作者权限")
  32. }
  33. }
  34. return
  35. }
  36. func (s *Service) checkArtAuthor(c context.Context, aid, mid int64) (am *model.Meta, err error) {
  37. if am, err = s.creationArticleMeta(c, aid); err != nil {
  38. return
  39. } else if am == nil {
  40. err = ecode.NothingFound
  41. return
  42. }
  43. if am.Author.Mid != mid {
  44. err = ecode.ArtCreationMIDErr
  45. }
  46. return
  47. }
  48. // CreationArticle get creation article
  49. func (s *Service) creationArticleMeta(c context.Context, aid int64) (am *model.Meta, err error) {
  50. if am, err = s.dao.CreationArticleMeta(c, aid); err != nil {
  51. dao.PromError("creation:获取文章meta")
  52. return
  53. } else if am == nil {
  54. return
  55. }
  56. if s.categoriesMap[am.Category.ID] != nil {
  57. am.Category = s.categoriesMap[am.Category.ID]
  58. }
  59. var author *model.Author
  60. if author, _ = s.author(c, am.Author.Mid); author != nil {
  61. am.Author = author
  62. }
  63. return
  64. }
  65. // CreationArticle .
  66. func (s *Service) CreationArticle(c context.Context, aid, mid int64) (a *model.Article, err error) {
  67. if err = s.checkPrivilege(c, mid); err != nil {
  68. return
  69. }
  70. var (
  71. content string
  72. am *model.Meta
  73. )
  74. a = &model.Article{}
  75. if am, err = s.checkArtAuthor(c, aid, mid); err != nil {
  76. return
  77. }
  78. if am.State == model.StateRePending || am.State == model.StateReReject {
  79. if a, err = s.ArticleVersion(c, aid); err != nil {
  80. return
  81. }
  82. a.Author = am.Author
  83. a.Category = s.categoriesMap[a.Category.ID]
  84. a.List, _ = s.dao.ArtList(c, aid)
  85. a.Stats, _ = s.stat(c, aid)
  86. return
  87. }
  88. group, errCtx := errgroup.WithContext(c)
  89. group.Go(func() (err error) {
  90. content, err = s.dao.CreationArticleContent(c, aid)
  91. return
  92. })
  93. group.Go(func() (err error) {
  94. am.Stats, _ = s.stat(errCtx, aid)
  95. return
  96. })
  97. group.Go(func() (err error) {
  98. am.Tags, _ = s.Tags(errCtx, aid, true)
  99. return
  100. })
  101. group.Go(func() (err error) {
  102. am.List, _ = s.dao.ArtList(errCtx, aid)
  103. return
  104. })
  105. if err = group.Wait(); err != nil {
  106. return
  107. }
  108. a.Meta = am
  109. a.Content = content
  110. log.Info("s.CreationArticle() aid(%d) title(%s) content length(%d)", a.ID, a.Title, len(a.Content))
  111. return
  112. }
  113. // AddArticle adds article.
  114. func (s *Service) AddArticle(c context.Context, a *model.Article, actID, listID int64, ip string) (id int64, err error) {
  115. log.Info("s.AddArticle() aid(%d) title(%s) actID(%d) content length(%d)", a.ID, a.Title, actID, len(a.Content))
  116. defer func() {
  117. if err != nil && err != ecode.CreativeArticleCanNotRepeat {
  118. s.dao.DelSubmitCache(c, a.Author.Mid, a.Title)
  119. }
  120. }()
  121. if err = s.checkPrivilege(c, a.Author.Mid); err != nil {
  122. return
  123. }
  124. a.Content = xssFilter(a.Content)
  125. if err = s.preArticleCheck(c, a); err != nil {
  126. return
  127. }
  128. var num int
  129. if num, err = s.ArticleRemainCount(c, a.Author.Mid); err != nil {
  130. return
  131. } else if num <= 0 {
  132. err = ecode.ArtCreationArticleFull
  133. return
  134. }
  135. if s.checkList(c, a.Author.Mid, listID); err != nil {
  136. return
  137. }
  138. a.State = model.StatePending
  139. var tx *sql.Tx
  140. if tx, err = s.dao.BeginTran(c); err != nil {
  141. log.Error("tx.BeginTran() error(%+v)", err)
  142. return
  143. }
  144. defer func() {
  145. if err != nil {
  146. if err1 := tx.Rollback(); err1 != nil {
  147. log.Error("tx.Rollback() error(%+v)", err1)
  148. }
  149. return
  150. }
  151. if err = tx.Commit(); err != nil {
  152. dao.PromError("creation:添加文章")
  153. log.Error("tx.Commit() error(%+v)", err)
  154. return
  155. }
  156. // id is article id
  157. if e1 := s.creativeAddArticleList(c, a.Meta.Author.Mid, listID, id, false); e1 != nil {
  158. dao.PromError("creation:添加文章绑定文集")
  159. 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))
  160. }
  161. }()
  162. // del draft
  163. if err = s.dao.TxDeleteArticleDraft(c, tx, a.Author.Mid, a.ID); err != nil {
  164. dao.PromError("creation:删除草稿")
  165. return
  166. }
  167. if id, err = s.dao.TxAddArticleMeta(c, tx, a.Meta, actID); err != nil {
  168. dao.PromError("creation:删除文章meta")
  169. return
  170. }
  171. keywords, _ := s.Segment(c, int32(id), a.Content, 1, "article")
  172. if err = s.dao.TxAddArticleContent(c, tx, id, a.Content, keywords); err != nil {
  173. dao.PromError("creation:添加文章content")
  174. return
  175. }
  176. // add version
  177. if err = s.dao.TxAddArticleVersion(c, tx, id, a, actID); err != nil {
  178. dao.PromError("creation:添加历史记录")
  179. return
  180. }
  181. if actID > 0 {
  182. if e := s.dao.HandleActivity(c, a.Author.Mid, id, actID, _stateJoin, ip); e != nil {
  183. log.Error("creation: s.act.HandleActivity mid(%d) aid(%d) actID(%d) ip(%s) error(%+v)", a.Author.Mid, id, actID, ip, e)
  184. }
  185. }
  186. var tags []string
  187. for _, t := range a.Tags {
  188. tags = append(tags, t.Name)
  189. }
  190. if err = s.BindTags(c, a.Meta.Author.Mid, id, tags, ip, actID); err != nil {
  191. dao.PromError("creation:发布文章绑定tag")
  192. }
  193. return
  194. }
  195. // UpdateArticle update article.
  196. func (s *Service) UpdateArticle(c context.Context, a *model.Article, actID, listID int64, ip string) (err error) {
  197. log.Info("s.UpdateArticle() aid(%d) title(%s) content length(%d)", a.ID, a.Title, len(a.Content))
  198. if err = s.checkPrivilege(c, a.Author.Mid); err != nil {
  199. return
  200. }
  201. a.Content = xssFilter(a.Content)
  202. if err = s.preArticleCheck(c, a); err != nil {
  203. return
  204. }
  205. a.State = model.StatePending
  206. if a.ID <= 0 {
  207. err = ecode.ArtCreationIDErr
  208. return
  209. }
  210. if _, err = s.checkArtAuthor(c, a.ID, a.Author.Mid); err != nil {
  211. return
  212. }
  213. //这里转换成 -2 2 几种状态
  214. if err = s.convertState(c, a); err != nil {
  215. return
  216. }
  217. if a.State == model.StateRePending {
  218. err = s.updateArticleVersion(c, a, actID)
  219. return
  220. }
  221. if err = s.updateArticleDB(c, a); err != nil {
  222. return
  223. }
  224. var tags []string
  225. for _, t := range a.Tags {
  226. tags = append(tags, t.Name)
  227. }
  228. s.BindTags(c, a.Meta.Author.Mid, a.Meta.ID, tags, ip, actID)
  229. s.CreativeUpdateArticleList(c, a.Meta.Author.Mid, a.ID, listID, false)
  230. return
  231. }
  232. func (s *Service) updateArticleVersion(c context.Context, a *model.Article, actID int64) (err error) {
  233. var tx *sql.Tx
  234. if tx, err = s.dao.BeginTran(c); err != nil {
  235. log.Error("updateArticleVersion.tx.BeginTran() error(%+v)", err)
  236. return
  237. }
  238. defer func() {
  239. if err != nil {
  240. if err1 := tx.Rollback(); err1 != nil {
  241. log.Error("updateArticleVersion.tx.Rollback() error(%+v)", err1)
  242. }
  243. return
  244. }
  245. if err = tx.Commit(); err != nil {
  246. log.Error("updateArticleVersion.tx.Commit() error(%+v)", err)
  247. return
  248. }
  249. }()
  250. if err = s.dao.TxUpdateArticleStateApplyTime(c, tx, a.ID, a.State); err != nil {
  251. dao.PromError("creation:修改文章状态")
  252. return
  253. }
  254. if x, e := s.ArticleVersion(c, a.ID); e == nil && x.ID != 0 {
  255. if err = s.dao.TxUpdateArticleVersion(c, tx, a.ID, a, actID); err != nil {
  256. dao.PromError("creation:更新版本")
  257. }
  258. } else {
  259. if err = s.dao.TxAddArticleVersion(c, tx, a.ID, a, actID); err != nil {
  260. dao.PromError("creation:创建版本")
  261. }
  262. }
  263. // if err = s.dao.TxUpdateArticleVersion(c, tx, a.ID, a, actID); err != nil {
  264. // dao.PromError("creation:更新版本")
  265. // }
  266. return
  267. }
  268. func (s *Service) updateArticleDB(c context.Context, a *model.Article) (err error) {
  269. var tx *sql.Tx
  270. if tx, err = s.dao.BeginTran(c); err != nil {
  271. log.Error("tx.BeginTran() error(%+v)", err)
  272. return
  273. }
  274. if err = s.dao.TxUpdateArticleMeta(c, tx, a.Meta); err != nil {
  275. if err1 := tx.Rollback(); err1 != nil {
  276. dao.PromError("creation:更新文章meta")
  277. log.Error("tx.Rollback() error(%+v)", err1)
  278. }
  279. return
  280. }
  281. keywords, _ := s.Segment(c, int32(a.ID), a.Content, 1, "article")
  282. if err = s.dao.TxUpdateArticleContent(c, tx, a.ID, a.Content, keywords); err != nil {
  283. if err1 := tx.Rollback(); err1 != nil {
  284. dao.PromError("creation:更新文章content")
  285. log.Error("tx.Rollback() error(%+v)", err1)
  286. }
  287. return
  288. }
  289. // add version
  290. if err = s.dao.TxUpdateArticleVersion(c, tx, a.ID, a, 0); err != nil {
  291. dao.PromError("creation:添加历史记录")
  292. return
  293. }
  294. if err = tx.Commit(); err != nil {
  295. dao.PromError("creation:更新文章")
  296. log.Error("tx.Commit() error(%+v)", err)
  297. }
  298. return
  299. }
  300. // DelArticle drops article.
  301. func (s *Service) DelArticle(c context.Context, aid, mid int64) (err error) {
  302. if aid <= 0 {
  303. err = ecode.ArtCreationIDErr
  304. return
  305. }
  306. if err = s.checkPrivilege(c, mid); err != nil {
  307. return
  308. }
  309. var a *model.Meta
  310. if a, err = s.checkArtAuthor(c, aid, mid); err != nil {
  311. return
  312. }
  313. // can not delelte article which state is pending.
  314. if a.State == model.StatePending || a.State == model.StateOpenPending {
  315. err = ecode.ArtCreationDelPendingErr
  316. return
  317. }
  318. lists, _ := s.dao.RawArtsListID(c, []int64{aid})
  319. if err = s.delArticle(c, aid); err != nil {
  320. return
  321. }
  322. s.dao.DelActivity(c, aid, "")
  323. cache.Save(func() {
  324. c := context.TODO()
  325. s.dao.DelRecommend(c, aid)
  326. s.DelRecommendArtCache(c, aid, a.Category.ID)
  327. s.DelArticleCache(c, mid, aid)
  328. s.deleteArtsListCache(c, aid)
  329. if lists[aid] > 0 {
  330. s.updateListInfo(c, lists[aid])
  331. s.RebuildListCache(c, lists[aid])
  332. }
  333. })
  334. return
  335. }
  336. func (s *Service) delArticle(c context.Context, aid int64) (err error) {
  337. var tx *sql.Tx
  338. if tx, err = s.dao.BeginTran(c); err != nil {
  339. log.Error("tx.BeginTran() error(%+v)", err)
  340. return
  341. }
  342. defer func() {
  343. if err != nil {
  344. if err1 := tx.Rollback(); err1 != nil {
  345. log.Error("tx.Rollback() error(%+v)", err1)
  346. }
  347. return
  348. }
  349. if err = tx.Commit(); err != nil {
  350. dao.PromError("creation:删除文章")
  351. log.Error("tx.Commit() error(%+v)", err)
  352. }
  353. }()
  354. if err = s.dao.TxDeleteArticleMeta(c, tx, aid); err != nil {
  355. dao.PromError("creation:删除文章meta")
  356. return
  357. }
  358. if err = s.dao.TxDeleteArticleContent(c, tx, aid); err != nil {
  359. dao.PromError("creation:delete文章content")
  360. return
  361. }
  362. if err = s.dao.TxDelFilteredArtMeta(c, tx, aid); err != nil {
  363. dao.PromError("creation:删除过滤文章meta")
  364. return
  365. }
  366. if err = s.dao.TxDelFilteredArtContent(c, tx, aid); err != nil {
  367. dao.PromError("creation:删除过滤文章content")
  368. return
  369. }
  370. if err = s.dao.TxDelArticleList(tx, aid); err != nil {
  371. return
  372. }
  373. if err = s.dao.TxDelArticleVersion(c, tx, aid); err != nil {
  374. return
  375. }
  376. return
  377. }
  378. // CreationUpperArticlesMeta gets article list by mid.
  379. func (s *Service) CreationUpperArticlesMeta(c context.Context, mid int64, group, category, sortType, pn, ps int, ip string) (res *model.CreationArts, err error) {
  380. res = &model.CreationArts{}
  381. if err = s.checkPrivilege(c, mid); err != nil {
  382. return
  383. }
  384. var (
  385. aids []int64
  386. ams, as []*model.Meta
  387. stats = make(map[int64]*model.Stats)
  388. start = (pn - 1) * ps
  389. end = start + ps - 1
  390. total int
  391. )
  392. res.Page = &model.ArtPage{
  393. Pn: pn,
  394. Ps: ps,
  395. }
  396. eg, errCtx := errgroup.WithContext(c)
  397. eg.Go(func() (err error) {
  398. res.Type, _ = s.dao.UpperArticlesTypeCount(errCtx, mid)
  399. return
  400. })
  401. eg.Go(func() (err error) {
  402. if ams, err = s.dao.UpperArticlesMeta(errCtx, mid, group, category); err != nil {
  403. return
  404. }
  405. total = len(ams)
  406. res.Page.Total = total
  407. return
  408. })
  409. if err = eg.Wait(); err != nil {
  410. log.Error("eg.Wait() error(%+v)", err)
  411. return
  412. }
  413. if total == 0 || total < start {
  414. return
  415. }
  416. for _, v := range ams {
  417. aids = append(aids, v.ID)
  418. }
  419. if stats, err = s.stats(c, aids); err != nil {
  420. dao.PromError("creation:获取计数信息")
  421. log.Error("s.stats(%v) err: %+v", aids, err)
  422. err = nil
  423. }
  424. for _, v := range ams {
  425. // stats
  426. if st := stats[v.ID]; st != nil {
  427. v.Stats = st
  428. } else {
  429. v.Stats = new(model.Stats)
  430. }
  431. }
  432. switch sortType {
  433. case _sortDefault, _sortByCtime:
  434. sort.Slice(ams, func(i, j int) bool { return ams[i].Ctime > ams[j].Ctime })
  435. case _sortByLike:
  436. sort.Slice(ams, func(i, j int) bool { return ams[i].Stats.Like > ams[j].Stats.Like })
  437. case _sortByReply:
  438. sort.Slice(ams, func(i, j int) bool { return ams[i].Stats.Reply > ams[j].Stats.Reply })
  439. case _sortByView:
  440. sort.Slice(ams, func(i, j int) bool { return ams[i].Stats.View > ams[j].Stats.View })
  441. case _sortByFavorite:
  442. sort.Slice(ams, func(i, j int) bool { return ams[i].Stats.Favorite > ams[j].Stats.Favorite })
  443. case _sortByCoin:
  444. sort.Slice(ams, func(i, j int) bool { return ams[i].Stats.Coin > ams[j].Stats.Coin })
  445. }
  446. if total > end {
  447. as = ams[start : end+1]
  448. } else {
  449. as = ams[start:]
  450. }
  451. for _, v := range as {
  452. var pid int64
  453. if pid, err = s.CategoryToRoot(v.Category.ID); err != nil {
  454. dao.PromError("creation:获取根分区")
  455. log.Error("s.CategoryToRoot(%d) error(%+v)", v.Category.ID, err)
  456. continue
  457. }
  458. v.Category = s.categoriesMap[pid]
  459. v.List, _ = s.dao.ArtList(c, v.ID)
  460. }
  461. res.Articles = as
  462. return
  463. }
  464. // convertState converts -1 to 1, -2 to 2 if the article had been published.
  465. func (s *Service) convertState(c context.Context, a *model.Article) (err error) {
  466. var am *model.Meta
  467. if am, err = s.dao.CreationArticleMeta(c, a.ID); err != nil {
  468. return
  469. }
  470. if am.State == model.StateOpen || am.State == model.StateRePass || am.State == model.StateReReject {
  471. if s.EditTimes(c, a.ID) <= 0 {
  472. err = ecode.ArtUpdateFullErr
  473. return
  474. }
  475. a.State = model.StateRePending
  476. return
  477. }
  478. if (am.State != model.StateReject) && (am.State != model.StateOpenReject) {
  479. err = ecode.ArtCannotEditErr
  480. return
  481. }
  482. if am.State > 0 && a.State < 0 {
  483. a.State = -a.State
  484. }
  485. return
  486. }
  487. // CreationWithdrawArticle recall the article and add it to draft.
  488. func (s *Service) CreationWithdrawArticle(c context.Context, mid, aid int64) (err error) {
  489. if err = s.checkPrivilege(c, mid); err != nil {
  490. return
  491. }
  492. var am *model.Meta
  493. if am, err = s.checkArtAuthor(c, aid, mid); err != nil {
  494. return
  495. }
  496. // 只有初次提交待审的文章才可以撤回,发布后的修改待审不允许撤回
  497. if am.State != model.StatePending {
  498. err = ecode.ArtCreationStateErr
  499. return
  500. }
  501. var content string
  502. if content, err = s.dao.CreationArticleContent(c, aid); err != nil {
  503. return
  504. }
  505. // add draft
  506. var tags []string
  507. if ts, e := s.Tags(c, aid, false); e == nil && len(ts) > 0 {
  508. for _, v := range ts {
  509. tags = append(tags, v.Name)
  510. }
  511. }
  512. am.ID = 0
  513. draft := &model.Draft{Article: &model.Article{Meta: am, Content: content}, Tags: tags}
  514. if _, err = s.AddArtDraft(c, draft); err != nil {
  515. return
  516. }
  517. // delete article
  518. err = s.delArticle(c, aid)
  519. // err = s.dao.UpdateArticleState(c, aid, model.StateOpen)
  520. return
  521. }
  522. // UpStat up stat
  523. func (s *Service) UpStat(c context.Context, mid int64) (res model.UpStat, err error) {
  524. return s.dao.UpStat(c, mid)
  525. }
  526. // UpThirtyDayStat for 30 days stat.
  527. func (s *Service) UpThirtyDayStat(c context.Context, mid int64) (res []*model.ThirtyDayArticle, err error) {
  528. res, err = s.dao.ThirtyDayArticle(c, mid)
  529. return
  530. }
  531. // ArticleUpCover article upload cover.
  532. func (s *Service) ArticleUpCover(c context.Context, fileType string, body []byte) (url string, err error) {
  533. if len(body) == 0 {
  534. err = ecode.FileNotExists
  535. return
  536. }
  537. if len(body) > s.c.BFS.MaxFileSize {
  538. err = ecode.FileTooLarge
  539. return
  540. }
  541. url, err = s.dao.UploadImage(c, fileType, body)
  542. if err != nil {
  543. log.Error("creation: s.bfs.Upload error(%v)", err)
  544. }
  545. return
  546. }
  547. // ArticleVersion .
  548. func (s *Service) ArticleVersion(c context.Context, aid int64) (a *model.Article, err error) {
  549. if a, err = s.dao.ArticleVersion(c, aid); err != nil {
  550. return
  551. }
  552. a.Category = s.categoriesMap[a.Category.ID]
  553. return
  554. }
  555. // EditTimes .
  556. func (s *Service) EditTimes(c context.Context, id int64) (res int) {
  557. var (
  558. et = _editTimes
  559. count = et
  560. err error
  561. )
  562. if s.c.Article.EditTimes != 0 {
  563. et = s.c.Article.EditTimes
  564. }
  565. if count, err = s.dao.EditTimes(c, id); err != nil {
  566. return
  567. }
  568. res = et - count
  569. if res < 0 {
  570. res = 0
  571. }
  572. return
  573. }
  574. func (s *Service) lastReason(c context.Context, id int64, state int32) (res string, err error) {
  575. return s.dao.LastReason(c, id, state)
  576. }