123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663 |
- package dao
- import (
- "context"
- "fmt"
- "strconv"
- "strings"
- "sync"
- "time"
- "go-common/app/interface/openplatform/article/model"
- "go-common/library/cache/memcache"
- "go-common/library/ecode"
- "go-common/library/log"
- "go-common/library/xstr"
- "go-common/library/sync/errgroup"
- )
- const (
- _prefixArtMeta = "art_mp_%d"
- _prefixArtContent = "art_c_%d"
- _prefixArtKeywords = "art_kw_%d"
- _prefixArtStat = "art_s_%d"
- _prefixCard = "art_cards_"
- _bulkSize = 50
- )
- func artMetaKey(id int64) string {
- return fmt.Sprintf(_prefixArtMeta, id)
- }
- func artContentKey(id int64) string {
- return fmt.Sprintf(_prefixArtContent, id)
- }
- func artKeywordsKey(id int64) string {
- return fmt.Sprintf(_prefixArtKeywords, id)
- }
- func artStatsKey(id int64) string {
- return fmt.Sprintf(_prefixArtStat, id)
- }
- func cardKey(id string) string {
- return _prefixCard + id
- }
- func hotspotsKey() string {
- return fmt.Sprintf("art_hotspots")
- }
- func mcHotspotKey(id int64) string {
- return fmt.Sprintf("art_hotspot_%d", id)
- }
- func mcAuthorKey(mid int64) string {
- return fmt.Sprintf("art_author_%d", mid)
- }
- func mcTagKey(tag int64) string {
- return fmt.Sprintf("tag_aids_%d", tag)
- }
- func mcUpStatKey(mid int64) string {
- var (
- hour int
- day int
- )
- now := time.Now()
- hour = now.Hour()
- if hour < 7 {
- day = now.Add(time.Hour * -24).Day()
- } else {
- day = now.Day()
- }
- return fmt.Sprintf("up_stat_daily_%d_%d", mid, day)
- }
- // statsValue convert stats to string, format: "view,favorite,like,unlike,reply..."
- func statsValue(s *model.Stats) string {
- if s == nil {
- return ",,,,,,"
- }
- ids := []int64{s.View, s.Favorite, s.Like, s.Dislike, s.Reply, s.Share, s.Coin}
- return xstr.JoinInts(ids)
- }
- func revoverStatsValue(c context.Context, s string) (res *model.Stats) {
- var (
- vs []int64
- err error
- )
- res = new(model.Stats)
- if s == "" {
- return
- }
- if vs, err = xstr.SplitInts(s); err != nil || len(vs) < 7 {
- PromError("mc:stats解析")
- log.Error("dao.revoverStatsValue(%s) err: %+v", s, err)
- return
- }
- res = &model.Stats{
- View: vs[0],
- Favorite: vs[1],
- Like: vs[2],
- Dislike: vs[3],
- Reply: vs[4],
- Share: vs[5],
- Coin: vs[6],
- }
- return
- }
- // pingMc ping memcache
- func (d *Dao) pingMC(c context.Context) (err error) {
- conn := d.mc.Get(c)
- defer conn.Close()
- item := memcache.Item{Key: "ping", Value: []byte{1}, Expiration: d.mcArticleExpire}
- err = conn.Set(&item)
- return
- }
- //AddArticlesMetaCache add articles meta cache
- func (d *Dao) AddArticlesMetaCache(c context.Context, vs ...*model.Meta) (err error) {
- conn := d.mc.Get(c)
- defer conn.Close()
- for _, v := range vs {
- if v == nil {
- continue
- }
- item := &memcache.Item{Key: artMetaKey(v.ID), Object: v, Flags: memcache.FlagProtobuf, Expiration: d.mcArticleExpire}
- if err = conn.Set(item); err != nil {
- PromError("mc:增加文章meta缓存")
- log.Error("conn.Store(%s) error(%+v)", artMetaKey(v.ID), err)
- return
- }
- }
- return
- }
- // ArticleMetaCache gets article's meta cache.
- func (d *Dao) ArticleMetaCache(c context.Context, aid int64) (res *model.Meta, err error) {
- var (
- conn = d.mc.Get(c)
- key = artMetaKey(aid)
- )
- defer conn.Close()
- reply, err := conn.Get(key)
- if err != nil {
- if err == memcache.ErrNotFound {
- missedCount.Incr("article-meta")
- err = nil
- return
- }
- PromError("mc:获取文章meta缓存")
- log.Error("conn.Get(%v) error(%+v)", key, err)
- return
- }
- res = &model.Meta{}
- if err = conn.Scan(reply, res); err != nil {
- PromError("mc:文章meta缓存json解析")
- log.Error("reply.Scan(%s) error(%+v)", reply.Value, err)
- return
- }
- res.Strong()
- cachedCount.Incr("article-meta")
- return
- }
- //ArticlesMetaCache articles meta cache
- func (d *Dao) ArticlesMetaCache(c context.Context, ids []int64) (cached map[int64]*model.Meta, missed []int64, err error) {
- if len(ids) == 0 {
- return
- }
- cached = make(map[int64]*model.Meta, len(ids))
- allKeys := make([]string, 0, len(ids))
- idmap := make(map[string]int64, len(ids))
- for _, id := range ids {
- k := artMetaKey(id)
- allKeys = append(allKeys, k)
- idmap[k] = id
- }
- group, errCtx := errgroup.WithContext(c)
- mutex := sync.Mutex{}
- keysLen := len(allKeys)
- for i := 0; i < keysLen; i += _bulkSize {
- var keys []string
- if (i + _bulkSize) > keysLen {
- keys = allKeys[i:]
- } else {
- keys = allKeys[i : i+_bulkSize]
- }
- group.Go(func() (err error) {
- conn := d.mc.Get(errCtx)
- defer conn.Close()
- replys, err := conn.GetMulti(keys)
- if err != nil {
- PromError("mc:获取文章meta缓存")
- log.Error("conn.Gets(%v) error(%+v)", keys, err)
- err = nil
- return
- }
- for key, item := range replys {
- art := &model.Meta{}
- if err = conn.Scan(item, art); err != nil {
- PromError("mc:文章meta缓存json解析")
- log.Error("item.Scan(%s) error(%+v)", item.Value, err)
- err = nil
- continue
- }
- mutex.Lock()
- cached[idmap[key]] = art.Strong()
- delete(idmap, key)
- mutex.Unlock()
- }
- return
- })
- }
- group.Wait()
- missed = make([]int64, 0, len(idmap))
- for _, id := range idmap {
- missed = append(missed, id)
- }
- missedCount.Add("article-meta", int64(len(missed)))
- cachedCount.Add("article-meta", int64(len(cached)))
- return
- }
- // AddArticleStatsCache batch set article cache.
- func (d *Dao) AddArticleStatsCache(c context.Context, id int64, v *model.Stats) (err error) {
- conn := d.mc.Get(c)
- defer conn.Close()
- bs := []byte(statsValue(v))
- item := &memcache.Item{Key: artStatsKey(id), Value: bs, Expiration: d.mcStatsExpire}
- if err = conn.Set(item); err != nil {
- PromError("mc:增加文章统计缓存")
- log.Error("conn.Store(%s) error(%+v)", artStatsKey(id), err)
- }
- return
- }
- //AddArticleContentCache add article content cache
- func (d *Dao) AddArticleContentCache(c context.Context, id int64, content string) (err error) {
- conn := d.mc.Get(c)
- defer conn.Close()
- var bs = []byte(content)
- item := &memcache.Item{Key: artContentKey(id), Value: bs, Expiration: d.mcArticleExpire, Flags: memcache.FlagGzip}
- if err = conn.Set(item); err != nil {
- PromError("mc:增加文章内容缓存")
- log.Error("conn.Store(%s) error(%+v)", artContentKey(id), err)
- }
- return
- }
- // AddArticleKeywordsCache add article keywords cache.
- func (d *Dao) AddArticleKeywordsCache(c context.Context, id int64, keywords string) (err error) {
- conn := d.mc.Get(c)
- defer conn.Close()
- var bs = []byte(keywords)
- item := &memcache.Item{Key: artKeywordsKey(id), Value: bs, Expiration: d.mcArticleExpire, Flags: memcache.FlagGzip}
- if err = conn.Set(item); err != nil {
- PromError("mc:增加文章关键字缓存")
- log.Error("conn.Store(%s) error(%+v)", artKeywordsKey(id), err)
- }
- return
- }
- // ArticleContentCache article content cache
- func (d *Dao) ArticleContentCache(c context.Context, id int64) (res string, err error) {
- conn := d.mc.Get(c)
- defer conn.Close()
- reply, err := conn.Get(artContentKey(id))
- if err != nil {
- if err == memcache.ErrNotFound {
- missedCount.Incr("article-content")
- err = nil
- return
- }
- PromError("mc:获取文章内容缓存")
- log.Error("conn.Get(%v) error(%+v)", artContentKey(id), err)
- return
- }
- err = conn.Scan(reply, &res)
- return
- }
- // ArticleKeywordsCache article Keywords cache
- func (d *Dao) ArticleKeywordsCache(c context.Context, id int64) (res string, err error) {
- conn := d.mc.Get(c)
- defer conn.Close()
- reply, err := conn.Get(artKeywordsKey(id))
- if err != nil {
- if err == memcache.ErrNotFound {
- missedCount.Incr("article-keywords")
- err = nil
- return
- }
- PromError("mc:获取文章关键字缓存")
- log.Error("conn.Get(%v) error(%+v)", artKeywordsKey(id), err)
- return
- }
- err = conn.Scan(reply, &res)
- return
- }
- //DelArticleMetaCache delete article meta cache
- func (d *Dao) DelArticleMetaCache(c context.Context, id int64) (err error) {
- var (
- key = artMetaKey(id)
- conn = d.mc.Get(c)
- )
- defer conn.Close()
- if err = conn.Delete(key); err != nil {
- if err == memcache.ErrNotFound {
- err = nil
- } else {
- PromError("mc:删除文章meta缓存")
- log.Error("key(%v) error(%+v)", key, err)
- }
- }
- return
- }
- // DelArticleStatsCache delete article stats cache
- func (d *Dao) DelArticleStatsCache(c context.Context, id int64) (err error) {
- var (
- key = artStatsKey(id)
- conn = d.mc.Get(c)
- )
- defer conn.Close()
- if err = conn.Delete(key); err != nil {
- if err == memcache.ErrNotFound {
- err = nil
- } else {
- PromError("mc:删除文章stats缓存")
- log.Error("key(%v) error(%+v)", key, err)
- }
- }
- return
- }
- //DelArticleContentCache delete article content cache
- func (d *Dao) DelArticleContentCache(c context.Context, id int64) (err error) {
- var (
- key = artContentKey(id)
- conn = d.mc.Get(c)
- )
- defer conn.Close()
- if err = conn.Delete(key); err != nil {
- if err == memcache.ErrNotFound {
- err = nil
- } else {
- PromError("mc:删除文章content缓存")
- log.Error("key(%v) error(%+v)", key, err)
- }
- }
- return
- }
- // ArticleStatsCache article stats cache
- func (d *Dao) ArticleStatsCache(c context.Context, id int64) (res *model.Stats, err error) {
- if id == 0 {
- err = ecode.NothingFound
- return
- }
- var (
- conn = d.mc.Get(c)
- key = artStatsKey(id)
- statsStr string
- )
- defer conn.Close()
- reply, err := conn.Get(key)
- if err != nil {
- if err == memcache.ErrNotFound {
- res = nil
- err = nil
- return
- }
- PromError("mc:获取文章计数缓存")
- log.Error("conn.Get(%v) error(%+v)", key, err)
- return
- }
- if err = conn.Scan(reply, &statsStr); err == nil {
- res = revoverStatsValue(c, statsStr)
- } else {
- PromError("mc:获取文章计数缓存")
- log.Error("dao.ArticleStatsCache.reply.Scan(%v, %v) error(%+v)", key, statsStr, err)
- }
- return
- }
- // ArticlesStatsCache articles stats cache
- func (d *Dao) ArticlesStatsCache(c context.Context, ids []int64) (cached map[int64]*model.Stats, missed []int64, err error) {
- if len(ids) == 0 {
- return
- }
- cached = make(map[int64]*model.Stats, len(ids))
- allKeys := make([]string, 0, len(ids))
- idmap := make(map[string]int64, len(ids))
- for _, id := range ids {
- k := artStatsKey(id)
- allKeys = append(allKeys, k)
- idmap[k] = id
- }
- group, errCtx := errgroup.WithContext(c)
- mutex := sync.Mutex{}
- keysLen := len(allKeys)
- for i := 0; i < keysLen; i += _bulkSize {
- var keys []string
- if (i + _bulkSize) > keysLen {
- keys = allKeys[i:]
- } else {
- keys = allKeys[i : i+_bulkSize]
- }
- group.Go(func() (err error) {
- conn := d.mc.Get(errCtx)
- defer conn.Close()
- replys, err := conn.GetMulti(keys)
- if err != nil {
- PromError("mc:获取文章计数缓存")
- log.Error("conn.Gets(%v) error(%+v)", keys, err)
- err = nil
- return
- }
- for _, reply := range replys {
- var info string
- if e := conn.Scan(reply, &info); e != nil {
- PromError("mc:获取文章计数缓存scan")
- continue
- }
- art := revoverStatsValue(c, info)
- mutex.Lock()
- cached[idmap[reply.Key]] = art
- delete(idmap, reply.Key)
- mutex.Unlock()
- }
- return
- })
- }
- group.Wait()
- missed = make([]int64, 0, len(idmap))
- for _, id := range idmap {
- missed = append(missed, id)
- }
- missedCount.Add("article-stats", int64(len(missed)))
- cachedCount.Add("article-stats", int64(len(cached)))
- return
- }
- // AddCardsCache .
- func (d *Dao) addCardsCache(c context.Context, vs ...*model.Cards) (err error) {
- if len(vs) == 0 {
- return
- }
- conn := d.mc.Get(c)
- defer conn.Close()
- for _, v := range vs {
- if v == nil {
- continue
- }
- key := cardKey(v.Key())
- item := memcache.Item{Key: key, Object: v, Expiration: d.mcCardsExpire, Flags: memcache.FlagJSON}
- if err = conn.Set(&item); err != nil {
- PromError("mc:增加卡片缓存")
- log.Error("conn.Set(%s) error(%+v)", key, err)
- return
- }
- }
- return
- }
- // CardsCache ids like cv123 av123 au123
- func (d *Dao) cardsCache(c context.Context, ids []string) (res map[string]*model.Cards, err error) {
- if len(ids) == 0 {
- return
- }
- res = make(map[string]*model.Cards, len(ids))
- var keys []string
- for _, id := range ids {
- keys = append(keys, cardKey(id))
- }
- conn := d.mc.Get(c)
- replys, err := conn.GetMulti(keys)
- defer conn.Close()
- if err != nil {
- PromError("mc:获取cards缓存")
- log.Error("conn.Gets(%v) error(%+v)", keys, err)
- err = nil
- return
- }
- for _, reply := range replys {
- s := model.Cards{}
- if err = conn.Scan(reply, &s); err != nil {
- PromError("获取cards缓存json解析")
- log.Error("json.Unmarshal(%v) error(%+v)", reply.Value, err)
- err = nil
- continue
- }
- res[strings.TrimPrefix(reply.Key, _prefixCard)] = &s
- }
- return
- }
- // AddBangumiCardsCache .
- func (d *Dao) AddBangumiCardsCache(c context.Context, vs map[int64]*model.BangumiCard) (err error) {
- var cards []*model.Cards
- for _, v := range vs {
- cards = append(cards, &model.Cards{Type: model.CardPrefixBangumi, BangumiCard: v})
- }
- err = d.addCardsCache(c, cards...)
- return
- }
- // BangumiCardsCache .
- func (d *Dao) BangumiCardsCache(c context.Context, ids []int64) (vs map[int64]*model.BangumiCard, err error) {
- var cards map[string]*model.Cards
- var idsStr []string
- for _, id := range ids {
- idsStr = append(idsStr, model.CardPrefixBangumi+strconv.FormatInt(id, 10))
- }
- if cards, err = d.cardsCache(c, idsStr); err != nil {
- return
- }
- vs = make(map[int64]*model.BangumiCard)
- for _, card := range cards {
- if (card != nil) && (card.BangumiCard != nil) {
- vs[card.BangumiCard.ID] = card.BangumiCard
- }
- }
- return
- }
- // AddBangumiEpCardsCache .
- func (d *Dao) AddBangumiEpCardsCache(c context.Context, vs map[int64]*model.BangumiCard) (err error) {
- var cards []*model.Cards
- for _, v := range vs {
- cards = append(cards, &model.Cards{Type: model.CardPrefixBangumiEp, BangumiCard: v})
- }
- err = d.addCardsCache(c, cards...)
- return
- }
- // BangumiEpCardsCache .
- func (d *Dao) BangumiEpCardsCache(c context.Context, ids []int64) (vs map[int64]*model.BangumiCard, err error) {
- var cards map[string]*model.Cards
- var idsStr []string
- for _, id := range ids {
- idsStr = append(idsStr, model.CardPrefixBangumiEp+strconv.FormatInt(id, 10))
- }
- if cards, err = d.cardsCache(c, idsStr); err != nil {
- return
- }
- vs = make(map[int64]*model.BangumiCard)
- for _, card := range cards {
- if (card != nil) && (card.BangumiCard != nil) {
- vs[card.BangumiCard.ID] = card.BangumiCard
- }
- }
- return
- }
- // AddAudioCardsCache .
- func (d *Dao) AddAudioCardsCache(c context.Context, vs map[int64]*model.AudioCard) (err error) {
- var cards []*model.Cards
- for _, v := range vs {
- cards = append(cards, &model.Cards{Type: model.CardPrefixAudio, AudioCard: v})
- }
- err = d.addCardsCache(c, cards...)
- return
- }
- // AudioCardsCache .
- func (d *Dao) AudioCardsCache(c context.Context, ids []int64) (vs map[int64]*model.AudioCard, err error) {
- var cards map[string]*model.Cards
- var idsStr []string
- for _, id := range ids {
- idsStr = append(idsStr, model.CardPrefixAudio+strconv.FormatInt(id, 10))
- }
- if cards, err = d.cardsCache(c, idsStr); err != nil {
- return
- }
- vs = make(map[int64]*model.AudioCard)
- for _, card := range cards {
- if (card != nil) && (card.AudioCard != nil) {
- vs[card.AudioCard.ID] = card.AudioCard
- }
- }
- return
- }
- // AddMallCardsCache .
- func (d *Dao) AddMallCardsCache(c context.Context, vs map[int64]*model.MallCard) (err error) {
- var cards []*model.Cards
- for _, v := range vs {
- cards = append(cards, &model.Cards{Type: model.CardPrefixMall, MallCard: v})
- }
- err = d.addCardsCache(c, cards...)
- return
- }
- // MallCardsCache .
- func (d *Dao) MallCardsCache(c context.Context, ids []int64) (vs map[int64]*model.MallCard, err error) {
- var cards map[string]*model.Cards
- var idsStr []string
- for _, id := range ids {
- idsStr = append(idsStr, model.CardPrefixMall+strconv.FormatInt(id, 10))
- }
- if cards, err = d.cardsCache(c, idsStr); err != nil {
- return
- }
- vs = make(map[int64]*model.MallCard)
- for _, card := range cards {
- if (card != nil) && (card.MallCard != nil) {
- vs[card.MallCard.ID] = card.MallCard
- }
- }
- return
- }
- // AddTicketCardsCache .
- func (d *Dao) AddTicketCardsCache(c context.Context, vs map[int64]*model.TicketCard) (err error) {
- var cards []*model.Cards
- for _, v := range vs {
- cards = append(cards, &model.Cards{Type: model.CardPrefixTicket, TicketCard: v})
- }
- err = d.addCardsCache(c, cards...)
- return
- }
- // TicketCardsCache .
- func (d *Dao) TicketCardsCache(c context.Context, ids []int64) (vs map[int64]*model.TicketCard, err error) {
- var cards map[string]*model.Cards
- var idsStr []string
- for _, id := range ids {
- idsStr = append(idsStr, model.CardPrefixTicket+strconv.FormatInt(id, 10))
- }
- if cards, err = d.cardsCache(c, idsStr); err != nil {
- return
- }
- vs = make(map[int64]*model.TicketCard)
- for _, card := range cards {
- if (card != nil) && (card.TicketCard != nil) {
- vs[card.TicketCard.ID] = card.TicketCard
- }
- }
- return
- }
- // CacheHotspots .
- func (d *Dao) CacheHotspots(c context.Context) (res []*model.Hotspot, err error) {
- res, err = d.cacheHotspots(c)
- for _, r := range res {
- if r.TopArticles == nil {
- r.TopArticles = []int64{}
- }
- }
- return
- }
|