article.go 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156
  1. package service
  2. import (
  3. "context"
  4. "errors"
  5. "sort"
  6. "strconv"
  7. "sync"
  8. "go-common/app/interface/openplatform/article/dao"
  9. artmdl "go-common/app/interface/openplatform/article/model"
  10. account "go-common/app/service/main/account/model"
  11. "go-common/library/ecode"
  12. "go-common/library/log"
  13. "go-common/library/sync/errgroup"
  14. )
  15. var _moreNum = 3
  16. // AddArticleCache adds artmdl.
  17. func (s *Service) AddArticleCache(c context.Context, aid int64) (err error) {
  18. var a *artmdl.Article
  19. if a, err = s.dao.Article(c, aid); err != nil {
  20. dao.PromError("article:新增文章缓存获取文章")
  21. return
  22. }
  23. if a == nil {
  24. dao.PromError("article:新增文章缓存文章未找到")
  25. log.Error("s.Article(%d) is blank", aid)
  26. return
  27. }
  28. group, errCtx := errgroup.WithContext(c)
  29. group.Go(func() error {
  30. return s.dao.AddArticlesMetaCache(errCtx, a.Meta)
  31. })
  32. group.Go(func() error {
  33. return s.dao.AddArticleContentCache(errCtx, aid, a.Content)
  34. })
  35. group.Go(func() error {
  36. if a.Keywords == "" {
  37. return s.dao.AddArticleKeywordsCache(errCtx, aid, a.Summary)
  38. }
  39. return s.dao.AddArticleKeywordsCache(errCtx, aid, a.Keywords)
  40. })
  41. group.Go(func() (err error) {
  42. return s.addUpperCache(errCtx, a.Author.Mid, aid, int64(a.PublishTime))
  43. })
  44. group.Go(func() (err error) {
  45. return s.addArtSortCache(errCtx, a.Meta)
  46. })
  47. group.Go(func() (err error) {
  48. return s.AddCacheHotspotArt(c, s.metaToSearch(c, a.Meta))
  49. })
  50. group.Go(func() (err error) {
  51. if lid, _ := s.rebuildArticleListCache(c, aid); lid > 0 {
  52. s.updateListInfo(c, lid)
  53. return s.RebuildListCache(c, lid)
  54. }
  55. return
  56. })
  57. if err = group.Wait(); err != nil {
  58. log.Errorv(c, log.KV("log", "AddArticleCache"), log.KV("error", err), log.KV("msg", "group.Wait()"))
  59. dao.PromError("article:添加文章缓存")
  60. }
  61. return
  62. }
  63. // UpdateArticleCache adds artmdl.
  64. func (s *Service) UpdateArticleCache(c context.Context, aid, oldCid int64) (err error) {
  65. var a *artmdl.Article
  66. if a, err = s.dao.Article(c, aid); err != nil {
  67. dao.PromError("article:更新文章缓存获取文章")
  68. return
  69. }
  70. if a == nil {
  71. return
  72. }
  73. group, errCtx := errgroup.WithContext(c)
  74. group.Go(func() error {
  75. return s.dao.AddArticlesMetaCache(errCtx, a.Meta)
  76. })
  77. group.Go(func() error {
  78. return s.dao.AddArticleContentCache(errCtx, aid, a.Content)
  79. })
  80. group.Go(func() error {
  81. if a.Keywords == "" {
  82. return s.dao.AddArticleKeywordsCache(errCtx, aid, a.Summary)
  83. }
  84. return s.dao.AddArticleKeywordsCache(errCtx, aid, a.Keywords)
  85. })
  86. group.Go(func() error {
  87. if a.AttrVal(artmdl.AttrBitNoDistribute) {
  88. return s.dao.DelUpperCache(errCtx, a.Meta.Author.Mid, aid)
  89. }
  90. return s.addUpperCache(errCtx, a.Meta.Author.Mid, aid, int64(a.Meta.PublishTime))
  91. })
  92. group.Go(func() (err error) {
  93. if err = s.DelCacheHotspotArt(c, aid); err != nil {
  94. return
  95. }
  96. if !a.AttrVal(artmdl.AttrBitNoDistribute) {
  97. return s.AddCacheHotspotArt(c, s.metaToSearch(c, a.Meta))
  98. }
  99. return
  100. })
  101. group.Go(func() (err error) {
  102. var lid int64
  103. if lid, err = s.rebuildArticleListCache(c, aid); lid > 0 {
  104. s.updateListInfo(c, lid)
  105. err = s.RebuildListCache(c, lid)
  106. }
  107. return
  108. })
  109. group.Go(func() (err error) {
  110. var root, oldRoot int64
  111. if root, err = s.CategoryToRoot(a.Category.ID); err != nil {
  112. dao.PromError("article:更新文章缓存查找分类")
  113. log.Error("s.CategoryToRoot(%d,%d) error(%+v)", aid, a.Category.ID, err)
  114. return
  115. }
  116. if oldCid == a.Category.ID {
  117. return nil
  118. }
  119. if oldRoot, err = s.CategoryToRoot(oldCid); err != nil {
  120. dao.PromError("article:更新文章缓存查找分类")
  121. log.Error("s.CategoryToRoot(%d,%d) error(%+v)", aid, oldCid, err)
  122. return
  123. }
  124. cids := []int64{oldCid}
  125. if root != oldRoot {
  126. cids = append(cids, oldRoot)
  127. }
  128. if err = s.delArtSortCacheFromCid(errCtx, aid, cids...); err != nil {
  129. return
  130. }
  131. return s.addArtSortCache(errCtx, a.Meta)
  132. })
  133. if err = group.Wait(); err != nil {
  134. log.Errorv(c, log.KV("log", "UpdateArticleCache"), log.KV("error", err), log.KV("msg", "group.Wait()"))
  135. dao.PromError("article:更新文章缓存")
  136. }
  137. return
  138. }
  139. func (s *Service) addUpperCache(c context.Context, mid, aid, ptime int64) (err error) {
  140. var (
  141. exists map[int64]bool
  142. arts map[int64][][2]int64
  143. )
  144. if exists, err = s.dao.ExpireUppersCache(c, []int64{mid}); err != nil {
  145. return
  146. }
  147. if exists[mid] {
  148. return s.dao.AddUpperCache(c, mid, aid, ptime)
  149. }
  150. if arts, err = s.dao.UppersPassed(c, []int64{mid}); err != nil {
  151. dao.PromError("article:新增文章缓存获取up过审")
  152. return
  153. }
  154. return s.dao.AddUpperCaches(c, arts)
  155. }
  156. //RootCategory 找到一级分区
  157. func (s *Service) RootCategory(c context.Context, aid int64) (root int64, cid int64, err error) {
  158. var art *artmdl.Meta
  159. if art, err = s.dao.AllArticleMeta(c, aid); err != nil {
  160. return
  161. }
  162. if art == nil {
  163. err = ecode.NothingFound
  164. return
  165. }
  166. cid = art.Category.ID
  167. root, err = s.CategoryToRoot(art.Category.ID)
  168. return
  169. }
  170. // CategoryToRoot 找到一级分区
  171. func (s *Service) CategoryToRoot(cid int64) (res int64, err error) {
  172. for (s.categoriesMap[cid] != nil) && (s.categoriesMap[cid].ParentID != _recommendCategory) {
  173. cid = s.categoriesMap[cid].ParentID
  174. }
  175. if (s.categoriesMap[cid] == nil) || (s.categoriesMap[cid].ParentID != _recommendCategory) {
  176. err = ecode.ArtNoCategory
  177. return
  178. }
  179. res = cid
  180. return
  181. }
  182. // DelArticleCache deletes artmdl.
  183. func (s *Service) DelArticleCache(c context.Context, mid, aid int64) (err error) {
  184. group, errCtx := errgroup.WithContext(c)
  185. group.Go(func() error {
  186. return s.dao.DelUpperCache(errCtx, mid, aid)
  187. })
  188. group.Go(func() error {
  189. return s.dao.DelArticleMetaCache(errCtx, aid)
  190. })
  191. group.Go(func() error {
  192. return s.dao.DelArticleContentCache(errCtx, aid)
  193. })
  194. group.Go(func() error {
  195. return s.DelCacheHotspotArt(c, aid)
  196. })
  197. group.Go(func() error {
  198. return s.dao.DelArticleStatsCache(errCtx, aid)
  199. })
  200. group.Go(func() (err error) {
  201. err = s.delArtSortCache(errCtx, aid)
  202. return
  203. })
  204. group.Go(func() (err error) {
  205. var lid int64
  206. if lid, err = s.rebuildArticleListCache(c, aid); lid > 0 {
  207. s.updateListInfo(c, lid)
  208. err = s.RebuildListCache(c, lid)
  209. }
  210. return
  211. })
  212. if err = group.Wait(); err != nil {
  213. log.Errorv(c, log.KV("log", "DelArticleCache"), log.KV("error", err), log.KV("msg", "group.Wait()"))
  214. dao.PromError("article:删除文章缓存")
  215. }
  216. return
  217. }
  218. // Article get article
  219. func (s *Service) Article(c context.Context, id int64) (res *artmdl.Article, err error) {
  220. var am *artmdl.Meta
  221. if am, err = s.ArticleMeta(c, id); err != nil || am == nil {
  222. return
  223. }
  224. res = &artmdl.Article{Meta: am}
  225. if res.Content, err = s.content(c, id); err != nil {
  226. return
  227. }
  228. res.Keywords = s.keywords(c, id, res.Summary)
  229. if res.Content == "" {
  230. dao.PromError("article:文章内容为空")
  231. log.Error("s.Article(%v) content is blank", id)
  232. }
  233. s.media(c, am)
  234. log.Info("s.Article() aid(%d) title(%s) content length(%d)", res.ID, res.Title, len(res.Content))
  235. return
  236. }
  237. // MediaCategory .
  238. func (s *Service) MediaCategory(c context.Context, mediaID int64, mid int64) (cg *artmdl.Category, err error) {
  239. var (
  240. res *artmdl.MediaResult
  241. cid int64
  242. )
  243. if mediaID == 0 {
  244. return
  245. }
  246. if res, err = s.dao.Media(c, mediaID, mid); err != nil {
  247. log.Error("s.MediaCategory(%d) get media info failed: %v", mediaID, err)
  248. dao.PromError("article:番剧信息获取失败")
  249. return
  250. }
  251. if res.Media.TypeID > 4 || res.Media.TypeID == 0 || len(s.c.Article.Media) < 5 {
  252. err = errors.New("番剧类型错误或者未配置番剧类别")
  253. return
  254. }
  255. cid = s.c.Article.Media[res.Media.TypeID]
  256. cg = s.categoriesMap[cid]
  257. return
  258. }
  259. func (s *Service) media(c context.Context, am *artmdl.Meta) {
  260. var (
  261. res *artmdl.MediaResult
  262. err error
  263. )
  264. if am.Media == nil || am.Media.MediaID == 0 {
  265. return
  266. }
  267. if res, err = s.dao.Media(c, am.Media.MediaID, am.Author.Mid); err != nil {
  268. log.Error("s.media(%d) get media info failed: %v", am.Media.MediaID, err)
  269. dao.PromError("article:番剧信息获取失败")
  270. return
  271. }
  272. am.Media.MediaID = res.Media.MediaID
  273. am.Media.Score = res.Score
  274. am.Media.Title = res.Media.Title
  275. am.Media.Cover = res.Media.Cover
  276. am.Media.Area = res.Media.Area
  277. am.Media.TypeID = res.Media.TypeID
  278. am.Media.TypeName = res.Media.TypeName
  279. return
  280. }
  281. // ArticleMeta gets article's meta.
  282. func (s *Service) ArticleMeta(c context.Context, aid int64) (res *artmdl.Meta, err error) {
  283. var addCache = true
  284. if res, err = s.dao.ArticleMetaCache(c, aid); err != nil {
  285. addCache = false
  286. err = nil
  287. }
  288. if res == nil {
  289. if res, err = s.dao.ArticleMeta(c, aid); err != nil || res == nil {
  290. return
  291. }
  292. }
  293. if s.categoriesMap[res.Category.ID] != nil {
  294. res.Category = s.categoriesMap[res.Category.ID]
  295. res.Categories = s.categoryParents[res.Category.ID]
  296. }
  297. group := &errgroup.Group{}
  298. // get author
  299. group.Go(func() error {
  300. var author *artmdl.Author
  301. if author, _ = s.author(c, res.Author.Mid); author != nil {
  302. res.Author = author
  303. }
  304. return nil
  305. })
  306. // get stats
  307. group.Go(func() error {
  308. var stat *artmdl.Stats
  309. if stat, _ = s.stat(c, aid); stat != nil {
  310. res.Stats = stat
  311. return nil
  312. }
  313. if res.Stats == nil {
  314. res.Stats = new(artmdl.Stats)
  315. }
  316. return nil
  317. })
  318. // get tag
  319. group.Go(func() error {
  320. var tags []*artmdl.Tag
  321. if tags, _ = s.Tags(c, aid, false); len(tags) > 0 {
  322. res.Tags = tags
  323. return nil
  324. }
  325. if len(res.Tags) == 0 {
  326. res.Tags = []*artmdl.Tag{}
  327. }
  328. return nil
  329. })
  330. // get list
  331. group.Go(func() (err error) {
  332. lists, _ := s.dao.ArtsList(c, []int64{aid})
  333. res.List = lists[aid]
  334. return
  335. })
  336. group.Wait()
  337. if addCache {
  338. cache.Save(func() { s.dao.AddArticlesMetaCache(context.TODO(), res) })
  339. }
  340. return
  341. }
  342. func (s *Service) accountInfo(c context.Context, mid int64) (info *account.Card, err error) {
  343. var (
  344. arg = &account.ArgMid{Mid: mid}
  345. )
  346. if info, err = s.accountRPC.Card3(c, arg); err != nil {
  347. dao.PromError("article:获取作者信息")
  348. log.Error("s.accountRPC.Card3(%+v) error(%+v)", arg, err)
  349. return
  350. }
  351. return
  352. }
  353. func (s *Service) author(c context.Context, mid int64) (res *artmdl.Author, err error) {
  354. var (
  355. card *account.Card
  356. arg = &account.ArgMid{Mid: mid}
  357. )
  358. if card, err = s.accountRPC.Card3(c, arg); err != nil {
  359. dao.PromError("article:获取作者信息")
  360. log.Error("s.accountRPC.Info(%+v) error(%+v)", arg, err)
  361. return
  362. }
  363. res = &artmdl.Author{
  364. Mid: mid,
  365. Name: card.Name,
  366. Face: card.Face,
  367. Pendant: artmdl.Pendant{
  368. Pid: int32(card.Pendant.Pid),
  369. Name: card.Pendant.Name,
  370. Image: card.Pendant.Image,
  371. Expire: int32(card.Pendant.Expire),
  372. },
  373. Nameplate: artmdl.Nameplate{
  374. Nid: card.Nameplate.Nid,
  375. Name: card.Nameplate.Name,
  376. Image: card.Nameplate.Image,
  377. ImageSmall: card.Nameplate.ImageSmall,
  378. Level: card.Nameplate.Level,
  379. Condition: card.Nameplate.Condition,
  380. },
  381. Vip: artmdl.VipInfo{
  382. Type: card.Vip.Type,
  383. Status: card.Vip.Status,
  384. },
  385. }
  386. if card.Official.Role == 0 {
  387. res.OfficialVerify.Type = -1
  388. } else {
  389. if card.Official.Role <= 2 {
  390. res.OfficialVerify.Type = 0
  391. } else {
  392. res.OfficialVerify.Type = 1
  393. }
  394. res.OfficialVerify.Desc = card.Official.Title
  395. }
  396. return
  397. }
  398. func (s *Service) authors(c context.Context, mids []int64) (res map[int64]*artmdl.Author, err error) {
  399. res = make(map[int64]*artmdl.Author)
  400. if len(mids) == 0 {
  401. return
  402. }
  403. var (
  404. cards map[int64]*account.Card
  405. arg = &account.ArgMids{Mids: mids}
  406. )
  407. if cards, err = s.accountRPC.Cards3(c, arg); err != nil {
  408. dao.PromError("article:批量获取作者信息")
  409. log.Error("s.accountRPC.Infos(%+v) error(%+v)", arg, err)
  410. return
  411. }
  412. for mid, card := range cards {
  413. au := &artmdl.Author{
  414. Mid: mid,
  415. Name: card.Name,
  416. Face: card.Face,
  417. Pendant: artmdl.Pendant{
  418. Pid: int32(card.Pendant.Pid),
  419. Name: card.Pendant.Name,
  420. Image: card.Pendant.Image,
  421. Expire: int32(card.Pendant.Expire),
  422. },
  423. Nameplate: artmdl.Nameplate{
  424. Nid: card.Nameplate.Nid,
  425. Name: card.Nameplate.Name,
  426. Image: card.Nameplate.Image,
  427. ImageSmall: card.Nameplate.ImageSmall,
  428. Level: card.Nameplate.Level,
  429. Condition: card.Nameplate.Condition,
  430. },
  431. Vip: artmdl.VipInfo{
  432. Type: card.Vip.Type,
  433. Status: card.Vip.Status,
  434. },
  435. }
  436. if card.Official.Role == 0 {
  437. au.OfficialVerify.Type = -1
  438. } else {
  439. if card.Official.Role <= 2 {
  440. au.OfficialVerify.Type = 0
  441. } else {
  442. au.OfficialVerify.Type = 1
  443. }
  444. au.OfficialVerify.Desc = card.Official.Title
  445. }
  446. res[mid] = au
  447. }
  448. return
  449. }
  450. func (s *Service) authorDetail(c context.Context, mid int64) (res *artmdl.Author, err error) {
  451. var (
  452. card *account.Card
  453. arg = &account.ArgMid{Mid: mid}
  454. )
  455. if card, err = s.accountRPC.Card3(c, arg); err != nil {
  456. dao.PromError("article:单个获取作者信息")
  457. log.Error("s.accountRPC.Info(%+v) error(%+v)", arg, err)
  458. return
  459. }
  460. if card == nil {
  461. return
  462. }
  463. res = &artmdl.Author{
  464. Mid: mid,
  465. Name: card.Name,
  466. Face: card.Face,
  467. Pendant: artmdl.Pendant{
  468. Pid: int32(card.Pendant.Pid),
  469. Name: card.Pendant.Name,
  470. Image: card.Pendant.Image,
  471. Expire: int32(card.Pendant.Expire),
  472. },
  473. Nameplate: artmdl.Nameplate{
  474. Nid: card.Nameplate.Nid,
  475. Name: card.Nameplate.Name,
  476. Image: card.Nameplate.Image,
  477. ImageSmall: card.Nameplate.ImageSmall,
  478. Level: card.Nameplate.Level,
  479. Condition: card.Nameplate.Condition,
  480. },
  481. }
  482. if card.Official.Role == 0 {
  483. res.OfficialVerify.Type = -1
  484. } else {
  485. if card.Official.Role <= 2 {
  486. res.OfficialVerify.Type = 0
  487. } else {
  488. res.OfficialVerify.Type = 1
  489. }
  490. res.OfficialVerify.Desc = card.Official.Title
  491. }
  492. return
  493. }
  494. func (s *Service) content(c context.Context, aid int64) (res string, err error) {
  495. var addCache = true
  496. if res, err = s.dao.ArticleContentCache(c, aid); err != nil {
  497. addCache = false
  498. err = nil
  499. } else if res != "" {
  500. return
  501. }
  502. if res, err = s.dao.ArticleContent(c, aid); err != nil {
  503. dao.PromError("article:稿件内容")
  504. return
  505. }
  506. if addCache && res != "" {
  507. cache.Save(func() {
  508. s.dao.AddArticleContentCache(context.TODO(), aid, res)
  509. })
  510. }
  511. return
  512. }
  513. // ListCategories list categories
  514. func (s *Service) ListCategories(c context.Context, ip string) (res artmdl.Categories, err error) {
  515. if len(s.Categories) == 0 {
  516. err = ecode.NothingFound
  517. return
  518. }
  519. res = s.Categories
  520. return
  521. }
  522. // ListCategoriesMap list category map
  523. func (s *Service) ListCategoriesMap(c context.Context, ip string) (res map[int64]*artmdl.Category, err error) {
  524. if len(s.Categories) == 0 {
  525. err = ecode.NothingFound
  526. return
  527. }
  528. res = s.categoriesMap
  529. return
  530. }
  531. // ArticleMetas get article meta
  532. func (s *Service) ArticleMetas(c context.Context, ids []int64) (res map[int64]*artmdl.Meta, err error) {
  533. var (
  534. addCache = true
  535. group *errgroup.Group
  536. cachedMetas, missedMetas map[int64]*artmdl.Meta
  537. missedMetaIDs, resIDs []int64
  538. mutex = &sync.Mutex{}
  539. )
  540. res = make(map[int64]*artmdl.Meta)
  541. // get meta
  542. if cachedMetas, missedMetaIDs, err = s.dao.ArticlesMetaCache(c, ids); err != nil {
  543. addCache = false
  544. err = nil
  545. }
  546. if len(missedMetaIDs) > 0 {
  547. missedMetas, _ = s.dao.ArticleMetas(c, missedMetaIDs)
  548. }
  549. // 合并缓存和回源的数据
  550. for id, artm := range cachedMetas {
  551. res[id] = artm
  552. resIDs = append(resIDs, id)
  553. }
  554. for id, artm := range missedMetas {
  555. res[id] = artm
  556. resIDs = append(resIDs, id)
  557. }
  558. // 更新分类
  559. for id, art := range res {
  560. if art.Category == nil {
  561. continue
  562. }
  563. if s.categoriesMap[art.Category.ID] != nil {
  564. res[id].Category = s.categoriesMap[art.Category.ID]
  565. res[id].Categories = s.categoryParents[art.Category.ID]
  566. }
  567. }
  568. group = &errgroup.Group{}
  569. // get author
  570. group.Go(func() (err error) {
  571. var (
  572. mids []int64
  573. authors map[int64]*artmdl.Author
  574. authorsMap = make(map[int64]bool)
  575. )
  576. for _, art := range res {
  577. authorsMap[art.Author.Mid] = true
  578. }
  579. for id := range authorsMap {
  580. mids = append(mids, id)
  581. }
  582. if authors, err = s.authors(c, mids); err != nil {
  583. dao.PromError("article:稿件获取作者信息")
  584. err = nil
  585. return
  586. }
  587. mutex.Lock()
  588. for _, art := range res {
  589. author := authors[art.Author.Mid]
  590. if author != nil {
  591. art.Author = author
  592. }
  593. }
  594. mutex.Unlock()
  595. return
  596. })
  597. //get stats
  598. group.Go(func() (err error) {
  599. stats, _ := s.stats(c, resIDs)
  600. mutex.Lock()
  601. for id := range res {
  602. s := stats[id]
  603. if s == nil {
  604. s = new(artmdl.Stats)
  605. }
  606. res[id].Stats = s
  607. }
  608. mutex.Unlock()
  609. return
  610. })
  611. // get list
  612. group.Go(func() (err error) {
  613. lists, _ := s.dao.ArtsList(c, resIDs)
  614. mutex.Lock()
  615. for id := range res {
  616. res[id].List = lists[id]
  617. }
  618. mutex.Unlock()
  619. return
  620. })
  621. group.Wait()
  622. if addCache && len(missedMetas) > 0 {
  623. cache.Save(func() {
  624. for _, art := range missedMetas {
  625. s.dao.AddArticlesMetaCache(context.TODO(), art)
  626. }
  627. })
  628. }
  629. return
  630. }
  631. func filterNoDistributeArts(as []*artmdl.Meta) (res []*artmdl.Meta) {
  632. for _, a := range as {
  633. if (a != nil) && !a.AttrVal(artmdl.AttrBitNoDistribute) {
  634. res = append(res, a)
  635. }
  636. }
  637. return
  638. }
  639. func filterNoDistributeArtsMap(as map[int64]*artmdl.Meta) {
  640. for id, a := range as {
  641. if (a != nil) && a.AttrVal(artmdl.AttrBitNoDistribute) {
  642. delete(as, id)
  643. }
  644. }
  645. }
  646. // AddArtContentCache add article content cache
  647. func (s *Service) AddArtContentCache(c context.Context, aid int64, content string) (err error) {
  648. if content == "" {
  649. return
  650. }
  651. err = s.dao.AddArticleContentCache(c, aid, content)
  652. return
  653. }
  654. // ArticleRemainCount returns the number that user could be use to posting new articles.
  655. func (s *Service) ArticleRemainCount(c context.Context, mid int64) (num int, err error) {
  656. if mid <= 0 {
  657. return
  658. }
  659. var count, limit int
  660. if count, err = s.dao.ArticleRemainCount(c, mid); err != nil {
  661. return
  662. }
  663. author, _ := s.dao.Author(c, mid)
  664. if author != nil {
  665. limit = author.Limit
  666. }
  667. if limit == 0 {
  668. limit = s.c.Article.UpperArticleLimit
  669. }
  670. if count > limit {
  671. return
  672. }
  673. num = limit - count
  674. return
  675. }
  676. // AddComplaint add complaint.
  677. func (s *Service) AddComplaint(c context.Context, aid, mid, ctype int64, reason, imageUrls, ip string) (err error) {
  678. var exist, protected bool
  679. if exist, err = s.dao.ComplaintExist(c, aid, mid); (err != nil) || exist {
  680. return
  681. }
  682. if err = s.dao.AddComplaint(c, aid, mid, ctype, reason, imageUrls); err != nil {
  683. return
  684. }
  685. if protected, err = s.dao.ComplaintProtected(c, aid); err != nil || protected {
  686. return
  687. }
  688. err = s.dao.AddComplaintCount(c, aid)
  689. return
  690. }
  691. // MoreArts get author's more articles.
  692. func (s *Service) MoreArts(c context.Context, aid int64) (res []*artmdl.Meta, err error) {
  693. var am *artmdl.Meta
  694. if am, err = s.ArticleMeta(c, aid); err != nil {
  695. dao.PromError("article:获取文章meta")
  696. return
  697. }
  698. if am == nil || am.Author == nil {
  699. return
  700. }
  701. var (
  702. exist bool
  703. beforeAids, afterAids, tmpAids []int64
  704. aidTimes [][2]int64
  705. addCache = true
  706. tmpRes map[int64]*artmdl.Meta
  707. mid = am.Author.Mid
  708. )
  709. if exist, err = s.dao.ExpireUpperCache(c, mid); err != nil {
  710. addCache = false
  711. err = nil
  712. } else if exist {
  713. if beforeAids, afterAids, err = s.dao.MoreArtsCaches(c, mid, int64(am.PublishTime), _moreNum+4); err != nil {
  714. addCache = false
  715. exist = false
  716. }
  717. }
  718. if !exist {
  719. if aidTimes, err = s.dao.UpperPassed(c, mid); err != nil {
  720. return
  721. }
  722. if addCache {
  723. cache.Save(func() {
  724. s.dao.AddUpperCaches(context.TODO(), map[int64][][2]int64{mid: aidTimes})
  725. })
  726. }
  727. for _, aidTime := range aidTimes {
  728. tmpAids = append(tmpAids, aidTime[0])
  729. }
  730. beforeAids, afterAids = splitAids(tmpAids, aid)
  731. }
  732. if len(beforeAids)+len(afterAids) == 0 {
  733. return
  734. }
  735. tmpAids = append([]int64{}, beforeAids...)
  736. tmpAids = append(tmpAids, afterAids...)
  737. if tmpRes, err = s.ArticleMetas(c, tmpAids); err != nil {
  738. return
  739. }
  740. filterNoDistributeArtsMap(tmpRes)
  741. res = fmtMoreArts(beforeAids, afterAids, tmpRes)
  742. return
  743. }
  744. func splitAids(aids []int64, aid int64) (beforeAids []int64, afterAids []int64) {
  745. position := -1
  746. for i, a := range aids {
  747. if a == aid {
  748. position = i
  749. break
  750. }
  751. }
  752. if position == -1 {
  753. return
  754. }
  755. l := len(aids)
  756. if position+_moreNum > l {
  757. beforeAids = aids[position+1 : l]
  758. } else {
  759. beforeAids = aids[position+1 : position+_moreNum]
  760. }
  761. if position-_moreNum < 0 {
  762. afterAids = aids[0:position]
  763. } else {
  764. afterAids = aids[position-_moreNum : position]
  765. }
  766. return
  767. }
  768. // 位置说明: 按照ptime逆序: afterAids -> (aid) -> beforeAids, after取一个 before取2个
  769. func fmtMoreArts(beforeAids, afterAids []int64, tmpRes map[int64]*artmdl.Meta) (res []*artmdl.Meta) {
  770. var before, after []*artmdl.Meta
  771. for _, aid := range beforeAids {
  772. if v := tmpRes[aid]; v != nil {
  773. before = append(before, v)
  774. }
  775. }
  776. sort.Sort(artmdl.Metas(before))
  777. for _, aid := range afterAids {
  778. if v := tmpRes[aid]; v != nil {
  779. after = append(after, v)
  780. }
  781. }
  782. sort.Sort(artmdl.Metas(after))
  783. if len(after) > _moreNum {
  784. after = after[len(after)-_moreNum:]
  785. }
  786. if len(before) > _moreNum {
  787. before = before[:_moreNum]
  788. }
  789. lenAfter := len(after)
  790. lenBefore := len(before)
  791. // 正常逻辑: 前一个后三个
  792. if (lenAfter > 0) && (lenBefore > 1) {
  793. res = []*artmdl.Meta{after[lenAfter-1]}
  794. res = append(res, before[:2]...)
  795. } else if lenAfter == 0 {
  796. // 前面不够
  797. res = before
  798. } else if lenBefore == 0 {
  799. // 后面不够
  800. res = after
  801. } else {
  802. //前面补全后面缺失的
  803. if lenAfter-(_moreNum-lenBefore) > 0 {
  804. res = append(res, after[lenAfter-(_moreNum-lenBefore):]...)
  805. } else {
  806. res = append(res, after...)
  807. }
  808. res = append(res, before...)
  809. }
  810. return
  811. }
  812. // NewArticleCount get new article count
  813. func (s *Service) NewArticleCount(c context.Context, ptime int64) (res int64, err error) {
  814. res, err = s.dao.NewArticleCount(c, ptime)
  815. return
  816. }
  817. // Mores get more articles
  818. func (s *Service) Mores(c context.Context, aid, loginMID int64) (res artmdl.MoreArts, err error) {
  819. var art *artmdl.Meta
  820. group := &errgroup.Group{}
  821. if art, err = s.ArticleMeta(c, aid); (err != nil) || (art == nil) || (!art.IsNormal()) {
  822. err = ecode.NothingFound
  823. return
  824. }
  825. res.Author = &artmdl.AccountCard{Mid: strconv.FormatInt(art.Author.Mid, 10), Face: art.Author.Face, Name: art.Author.Name}
  826. mid := art.Author.Mid
  827. group.Go(func() (e error) {
  828. var profile *account.ProfileStat
  829. if profile, e = s.accountRPC.ProfileWithStat3(c, &account.ArgMid{Mid: mid}); e != nil {
  830. dao.PromError("article:Card3")
  831. log.Error("s.acc.Card3(%d) error %v", mid, e)
  832. return
  833. }
  834. if profile != nil {
  835. res.Author.FromProfileStat(profile)
  836. }
  837. return
  838. })
  839. group.Go(func() (e error) {
  840. if res.Articles, e = s.similars(c, aid, art.Category.ID); e != nil {
  841. dao.PromError("article:similars")
  842. }
  843. if res.Articles != nil && len(res.Articles) > 0 {
  844. return
  845. }
  846. if res.Articles, e = s.MoreArts(c, aid); e != nil {
  847. dao.PromError("article:MoreArts")
  848. }
  849. if res.Articles == nil {
  850. res.Articles = []*artmdl.Meta{}
  851. }
  852. return
  853. })
  854. group.Go(func() (e error) {
  855. if res.Total, e = s.UpperArtsCount(c, mid); e != nil {
  856. dao.PromError("article:获取作者文章数")
  857. }
  858. return
  859. })
  860. group.Go(func() error {
  861. // read count
  862. if stat, e := s.dao.CacheUpStatDaily(c, mid); e != nil {
  863. dao.PromError("article:CacheUpStatDaily")
  864. } else if stat != nil {
  865. res.ReadCount = stat.View
  866. return nil
  867. }
  868. if stat, e := s.dao.UpStat(c, mid); e != nil {
  869. dao.PromError("article:获取作者文章数")
  870. } else {
  871. res.ReadCount = stat.View
  872. }
  873. return nil
  874. })
  875. group.Go(func() (e error) {
  876. if loginMID == 0 {
  877. return
  878. }
  879. if res.Attention, e = s.isAttention(c, loginMID, mid); e != nil {
  880. dao.PromError("article:获取作者文章数")
  881. }
  882. return
  883. })
  884. group.Wait()
  885. return
  886. }
  887. // FeedArticleMetas .
  888. func (s *Service) FeedArticleMetas(c context.Context, ids []int64) (res map[int64]*artmdl.Meta, err error) {
  889. var (
  890. addCache = true
  891. group *errgroup.Group
  892. cachedMetas, missedMetas map[int64]*artmdl.Meta
  893. missedMetaIDs, resIDs []int64
  894. mutex = &sync.Mutex{}
  895. )
  896. res = make(map[int64]*artmdl.Meta)
  897. // get meta
  898. if cachedMetas, missedMetaIDs, err = s.dao.ArticlesMetaCache(c, ids); err != nil {
  899. addCache = false
  900. err = nil
  901. }
  902. if len(missedMetaIDs) > 0 {
  903. missedMetas, _ = s.dao.ArticleMetas(c, missedMetaIDs)
  904. }
  905. // 合并缓存和回源的数据
  906. for id, artm := range cachedMetas {
  907. res[id] = artm
  908. resIDs = append(resIDs, id)
  909. }
  910. for id, artm := range missedMetas {
  911. res[id] = artm
  912. resIDs = append(resIDs, id)
  913. }
  914. // 更新分类
  915. for id, art := range res {
  916. if art.Category == nil {
  917. continue
  918. }
  919. if s.categoriesMap[art.Category.ID] != nil {
  920. res[id].Category = s.categoriesMap[art.Category.ID]
  921. res[id].Categories = s.categoryParents[art.Category.ID]
  922. }
  923. }
  924. group = &errgroup.Group{}
  925. // get author
  926. group.Go(func() (err error) {
  927. var (
  928. mids []int64
  929. authors map[int64]*artmdl.Author
  930. authorsMap = make(map[int64]bool)
  931. )
  932. for _, art := range missedMetas {
  933. authorsMap[art.Author.Mid] = true
  934. }
  935. for id := range authorsMap {
  936. mids = append(mids, id)
  937. }
  938. if authors, err = s.authors(c, mids); err != nil {
  939. dao.PromError("article:稿件获取作者信息")
  940. err = nil
  941. return
  942. }
  943. mutex.Lock()
  944. for _, art := range missedMetas {
  945. author := authors[art.Author.Mid]
  946. if author != nil {
  947. art.Author = author
  948. }
  949. }
  950. mutex.Unlock()
  951. return
  952. })
  953. //get stats
  954. group.Go(func() (err error) {
  955. stats, _ := s.stats(c, resIDs)
  956. mutex.Lock()
  957. for id := range res {
  958. s := stats[id]
  959. if s == nil {
  960. s = new(artmdl.Stats)
  961. }
  962. res[id].Stats = s
  963. }
  964. mutex.Unlock()
  965. return
  966. })
  967. group.Wait()
  968. if addCache && len(missedMetas) > 0 {
  969. cache.Save(func() {
  970. for _, art := range missedMetas {
  971. s.dao.AddArticlesMetaCache(context.TODO(), art)
  972. }
  973. })
  974. }
  975. return
  976. }
  977. // AddCheatFilter .
  978. func (s *Service) AddCheatFilter(c context.Context, aid int64, lv int) (err error) {
  979. return s.dao.AddCheatFilter(c, aid, lv)
  980. }
  981. // DelCheatFilter .
  982. func (s *Service) DelCheatFilter(c context.Context, aid int64) (err error) {
  983. return s.dao.DelCheatFilter(c, aid)
  984. }
  985. // similars .
  986. func (s *Service) similars(c context.Context, aid int64, cid int64) (res []*artmdl.Meta, err error) {
  987. var (
  988. tags []*artmdl.Tag
  989. aidst = make(map[int64]bool)
  990. aidsr []int64
  991. aidsa = make(map[int64]bool)
  992. tmps []int64
  993. aids []int64
  994. id int64
  995. meta *artmdl.Meta
  996. metas map[int64]*artmdl.Meta
  997. nils []int64
  998. )
  999. if tags, err = s.Tags(c, aid, false); err != nil {
  1000. return
  1001. }
  1002. for _, tag := range tags {
  1003. var tagArts *artmdl.TagArts
  1004. if tagArts, err = s.dao.CacheAidsByTag(c, tag.Tid); err != nil {
  1005. return
  1006. }
  1007. if tagArts == nil {
  1008. nils = append(nils, tag.Tid)
  1009. continue
  1010. }
  1011. for _, id = range tagArts.Aids {
  1012. aidst[id] = true
  1013. }
  1014. }
  1015. if len(nils) > 0 {
  1016. if tmps, err = s.dao.TagArticles(c, nils); err != nil {
  1017. return
  1018. }
  1019. for _, id = range tmps {
  1020. aidst[id] = true
  1021. }
  1022. }
  1023. delete(aidst, aid)
  1024. aidsr = s.getRecommentsGroups(c, cid, aid)
  1025. if len(aidsr) == 0 && len(aidst) == 0 {
  1026. return
  1027. }
  1028. if len(aidsr) == 0 {
  1029. for id = range aidst {
  1030. aids = append(aids, id)
  1031. if len(aids) > 10 {
  1032. break
  1033. }
  1034. }
  1035. if metas, err = s.ArticleMetas(c, aids); err != nil {
  1036. return
  1037. }
  1038. for _, meta := range metas {
  1039. if artmdl.NoDistributeAttr(meta.Attributes) || artmdl.NoRegionAttr(meta.Attributes) {
  1040. continue
  1041. }
  1042. res = append(res, meta)
  1043. if len(res) == 3 {
  1044. break
  1045. }
  1046. }
  1047. return
  1048. }
  1049. for _, id = range aidsr {
  1050. aids = append(aids, id)
  1051. aidsa[id] = true
  1052. }
  1053. for id = range aidst {
  1054. aidsa[id] = true
  1055. }
  1056. if metas, err = s.ArticleMetas(c, aids); err != nil {
  1057. return
  1058. }
  1059. for _, meta = range metas {
  1060. res = append(res, meta)
  1061. delete(aidsa, meta.ID)
  1062. break
  1063. }
  1064. tmps = []int64{}
  1065. for id = range aidsa {
  1066. tmps = append(tmps, id)
  1067. if len(tmps) > 10 {
  1068. break
  1069. }
  1070. }
  1071. if metas, err = s.ArticleMetas(c, tmps); err != nil {
  1072. return
  1073. }
  1074. for _, meta := range metas {
  1075. if artmdl.NoDistributeAttr(meta.Attributes) || artmdl.NoRegionAttr(meta.Attributes) {
  1076. continue
  1077. }
  1078. res = append(res, meta)
  1079. if len(res) == 3 {
  1080. break
  1081. }
  1082. }
  1083. return
  1084. }
  1085. // MediaArticle .
  1086. func (s *Service) MediaArticle(c context.Context, mediaID, mid int64) (id int64, err error) {
  1087. return s.dao.MediaArticle(c, mediaID, mid)
  1088. }
  1089. // MediaIDByID .
  1090. func (s *Service) MediaIDByID(c context.Context, aid int64) (id int64, err error) {
  1091. return s.dao.MediaIDByID(c, aid)
  1092. }
  1093. func (s *Service) keywords(c context.Context, id int64, summary string) (keywords string) {
  1094. var (
  1095. err error
  1096. addCache = true
  1097. )
  1098. if keywords, err = s.dao.ArticleKeywordsCache(c, id); keywords != "" {
  1099. return
  1100. }
  1101. if err != nil {
  1102. addCache = false
  1103. }
  1104. if keywords, err = s.dao.ArticleKeywords(c, id); err != nil {
  1105. dao.PromError("article:文章关键词")
  1106. }
  1107. if keywords == "" {
  1108. keywords = summary
  1109. }
  1110. if addCache {
  1111. cache.Save(func() {
  1112. s.dao.AddArticleKeywordsCache(context.TODO(), id, keywords)
  1113. })
  1114. }
  1115. return
  1116. }