memcached.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. package dao
  2. import (
  3. "context"
  4. "fmt"
  5. "strconv"
  6. "strings"
  7. "sync"
  8. "time"
  9. "go-common/app/interface/openplatform/article/model"
  10. "go-common/library/cache/memcache"
  11. "go-common/library/ecode"
  12. "go-common/library/log"
  13. "go-common/library/xstr"
  14. "go-common/library/sync/errgroup"
  15. )
  16. const (
  17. _prefixArtMeta = "art_mp_%d"
  18. _prefixArtContent = "art_c_%d"
  19. _prefixArtKeywords = "art_kw_%d"
  20. _prefixArtStat = "art_s_%d"
  21. _prefixCard = "art_cards_"
  22. _bulkSize = 50
  23. )
  24. func artMetaKey(id int64) string {
  25. return fmt.Sprintf(_prefixArtMeta, id)
  26. }
  27. func artContentKey(id int64) string {
  28. return fmt.Sprintf(_prefixArtContent, id)
  29. }
  30. func artKeywordsKey(id int64) string {
  31. return fmt.Sprintf(_prefixArtKeywords, id)
  32. }
  33. func artStatsKey(id int64) string {
  34. return fmt.Sprintf(_prefixArtStat, id)
  35. }
  36. func cardKey(id string) string {
  37. return _prefixCard + id
  38. }
  39. func hotspotsKey() string {
  40. return fmt.Sprintf("art_hotspots")
  41. }
  42. func mcHotspotKey(id int64) string {
  43. return fmt.Sprintf("art_hotspot_%d", id)
  44. }
  45. func mcAuthorKey(mid int64) string {
  46. return fmt.Sprintf("art_author_%d", mid)
  47. }
  48. func mcTagKey(tag int64) string {
  49. return fmt.Sprintf("tag_aids_%d", tag)
  50. }
  51. func mcUpStatKey(mid int64) string {
  52. var (
  53. hour int
  54. day int
  55. )
  56. now := time.Now()
  57. hour = now.Hour()
  58. if hour < 7 {
  59. day = now.Add(time.Hour * -24).Day()
  60. } else {
  61. day = now.Day()
  62. }
  63. return fmt.Sprintf("up_stat_daily_%d_%d", mid, day)
  64. }
  65. // statsValue convert stats to string, format: "view,favorite,like,unlike,reply..."
  66. func statsValue(s *model.Stats) string {
  67. if s == nil {
  68. return ",,,,,,"
  69. }
  70. ids := []int64{s.View, s.Favorite, s.Like, s.Dislike, s.Reply, s.Share, s.Coin}
  71. return xstr.JoinInts(ids)
  72. }
  73. func revoverStatsValue(c context.Context, s string) (res *model.Stats) {
  74. var (
  75. vs []int64
  76. err error
  77. )
  78. res = new(model.Stats)
  79. if s == "" {
  80. return
  81. }
  82. if vs, err = xstr.SplitInts(s); err != nil || len(vs) < 7 {
  83. PromError("mc:stats解析")
  84. log.Error("dao.revoverStatsValue(%s) err: %+v", s, err)
  85. return
  86. }
  87. res = &model.Stats{
  88. View: vs[0],
  89. Favorite: vs[1],
  90. Like: vs[2],
  91. Dislike: vs[3],
  92. Reply: vs[4],
  93. Share: vs[5],
  94. Coin: vs[6],
  95. }
  96. return
  97. }
  98. // pingMc ping memcache
  99. func (d *Dao) pingMC(c context.Context) (err error) {
  100. conn := d.mc.Get(c)
  101. defer conn.Close()
  102. item := memcache.Item{Key: "ping", Value: []byte{1}, Expiration: d.mcArticleExpire}
  103. err = conn.Set(&item)
  104. return
  105. }
  106. //AddArticlesMetaCache add articles meta cache
  107. func (d *Dao) AddArticlesMetaCache(c context.Context, vs ...*model.Meta) (err error) {
  108. conn := d.mc.Get(c)
  109. defer conn.Close()
  110. for _, v := range vs {
  111. if v == nil {
  112. continue
  113. }
  114. item := &memcache.Item{Key: artMetaKey(v.ID), Object: v, Flags: memcache.FlagProtobuf, Expiration: d.mcArticleExpire}
  115. if err = conn.Set(item); err != nil {
  116. PromError("mc:增加文章meta缓存")
  117. log.Error("conn.Store(%s) error(%+v)", artMetaKey(v.ID), err)
  118. return
  119. }
  120. }
  121. return
  122. }
  123. // ArticleMetaCache gets article's meta cache.
  124. func (d *Dao) ArticleMetaCache(c context.Context, aid int64) (res *model.Meta, err error) {
  125. var (
  126. conn = d.mc.Get(c)
  127. key = artMetaKey(aid)
  128. )
  129. defer conn.Close()
  130. reply, err := conn.Get(key)
  131. if err != nil {
  132. if err == memcache.ErrNotFound {
  133. missedCount.Incr("article-meta")
  134. err = nil
  135. return
  136. }
  137. PromError("mc:获取文章meta缓存")
  138. log.Error("conn.Get(%v) error(%+v)", key, err)
  139. return
  140. }
  141. res = &model.Meta{}
  142. if err = conn.Scan(reply, res); err != nil {
  143. PromError("mc:文章meta缓存json解析")
  144. log.Error("reply.Scan(%s) error(%+v)", reply.Value, err)
  145. return
  146. }
  147. res.Strong()
  148. cachedCount.Incr("article-meta")
  149. return
  150. }
  151. //ArticlesMetaCache articles meta cache
  152. func (d *Dao) ArticlesMetaCache(c context.Context, ids []int64) (cached map[int64]*model.Meta, missed []int64, err error) {
  153. if len(ids) == 0 {
  154. return
  155. }
  156. cached = make(map[int64]*model.Meta, len(ids))
  157. allKeys := make([]string, 0, len(ids))
  158. idmap := make(map[string]int64, len(ids))
  159. for _, id := range ids {
  160. k := artMetaKey(id)
  161. allKeys = append(allKeys, k)
  162. idmap[k] = id
  163. }
  164. group, errCtx := errgroup.WithContext(c)
  165. mutex := sync.Mutex{}
  166. keysLen := len(allKeys)
  167. for i := 0; i < keysLen; i += _bulkSize {
  168. var keys []string
  169. if (i + _bulkSize) > keysLen {
  170. keys = allKeys[i:]
  171. } else {
  172. keys = allKeys[i : i+_bulkSize]
  173. }
  174. group.Go(func() (err error) {
  175. conn := d.mc.Get(errCtx)
  176. defer conn.Close()
  177. replys, err := conn.GetMulti(keys)
  178. if err != nil {
  179. PromError("mc:获取文章meta缓存")
  180. log.Error("conn.Gets(%v) error(%+v)", keys, err)
  181. err = nil
  182. return
  183. }
  184. for key, item := range replys {
  185. art := &model.Meta{}
  186. if err = conn.Scan(item, art); err != nil {
  187. PromError("mc:文章meta缓存json解析")
  188. log.Error("item.Scan(%s) error(%+v)", item.Value, err)
  189. err = nil
  190. continue
  191. }
  192. mutex.Lock()
  193. cached[idmap[key]] = art.Strong()
  194. delete(idmap, key)
  195. mutex.Unlock()
  196. }
  197. return
  198. })
  199. }
  200. group.Wait()
  201. missed = make([]int64, 0, len(idmap))
  202. for _, id := range idmap {
  203. missed = append(missed, id)
  204. }
  205. missedCount.Add("article-meta", int64(len(missed)))
  206. cachedCount.Add("article-meta", int64(len(cached)))
  207. return
  208. }
  209. // AddArticleStatsCache batch set article cache.
  210. func (d *Dao) AddArticleStatsCache(c context.Context, id int64, v *model.Stats) (err error) {
  211. conn := d.mc.Get(c)
  212. defer conn.Close()
  213. bs := []byte(statsValue(v))
  214. item := &memcache.Item{Key: artStatsKey(id), Value: bs, Expiration: d.mcStatsExpire}
  215. if err = conn.Set(item); err != nil {
  216. PromError("mc:增加文章统计缓存")
  217. log.Error("conn.Store(%s) error(%+v)", artStatsKey(id), err)
  218. }
  219. return
  220. }
  221. //AddArticleContentCache add article content cache
  222. func (d *Dao) AddArticleContentCache(c context.Context, id int64, content string) (err error) {
  223. conn := d.mc.Get(c)
  224. defer conn.Close()
  225. var bs = []byte(content)
  226. item := &memcache.Item{Key: artContentKey(id), Value: bs, Expiration: d.mcArticleExpire, Flags: memcache.FlagGzip}
  227. if err = conn.Set(item); err != nil {
  228. PromError("mc:增加文章内容缓存")
  229. log.Error("conn.Store(%s) error(%+v)", artContentKey(id), err)
  230. }
  231. return
  232. }
  233. // AddArticleKeywordsCache add article keywords cache.
  234. func (d *Dao) AddArticleKeywordsCache(c context.Context, id int64, keywords string) (err error) {
  235. conn := d.mc.Get(c)
  236. defer conn.Close()
  237. var bs = []byte(keywords)
  238. item := &memcache.Item{Key: artKeywordsKey(id), Value: bs, Expiration: d.mcArticleExpire, Flags: memcache.FlagGzip}
  239. if err = conn.Set(item); err != nil {
  240. PromError("mc:增加文章关键字缓存")
  241. log.Error("conn.Store(%s) error(%+v)", artKeywordsKey(id), err)
  242. }
  243. return
  244. }
  245. // ArticleContentCache article content cache
  246. func (d *Dao) ArticleContentCache(c context.Context, id int64) (res string, err error) {
  247. conn := d.mc.Get(c)
  248. defer conn.Close()
  249. reply, err := conn.Get(artContentKey(id))
  250. if err != nil {
  251. if err == memcache.ErrNotFound {
  252. missedCount.Incr("article-content")
  253. err = nil
  254. return
  255. }
  256. PromError("mc:获取文章内容缓存")
  257. log.Error("conn.Get(%v) error(%+v)", artContentKey(id), err)
  258. return
  259. }
  260. err = conn.Scan(reply, &res)
  261. return
  262. }
  263. // ArticleKeywordsCache article Keywords cache
  264. func (d *Dao) ArticleKeywordsCache(c context.Context, id int64) (res string, err error) {
  265. conn := d.mc.Get(c)
  266. defer conn.Close()
  267. reply, err := conn.Get(artKeywordsKey(id))
  268. if err != nil {
  269. if err == memcache.ErrNotFound {
  270. missedCount.Incr("article-keywords")
  271. err = nil
  272. return
  273. }
  274. PromError("mc:获取文章关键字缓存")
  275. log.Error("conn.Get(%v) error(%+v)", artKeywordsKey(id), err)
  276. return
  277. }
  278. err = conn.Scan(reply, &res)
  279. return
  280. }
  281. //DelArticleMetaCache delete article meta cache
  282. func (d *Dao) DelArticleMetaCache(c context.Context, id int64) (err error) {
  283. var (
  284. key = artMetaKey(id)
  285. conn = d.mc.Get(c)
  286. )
  287. defer conn.Close()
  288. if err = conn.Delete(key); err != nil {
  289. if err == memcache.ErrNotFound {
  290. err = nil
  291. } else {
  292. PromError("mc:删除文章meta缓存")
  293. log.Error("key(%v) error(%+v)", key, err)
  294. }
  295. }
  296. return
  297. }
  298. // DelArticleStatsCache delete article stats cache
  299. func (d *Dao) DelArticleStatsCache(c context.Context, id int64) (err error) {
  300. var (
  301. key = artStatsKey(id)
  302. conn = d.mc.Get(c)
  303. )
  304. defer conn.Close()
  305. if err = conn.Delete(key); err != nil {
  306. if err == memcache.ErrNotFound {
  307. err = nil
  308. } else {
  309. PromError("mc:删除文章stats缓存")
  310. log.Error("key(%v) error(%+v)", key, err)
  311. }
  312. }
  313. return
  314. }
  315. //DelArticleContentCache delete article content cache
  316. func (d *Dao) DelArticleContentCache(c context.Context, id int64) (err error) {
  317. var (
  318. key = artContentKey(id)
  319. conn = d.mc.Get(c)
  320. )
  321. defer conn.Close()
  322. if err = conn.Delete(key); err != nil {
  323. if err == memcache.ErrNotFound {
  324. err = nil
  325. } else {
  326. PromError("mc:删除文章content缓存")
  327. log.Error("key(%v) error(%+v)", key, err)
  328. }
  329. }
  330. return
  331. }
  332. // ArticleStatsCache article stats cache
  333. func (d *Dao) ArticleStatsCache(c context.Context, id int64) (res *model.Stats, err error) {
  334. if id == 0 {
  335. err = ecode.NothingFound
  336. return
  337. }
  338. var (
  339. conn = d.mc.Get(c)
  340. key = artStatsKey(id)
  341. statsStr string
  342. )
  343. defer conn.Close()
  344. reply, err := conn.Get(key)
  345. if err != nil {
  346. if err == memcache.ErrNotFound {
  347. res = nil
  348. err = nil
  349. return
  350. }
  351. PromError("mc:获取文章计数缓存")
  352. log.Error("conn.Get(%v) error(%+v)", key, err)
  353. return
  354. }
  355. if err = conn.Scan(reply, &statsStr); err == nil {
  356. res = revoverStatsValue(c, statsStr)
  357. } else {
  358. PromError("mc:获取文章计数缓存")
  359. log.Error("dao.ArticleStatsCache.reply.Scan(%v, %v) error(%+v)", key, statsStr, err)
  360. }
  361. return
  362. }
  363. // ArticlesStatsCache articles stats cache
  364. func (d *Dao) ArticlesStatsCache(c context.Context, ids []int64) (cached map[int64]*model.Stats, missed []int64, err error) {
  365. if len(ids) == 0 {
  366. return
  367. }
  368. cached = make(map[int64]*model.Stats, len(ids))
  369. allKeys := make([]string, 0, len(ids))
  370. idmap := make(map[string]int64, len(ids))
  371. for _, id := range ids {
  372. k := artStatsKey(id)
  373. allKeys = append(allKeys, k)
  374. idmap[k] = id
  375. }
  376. group, errCtx := errgroup.WithContext(c)
  377. mutex := sync.Mutex{}
  378. keysLen := len(allKeys)
  379. for i := 0; i < keysLen; i += _bulkSize {
  380. var keys []string
  381. if (i + _bulkSize) > keysLen {
  382. keys = allKeys[i:]
  383. } else {
  384. keys = allKeys[i : i+_bulkSize]
  385. }
  386. group.Go(func() (err error) {
  387. conn := d.mc.Get(errCtx)
  388. defer conn.Close()
  389. replys, err := conn.GetMulti(keys)
  390. if err != nil {
  391. PromError("mc:获取文章计数缓存")
  392. log.Error("conn.Gets(%v) error(%+v)", keys, err)
  393. err = nil
  394. return
  395. }
  396. for _, reply := range replys {
  397. var info string
  398. if e := conn.Scan(reply, &info); e != nil {
  399. PromError("mc:获取文章计数缓存scan")
  400. continue
  401. }
  402. art := revoverStatsValue(c, info)
  403. mutex.Lock()
  404. cached[idmap[reply.Key]] = art
  405. delete(idmap, reply.Key)
  406. mutex.Unlock()
  407. }
  408. return
  409. })
  410. }
  411. group.Wait()
  412. missed = make([]int64, 0, len(idmap))
  413. for _, id := range idmap {
  414. missed = append(missed, id)
  415. }
  416. missedCount.Add("article-stats", int64(len(missed)))
  417. cachedCount.Add("article-stats", int64(len(cached)))
  418. return
  419. }
  420. // AddCardsCache .
  421. func (d *Dao) addCardsCache(c context.Context, vs ...*model.Cards) (err error) {
  422. if len(vs) == 0 {
  423. return
  424. }
  425. conn := d.mc.Get(c)
  426. defer conn.Close()
  427. for _, v := range vs {
  428. if v == nil {
  429. continue
  430. }
  431. key := cardKey(v.Key())
  432. item := memcache.Item{Key: key, Object: v, Expiration: d.mcCardsExpire, Flags: memcache.FlagJSON}
  433. if err = conn.Set(&item); err != nil {
  434. PromError("mc:增加卡片缓存")
  435. log.Error("conn.Set(%s) error(%+v)", key, err)
  436. return
  437. }
  438. }
  439. return
  440. }
  441. // CardsCache ids like cv123 av123 au123
  442. func (d *Dao) cardsCache(c context.Context, ids []string) (res map[string]*model.Cards, err error) {
  443. if len(ids) == 0 {
  444. return
  445. }
  446. res = make(map[string]*model.Cards, len(ids))
  447. var keys []string
  448. for _, id := range ids {
  449. keys = append(keys, cardKey(id))
  450. }
  451. conn := d.mc.Get(c)
  452. replys, err := conn.GetMulti(keys)
  453. defer conn.Close()
  454. if err != nil {
  455. PromError("mc:获取cards缓存")
  456. log.Error("conn.Gets(%v) error(%+v)", keys, err)
  457. err = nil
  458. return
  459. }
  460. for _, reply := range replys {
  461. s := model.Cards{}
  462. if err = conn.Scan(reply, &s); err != nil {
  463. PromError("获取cards缓存json解析")
  464. log.Error("json.Unmarshal(%v) error(%+v)", reply.Value, err)
  465. err = nil
  466. continue
  467. }
  468. res[strings.TrimPrefix(reply.Key, _prefixCard)] = &s
  469. }
  470. return
  471. }
  472. // AddBangumiCardsCache .
  473. func (d *Dao) AddBangumiCardsCache(c context.Context, vs map[int64]*model.BangumiCard) (err error) {
  474. var cards []*model.Cards
  475. for _, v := range vs {
  476. cards = append(cards, &model.Cards{Type: model.CardPrefixBangumi, BangumiCard: v})
  477. }
  478. err = d.addCardsCache(c, cards...)
  479. return
  480. }
  481. // BangumiCardsCache .
  482. func (d *Dao) BangumiCardsCache(c context.Context, ids []int64) (vs map[int64]*model.BangumiCard, err error) {
  483. var cards map[string]*model.Cards
  484. var idsStr []string
  485. for _, id := range ids {
  486. idsStr = append(idsStr, model.CardPrefixBangumi+strconv.FormatInt(id, 10))
  487. }
  488. if cards, err = d.cardsCache(c, idsStr); err != nil {
  489. return
  490. }
  491. vs = make(map[int64]*model.BangumiCard)
  492. for _, card := range cards {
  493. if (card != nil) && (card.BangumiCard != nil) {
  494. vs[card.BangumiCard.ID] = card.BangumiCard
  495. }
  496. }
  497. return
  498. }
  499. // AddBangumiEpCardsCache .
  500. func (d *Dao) AddBangumiEpCardsCache(c context.Context, vs map[int64]*model.BangumiCard) (err error) {
  501. var cards []*model.Cards
  502. for _, v := range vs {
  503. cards = append(cards, &model.Cards{Type: model.CardPrefixBangumiEp, BangumiCard: v})
  504. }
  505. err = d.addCardsCache(c, cards...)
  506. return
  507. }
  508. // BangumiEpCardsCache .
  509. func (d *Dao) BangumiEpCardsCache(c context.Context, ids []int64) (vs map[int64]*model.BangumiCard, err error) {
  510. var cards map[string]*model.Cards
  511. var idsStr []string
  512. for _, id := range ids {
  513. idsStr = append(idsStr, model.CardPrefixBangumiEp+strconv.FormatInt(id, 10))
  514. }
  515. if cards, err = d.cardsCache(c, idsStr); err != nil {
  516. return
  517. }
  518. vs = make(map[int64]*model.BangumiCard)
  519. for _, card := range cards {
  520. if (card != nil) && (card.BangumiCard != nil) {
  521. vs[card.BangumiCard.ID] = card.BangumiCard
  522. }
  523. }
  524. return
  525. }
  526. // AddAudioCardsCache .
  527. func (d *Dao) AddAudioCardsCache(c context.Context, vs map[int64]*model.AudioCard) (err error) {
  528. var cards []*model.Cards
  529. for _, v := range vs {
  530. cards = append(cards, &model.Cards{Type: model.CardPrefixAudio, AudioCard: v})
  531. }
  532. err = d.addCardsCache(c, cards...)
  533. return
  534. }
  535. // AudioCardsCache .
  536. func (d *Dao) AudioCardsCache(c context.Context, ids []int64) (vs map[int64]*model.AudioCard, err error) {
  537. var cards map[string]*model.Cards
  538. var idsStr []string
  539. for _, id := range ids {
  540. idsStr = append(idsStr, model.CardPrefixAudio+strconv.FormatInt(id, 10))
  541. }
  542. if cards, err = d.cardsCache(c, idsStr); err != nil {
  543. return
  544. }
  545. vs = make(map[int64]*model.AudioCard)
  546. for _, card := range cards {
  547. if (card != nil) && (card.AudioCard != nil) {
  548. vs[card.AudioCard.ID] = card.AudioCard
  549. }
  550. }
  551. return
  552. }
  553. // AddMallCardsCache .
  554. func (d *Dao) AddMallCardsCache(c context.Context, vs map[int64]*model.MallCard) (err error) {
  555. var cards []*model.Cards
  556. for _, v := range vs {
  557. cards = append(cards, &model.Cards{Type: model.CardPrefixMall, MallCard: v})
  558. }
  559. err = d.addCardsCache(c, cards...)
  560. return
  561. }
  562. // MallCardsCache .
  563. func (d *Dao) MallCardsCache(c context.Context, ids []int64) (vs map[int64]*model.MallCard, err error) {
  564. var cards map[string]*model.Cards
  565. var idsStr []string
  566. for _, id := range ids {
  567. idsStr = append(idsStr, model.CardPrefixMall+strconv.FormatInt(id, 10))
  568. }
  569. if cards, err = d.cardsCache(c, idsStr); err != nil {
  570. return
  571. }
  572. vs = make(map[int64]*model.MallCard)
  573. for _, card := range cards {
  574. if (card != nil) && (card.MallCard != nil) {
  575. vs[card.MallCard.ID] = card.MallCard
  576. }
  577. }
  578. return
  579. }
  580. // AddTicketCardsCache .
  581. func (d *Dao) AddTicketCardsCache(c context.Context, vs map[int64]*model.TicketCard) (err error) {
  582. var cards []*model.Cards
  583. for _, v := range vs {
  584. cards = append(cards, &model.Cards{Type: model.CardPrefixTicket, TicketCard: v})
  585. }
  586. err = d.addCardsCache(c, cards...)
  587. return
  588. }
  589. // TicketCardsCache .
  590. func (d *Dao) TicketCardsCache(c context.Context, ids []int64) (vs map[int64]*model.TicketCard, err error) {
  591. var cards map[string]*model.Cards
  592. var idsStr []string
  593. for _, id := range ids {
  594. idsStr = append(idsStr, model.CardPrefixTicket+strconv.FormatInt(id, 10))
  595. }
  596. if cards, err = d.cardsCache(c, idsStr); err != nil {
  597. return
  598. }
  599. vs = make(map[int64]*model.TicketCard)
  600. for _, card := range cards {
  601. if (card != nil) && (card.TicketCard != nil) {
  602. vs[card.TicketCard.ID] = card.TicketCard
  603. }
  604. }
  605. return
  606. }
  607. // CacheHotspots .
  608. func (d *Dao) CacheHotspots(c context.Context) (res []*model.Hotspot, err error) {
  609. res, err = d.cacheHotspots(c)
  610. for _, r := range res {
  611. if r.TopArticles == nil {
  612. r.TopArticles = []int64{}
  613. }
  614. }
  615. return
  616. }