123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107 |
- package service
- import (
- "context"
- "errors"
- "strconv"
- "time"
- "go-common/app/interface/main/reply/dao/reply"
- model "go-common/app/interface/main/reply/model/reply"
- xmodel "go-common/app/interface/main/reply/model/xreply"
- accmdl "go-common/app/service/main/account/api"
- assmdl "go-common/app/service/main/assist/model/assist"
- "go-common/library/ecode"
- "go-common/library/log"
- "sort"
- "go-common/library/sync/errgroup.v2"
- )
- const (
- defaultChildrenSize = 5
- )
- // NewCursorByReplyID NewCursorByReplyID
- func (s *Service) NewCursorByReplyID(ctx context.Context, oid int64,
- otyp int8, replyID int64, size int, cmp model.Comp) (*model.Cursor, error) {
- rs, err := s.GetReplyByIDs(ctx, oid, otyp, []int64{replyID})
- if err != nil {
- return nil, err
- }
- if r, ok := rs[replyID]; ok {
- return model.NewCursor(int64(r.Floor), 0, size, cmp)
- }
- return nil, ecode.ReplyNotExist
- }
- // NewSubCursorByReplyID NewSubCursorByReplyID
- func (s *Service) NewSubCursorByReplyID(ctx context.Context, oid int64, otyp int8, replyID int64, size int, cmp model.Comp) (rootID int64, cursor *model.Cursor, err error) {
- rs, err := s.GetReplyByIDs(ctx, oid, otyp, []int64{replyID})
- if err != nil {
- return 0, nil, err
- }
- if r, ok := rs[replyID]; ok {
- if r.IsRoot() {
- rootID = r.RpID
- cursor, err = model.NewCursor(0, 1, size, cmp)
- return
- }
- // 不足一页面时,展示够一页
- floor := r.Floor
- if floor < size {
- floor = size
- }
- rootID = r.Root
- cursor, err = model.NewCursor(int64(floor), 0, size, cmp)
- return
- }
- return 0, nil, ecode.ReplyNotExist
- }
- // GetRootReplyListHeader GetRootReplyListHeader
- func (s *Service) GetRootReplyListHeader(ctx context.Context, sub *model.Subject, params *model.CursorParams) (*model.RootReplyListHeader, error) {
- var hotIDs []int64
- res, err := s.replyHotFeed(ctx, params.Mid, sub.Oid, int(sub.Type), 1, params.HotSize+2)
- if err == nil && res != nil && len(res.RpIDs) > 0 {
- log.Info("reply-feed(test): reply abtest mid(%d) oid(%d) type(%d) test name(%s) rpIDs(%v)", params.Mid, sub.Oid, sub.Type, res.Name, res.RpIDs)
- hotIDs = res.RpIDs
- } else {
- if err != nil {
- log.Error("reply-feed error(%v)", err)
- err = nil
- } else {
- log.Info("reply-feed(origin): reply abtest mid(%d) oid(%d) type(%d) test name(%s) rpIDs(%v)", params.Mid, sub.Oid, sub.Type, res.Name, res.RpIDs)
- }
- if hotIDs, err = s.GetRootReplyIDs(ctx, sub.Oid, sub.Type, model.SortByLike, 0, int64(params.HotSize+2)); err != nil {
- log.Error("%v", err)
- return nil, err
- }
- }
- var parentIDs []int64
- parentIDs = append(parentIDs, hotIDs...)
- var adminTopReply, upperTopReply *model.Reply
- if sub.AttrVal(model.SubAttrAdminTop) == model.AttrYes {
- adminTopReply, err = s.GetTopReply(ctx, params.Oid, params.OTyp, model.SubAttrAdminTop)
- if err != nil {
- return nil, err
- }
- if adminTopReply != nil {
- parentIDs = append(parentIDs, adminTopReply.RpID)
- }
- }
- if sub.AttrVal(model.SubAttrUpperTop) == model.AttrYes {
- upperTopReply, err = s.GetTopReply(ctx, params.Oid, params.OTyp, model.SubAttrUpperTop)
- if err != nil {
- return nil, err
- }
- if upperTopReply != nil {
- if !upperTopReply.IsNormal() && sub.Mid != params.Mid {
- upperTopReply = nil
- } else {
- parentIDs = append(parentIDs, upperTopReply.RpID)
- }
- }
- }
- parentChildrenIDRelation, err := s.ParentChildrenReplyIDRelation(ctx, sub, parentIDs)
- if err != nil {
- return nil, err
- }
- idReplyMap, err := s.IDReplyMap(ctx, sub, parentChildrenIDRelation)
- if err != nil {
- return nil, err
- }
- rootIDReplyMap := assemble(idReplyMap, parentChildrenIDRelation)
- if adminTopReply != nil {
- if r, ok := rootIDReplyMap[adminTopReply.RpID]; ok {
- adminTopReply = r
- }
- // For historic reasons, TopReply and HotReply may be overlapped
- hotIDs = Remove(hotIDs, adminTopReply.RpID)
- }
- if upperTopReply != nil {
- if r, ok := rootIDReplyMap[upperTopReply.RpID]; ok {
- upperTopReply = r
- }
- // For historic reasons, TopReply and HotReply may be overlapped
- hotIDs = Remove(hotIDs, upperTopReply.RpID)
- }
- return &model.RootReplyListHeader{
- TopAdmin: adminTopReply,
- TopUpper: upperTopReply,
- Hots: filterHot(Fetch(rootIDReplyMap, hotIDs), params.HotSize),
- }, nil
- }
- func filterHot(rs []*model.Reply, maxSize int) (hots []*model.Reply) {
- for _, r := range rs {
- if r.Like >= 3 {
- hots = append(hots, r)
- }
- }
- if hots == nil {
- hots = _emptyReplies
- } else if len(hots) > maxSize {
- hots = hots[:maxSize]
- }
- return hots
- }
- func needHeader(cursor *model.Cursor, rootLen int) bool {
- return cursor.Latest() ||
- (cursor.Increase() && rootLen < int(cursor.Len()))
- }
- // NeedInsertPendingReply NeedInsertPendingReply
- func NeedInsertPendingReply(params *model.CursorParams, sub *model.Subject) bool {
- return params.Mid > 0 &&
- params.Sort == model.SortByFloor &&
- sub.AttrVal(model.SubAttrAudit) == model.AttrYes
- }
- func collect(r *model.Reply, allIDs []int64, allMIDs []int64,
- allReply []*model.Reply) ([]int64, []int64, []*model.Reply) {
- if r == nil {
- return nil, nil, nil
- }
- allIDs = append(allIDs, r.RpID)
- allReply = append(allReply, r)
- allMIDs = append(allMIDs, r.Mid)
- if r.Content != nil {
- for _, mid := range r.Content.Ats {
- allMIDs = append(allMIDs, mid)
- }
- }
- return allIDs, allMIDs, allReply
- }
- // IDReplyMap IDReplyMap
- func (s *Service) IDReplyMap(ctx context.Context, sub *model.Subject,
- parentChildrenIDRelation map[int64][]int64) (map[int64]*model.Reply, error) {
- var allIDs []int64
- for parentID, childrenIDs := range parentChildrenIDRelation {
- allIDs = append(allIDs, childrenIDs...)
- allIDs = append(allIDs, parentID)
- }
- // WARNING: GetReplyByIDs should not contains subReplies, but currently there
- // exists a bug, which makes `idReplyMap` may contains sub_reply
- idReplyMap, err := s.GetReplyByIDs(ctx, sub.Oid, sub.Type, allIDs)
- if err != nil {
- return nil, err
- }
- // temporary solution :(, remove all children replies
- for _, reply := range idReplyMap {
- if reply.Replies != nil {
- reply.Replies = reply.Replies[:0]
- }
- }
- return idReplyMap, nil
- }
- // RootReplyListByCursor RootReplyListByCursor
- func (s *Service) RootReplyListByCursor(ctx context.Context, sub *model.Subject, params *model.CursorParams) ([]*model.Reply, error) {
- var parentIDs []int64
- if params.Cursor.Latest() {
- // 忽略错误,这个请求只为了增加统计数据
- s.replyFeed(ctx, params.Mid, 1, 20)
- } else {
- s.replyFeed(ctx, params.Mid, 2, 20)
- }
- rootIDs, err := s.GetRootReplyIDsByCursor(ctx, sub, params.Sort, params.Cursor)
- if err != nil {
- return nil, err
- }
- // 老版本折叠评论的逻辑
- if params.ShowFolded && sub.HasFolded() {
- foldedrpIDs, _ := s.foldedRepliesCursor(ctx, sub, 0, params.Cursor)
- if len(foldedrpIDs) > 0 {
- rootIDs = append(rootIDs, foldedrpIDs...)
- sort.Slice(rootIDs, func(x, y int) bool { return rootIDs[x] > rootIDs[y] })
- length := len(rootIDs)
- if length > params.Cursor.Len() {
- if params.Cursor.Increase() {
- // 对于根评论列表,往楼层大的方向翻页是向上翻,需要从后往前截断
- rootIDs = rootIDs[length-params.Cursor.Len():]
- } else {
- rootIDs = rootIDs[:params.Cursor.Len()]
- }
- }
- }
- }
- parentIDs = append(parentIDs, rootIDs...)
- parentChildrenIDRelation, err := s.ParentChildrenReplyIDRelation(ctx, sub, parentIDs)
- if err != nil {
- return nil, err
- }
- idReplyMap, err := s.IDReplyMap(ctx, sub, parentChildrenIDRelation)
- if err != nil {
- return nil, err
- }
- if NeedInsertPendingReply(params, sub) {
- // WARNING: here we assume that pending replies have no children
- // otherwise, we need to change logic here
- pendingIDReplyMap, err := s.GetPendingReply(ctx, params.Mid, sub.Oid, sub.Type)
- if err != nil {
- return nil, err
- }
- for id, r := range pendingIDReplyMap {
- if r.IsRoot() && !r.IsTop() {
- // insert pending reply into root reply list
- if params.Cursor.Latest() &&
- ((len(rootIDs) > 0 && id > rootIDs[0]) || len(rootIDs) == 0) {
- // when fetch latest reply list, and root reply list's length < the default size
- // and the pending reply ID > the max rootID
- // then just append the pending reply
- rootIDs = append([]int64{id}, rootIDs...)
- if len(rootIDs) > int(params.Cursor.Len()) {
- rootIDs = rootIDs[:params.Cursor.Len()]
- }
- } else {
- // otherwise, we need an algorithm to insert pending replyID into
- // rootIDs
- rootIDs = InsertInto(rootIDs, id, int(params.Cursor.Len()), model.OrderDESC)
- }
- parentChildrenIDRelation[id] = []int64{}
- } else if _, ok := idReplyMap[r.Root]; ok {
- // insert pending reply into sub reply list
- parentChildrenIDRelation[r.Root] = InsertInto(parentChildrenIDRelation[r.Root], id, defaultChildrenSize, model.OrderASC)
- } else {
- continue
- }
- sub.ACount++
- idReplyMap[id] = r
- }
- }
- return Fetch(assemble(idReplyMap, parentChildrenIDRelation), rootIDs), nil
- }
- // Remove Remove
- func Remove(arr []int64, k int64) []int64 {
- b := arr[:0]
- for _, a := range arr {
- if a != k {
- b = append(b, a)
- }
- }
- return b
- }
- // Unique Unique
- func Unique(arr []int64) []int64 {
- m := make(map[int64]struct{})
- for _, a := range arr {
- m[a] = struct{}{}
- }
- res := make([]int64, 0)
- for a := range m {
- res = append(res, a)
- }
- return res
- }
- // GetTopReply GetTopReply
- func (s *Service) GetTopReply(ctx context.Context, oid int64, otyp int8, topType uint32) (*model.Reply, error) {
- r, err := s.dao.Mc.GetTop(ctx, oid, otyp, topType)
- if err != nil {
- return nil, err
- }
- if r == nil {
- s.dao.Databus.AddTop(ctx, oid, otyp, topType)
- return nil, nil
- }
- return r, nil
- }
- // GetReplyFromDBByIDs GetReplyFromDBByIDs
- func (s *Service) GetReplyFromDBByIDs(ctx context.Context, oid int64, otyp int8, ids []int64) ([]*model.Reply, error) {
- rs := make([]*model.Reply, 0)
- if len(ids) == 0 {
- return rs, nil
- }
- idReplyMap, err := s.dao.Reply.GetByIds(ctx, oid, otyp, ids)
- if err != nil {
- return nil, err
- }
- idReplyContentMap, err := s.dao.Content.GetByIds(ctx, oid, ids)
- if err != nil {
- return nil, err
- }
- for _, id := range ids {
- if r, ok := idReplyMap[id]; ok {
- if r == nil {
- rs = append(rs, nil)
- continue
- }
- if content, ok := idReplyContentMap[id]; ok {
- r.Content = content
- }
- rs = append(rs, r)
- }
- }
- return rs, nil
- }
- // GetReplyByIDs GetReplyByIDs
- func (s *Service) GetReplyByIDs(ctx context.Context, oid int64, otyp int8, ids []int64) (map[int64]*model.Reply, error) {
- res := make(map[int64]*model.Reply)
- if len(ids) == 0 {
- return res, nil
- }
- cachedReplies, missedIDs, err := s.dao.Mc.GetReplyByIDs(ctx, ids)
- var rs []*model.Reply
- if err != nil {
- rs, err = s.GetReplyFromDBByIDs(ctx, oid, otyp, ids)
- if err != nil {
- return nil, err
- }
- for _, r := range rs {
- res[r.RpID] = r
- }
- return res, nil
- }
- for _, r := range cachedReplies {
- res[r.RpID] = r
- }
- if len(missedIDs) == 0 {
- return res, nil
- }
- missedReplies, err := s.GetReplyFromDBByIDs(ctx, oid, otyp, missedIDs)
- if err != nil {
- return nil, err
- }
- select {
- case s.replyChan <- replyChan{rps: missedReplies}:
- default:
- log.Error("s.replyChan is full")
- }
- for _, r := range missedReplies {
- res[r.RpID] = r.Clone()
- }
- return res, nil
- }
- // GetChildrenIDsByCursor GetChildrenIDsByCursor
- func (s *Service) GetChildrenIDsByCursor(ctx context.Context, sub *model.Subject, rootID int64, sort int8, cursor *model.Cursor) ([]int64, error) {
- k := reply.GenNewChildrenKeyByRootReplyID(rootID)
- cacheExist, err := s.dao.Redis.ExpireCache(ctx, k)
- if err != nil {
- return nil, err
- }
- var ids []int64
- if cacheExist {
- ids, err = s.dao.Redis.RangeChildrenIDByCursorScore(ctx, k, cursor)
- if err != nil {
- return nil, err
- }
- return ids, nil
- }
- s.dao.Databus.RecoverIndexByRoot(ctx, sub.Oid, rootID, sub.Type)
- switch sort {
- case model.SortByFloor:
- ids, err = s.dao.Reply.ChildrenIDSortByFloorCursor(ctx, sub.Oid, sub.Type, rootID, cursor)
- default:
- return nil, ecode.RequestErr
- }
- if err != nil {
- return nil, err
- }
- return ids, nil
- }
- // GetRootReplyIDsByCursor GetRootReplyIDsByCursor
- func (s *Service) GetRootReplyIDsByCursor(ctx context.Context, sub *model.Subject, sort int8, cursor *model.Cursor) ([]int64, error) {
- var (
- ids []int64
- isEnd bool
- )
- if sub.RCount == 0 {
- return []int64{}, nil
- }
- k := s.dao.Redis.CacheKeyRootReplyIDs(sub.Oid, sub.Type, sort)
- cacheExist, err := s.dao.Redis.ExpireCache(ctx, k)
- if err != nil {
- return nil, err
- }
- minFloor := cursor.Current() - 20
- if cursor.Latest() {
- minFloor = int64(sub.Count) - 20
- }
- if minFloor <= 0 {
- minFloor = 1
- }
- if cacheExist {
- ids, isEnd, err = s.dao.Redis.RangeRootIDByCursorScore(ctx, k, cursor)
- if err != nil {
- return nil, err
- }
- if sort == model.SortByFloor && len(ids) < cursor.Len() && !cursor.Increase() && !isEnd {
- ids, err = s.dao.Reply.RootIDSortByFloorCursor(ctx, sub.Oid, sub.Type, cursor)
- if err != nil {
- return nil, err
- }
- s.dao.Databus.RecoverFloorIdx(ctx, sub.Oid, sub.Type, int(minFloor), true)
- }
- return ids, nil
- }
- switch sort {
- case model.SortByFloor:
- s.dao.Databus.RecoverFloorIdx(ctx, sub.Oid, sub.Type, int(minFloor), true)
- ids, err = s.dao.Reply.RootIDSortByFloorCursor(ctx, sub.Oid, sub.Type, cursor)
- default:
- return nil, ecode.RequestErr
- }
- if err != nil {
- return nil, err
- }
- return ids, nil
- }
- // GetRootReplyIDs GetRootReplyIDs
- func (s *Service) GetRootReplyIDs(ctx context.Context, oid int64, otyp int8, sort int8, offset, limit int64) ([]int64, error) {
- var ids []int64
- k := s.dao.Redis.CacheKeyRootReplyIDs(oid, otyp, sort)
- cacheExist, err := s.dao.Redis.ExpireCache(ctx, k)
- if err != nil {
- return nil, err
- }
- if cacheExist {
- ids, err = s.dao.Redis.RangeRootReplyIDs(ctx, k, int(offset), int(offset+limit-1))
- if err != nil {
- return nil, err
- }
- return ids, nil
- }
- s.dao.Databus.RecoverIndex(ctx, oid, otyp, sort)
- switch sort {
- case model.SortByFloor:
- ids, err = s.dao.Reply.GetIdsSortFloor(ctx, oid, otyp, int(offset), int(limit))
- case model.SortByCount:
- ids, err = s.dao.Reply.GetIdsSortCount(ctx, oid, otyp, int(offset), int(limit))
- case model.SortByLike:
- ids, err = s.dao.Reply.GetIdsSortLike(ctx, oid, otyp, int(offset), int(limit))
- default:
- log.Error("unsupported sort:%d", sort)
- return nil, ecode.RequestErr
- }
- if err != nil {
- return nil, err
- }
- return ids, nil
- }
- // GetSubject GetSubject
- func (s *Service) GetSubject(ctx context.Context, oid int64, tp int8) (*model.Subject, error) {
- subject, err := s.getSubject(ctx, oid, tp)
- if err != nil {
- return nil, err
- }
- if subject.State == model.SubStateForbid {
- return nil, ecode.ReplyForbidReply
- }
- return subject, nil
- }
- func elementOf(k int64, arr []int64) bool {
- for _, i := range arr {
- if i == k {
- return true
- }
- }
- return false
- }
- // InsertInto insert `id` into sorted list(order by `cmp`) `ids`
- // after insertion if the total length > `size`, truncate the extra element
- func InsertInto(ids []int64, id int64, size int, cmp model.Comp) []int64 {
- if elementOf(id, ids) {
- return ids
- }
- if len(ids) < size {
- return model.SortArr(append(ids, id), cmp)
- }
- ids = model.SortArr(ids, cmp)
- if !withInRange(id, ids[0], ids[len(ids)-1]) {
- return ids
- }
- for i := 0; i < len(ids); i++ {
- if cmp(id, ids[i]) {
- ids = append(ids[:i], append([]int64{id}, ids[i:]...)...)
- break
- }
- }
- return ids[:size]
- }
- func withInRange(i, begin, end int64) bool {
- return (begin > i && end < i) || (begin < i && end > i)
- }
- // FillRootReplies FillRootReplies
- func (s *Service) FillRootReplies(ctx context.Context,
- rs []*model.Reply,
- mid int64,
- ip string,
- htmlEscape bool,
- sub *model.Subject) {
- var (
- allReply []*model.Reply
- allIDs, allMIDs []int64
- )
- if mid > 0 {
- allMIDs = append(allMIDs, mid)
- }
- for _, r := range rs {
- allIDs, allMIDs, allReply = collect(r, allIDs, allMIDs, allReply)
- for _, rr := range r.Replies {
- allIDs, allMIDs, allReply = collect(rr, allIDs, allMIDs, allReply)
- }
- }
- s.fillReplies(ctx, sub, allIDs, allReply, Unique(allMIDs), mid, ip, htmlEscape)
- }
- func (s *Service) fillReplies(ctx context.Context,
- sub *model.Subject,
- allReplyIDs []int64,
- rs []*model.Reply,
- mids []int64,
- reqMid int64,
- ip string,
- htmlEscape bool) {
- var (
- actionMap map[int64]int8
- blackedMap map[int64]bool
- relationMap map[int64]*accmdl.RelationReply
- assistMap map[int64]int
- fansMap map[int64]*model.FansDetail
- accountMap map[int64]*accmdl.Card
- )
- g := errgroup.WithContext(ctx)
- if reqMid > 0 {
- g.Go(func(ctx context.Context) error {
- actionMap, _ = s.actions(ctx, reqMid, sub.Oid, allReplyIDs)
- return nil
- })
- g.Go(func(ctx context.Context) error {
- relationMap, _ = s.GetRelationMap(ctx, reqMid, mids, ip)
- return nil
- })
- g.Go(func(ctx context.Context) error {
- blackedMap, _ = s.GetBlacklistMap(ctx, reqMid, ip)
- return nil
- })
- }
- g.Go(func(ctx context.Context) error {
- accountMap, _ = s.GetAccountInfoMap(ctx, mids, ip)
- return nil
- })
- if !(s.IsWhiteAid(sub.Oid, sub.Type)) {
- g.Go(func(ctx context.Context) error {
- assistMap, _ = s.GetAssistMap(ctx, sub.Mid, ip)
- return nil
- })
- g.Go(func(ctx context.Context) error {
- fansMap, _ = s.GetFansMap(ctx, mids, sub.Mid, ip)
- return nil
- })
- }
- g.Wait()
- for _, r := range rs {
- s.fillReply(r,
- htmlEscape,
- accountMap,
- actionMap,
- fansMap,
- blackedMap,
- assistMap,
- relationMap)
- }
- }
- func (s *Service) fillReply(r *model.Reply,
- escape bool,
- accountMap map[int64]*accmdl.Card,
- actionMap map[int64]int8,
- fansMap map[int64]*model.FansDetail,
- blackedMap map[int64]bool,
- assistMap map[int64]int,
- relationMap map[int64]*accmdl.RelationReply) {
- if r == nil {
- return
- }
- r.FillFolder()
- r.FillStr(escape)
- if r.Content != nil {
- r.Content.FillAts(accountMap)
- }
- r.Action = actionMap[r.RpID]
- r.Member = new(model.Member)
- var (
- ok bool
- blacked bool
- card *accmdl.Card
- )
- if card, ok = accountMap[r.Mid]; ok {
- r.Member.Info = new(model.Info)
- r.Member.Info.FromCard(card)
- } else {
- r.Member.Info = new(model.Info)
- *r.Member.Info = *s.defMember
- r.Member.Info.Mid = strconv.FormatInt(r.Mid, 10)
- }
- if r.Member.FansDetail, ok = fansMap[r.Mid]; ok {
- r.FansGrade = r.Member.FansDetail.Status
- }
- if blacked, ok = blackedMap[r.Mid]; ok && blacked {
- r.State = model.ReplyStateBlacklist
- }
- if r.Replies == nil {
- r.Replies = []*model.Reply{}
- }
- if _, ok = assistMap[r.Mid]; ok {
- r.Assist = 1
- }
- if attetion, ok := relationMap[r.Mid]; ok {
- if attetion.Following {
- r.Member.Following = 1
- }
- }
- if r.RCount < 0 {
- r.RCount = 0
- }
- }
- // Fetch Fetch
- func Fetch(idReplyMap map[int64]*model.Reply, ids []int64) []*model.Reply {
- res := make([]*model.Reply, 0, len(ids))
- for _, pid := range ids {
- if p, ok := idReplyMap[pid]; ok && p != nil {
- res = append(res, p)
- }
- }
- return res
- }
- // assemble insert children replies into their corresponding parents
- func assemble(idReplyMap map[int64]*model.Reply, parentChildrenMap map[int64][]int64) map[int64]*model.Reply {
- parentIDs := make([]int64, 0)
- for pid := range parentChildrenMap {
- parentIDs = append(parentIDs, pid)
- }
- res := make(map[int64]*model.Reply)
- for _, pid := range parentIDs {
- if p, ok := idReplyMap[pid]; ok {
- if childrenIDs, ok := parentChildrenMap[pid]; ok {
- for _, childID := range childrenIDs {
- if r, ok := idReplyMap[childID]; ok {
- p.Replies = append(p.Replies, r)
- }
- }
- }
- res[pid] = p
- }
- }
- return res
- }
- // ParentChildrenReplyIDRelation ParentChildrenReplyIDRelation
- func (s *Service) ParentChildrenReplyIDRelation(ctx context.Context, sub *model.Subject, parentIDs []int64) (map[int64][]int64, error) {
- idReplyMap, err := s.GetReplyByIDs(ctx, sub.Oid, sub.Type, parentIDs)
- if err != nil {
- return nil, err
- }
- var parentWithChildren, parentWithoutChildren []int64
- for id, reply := range idReplyMap {
- if reply.RCount > 0 {
- parentWithChildren = append(parentWithChildren, id)
- } else {
- parentWithoutChildren = append(parentWithoutChildren, id)
- }
- }
- parentChildrenIDRelation, err := s.parentChildrenReplyIDRelation(ctx, sub.Oid, sub.Type, parentWithChildren)
- if err != nil {
- return nil, err
- }
- for _, pid := range parentWithoutChildren {
- parentChildrenIDRelation[pid] = []int64{}
- }
- return parentChildrenIDRelation, nil
- }
- func (s *Service) parentChildrenReplyIDRelation(ctx context.Context, oid int64,
- tp int8, parentIDs []int64) (map[int64][]int64, error) {
- parentChildrenIDRelation, missedIDs, err := s.dao.Redis.ParentChildrenReplyIDMap(ctx, parentIDs, 0, 4)
- if err != nil {
- return nil, err
- }
- if len(missedIDs) > 0 {
- for _, rootID := range missedIDs {
- childrenIDs, err := s.dao.Reply.ChildrenIDsOfRootReply(ctx, oid, rootID, tp, 0, defaultChildrenSize)
- if err != nil {
- return nil, err
- }
- parentChildrenIDRelation[rootID] = childrenIDs
- s.dao.Databus.RecoverIndexByRoot(ctx, oid, rootID, tp)
- }
- }
- return parentChildrenIDRelation, nil
- }
- // GetAccountInfoMap fn
- func (s *Service) GetAccountInfoMap(ctx context.Context, mids []int64, ip string) (map[int64]*accmdl.Card, error) {
- if len(mids) == 0 {
- return _emptyCards, nil
- }
- args := &accmdl.MidsReq{Mids: mids}
- res, err := s.acc.Cards3(ctx, args)
- if err != nil {
- log.Error("s.acc.Infos2(%v), error(%v)", args, err)
- return nil, err
- }
- return res.Cards, nil
- }
- // GetFansMap fn
- func (s *Service) GetFansMap(ctx context.Context, uids []int64, mid int64, ip string) (map[int64]*model.FansDetail, error) {
- fans, err := s.fans.Fetch(ctx, uids, mid, time.Now())
- if err != nil {
- return nil, err
- }
- return fans, nil
- }
- // GetAssistMap fn
- func (s *Service) GetAssistMap(ctx context.Context, mid int64, ip string) (assistMap map[int64]int, err error) {
- arg := &assmdl.ArgAssists{
- Mid: mid,
- RealIP: ip,
- }
- assistMap = make(map[int64]int)
- ids, err := s.assist.AssistIDs(ctx, arg)
- if err != nil {
- log.Error("s.assist.AssistIDs(%v), error(%v)", arg, err)
- return
- }
- for _, id := range ids {
- assistMap[id] = 1
- }
- return
- }
- // GetRelationMap GetRelationMap
- func (s *Service) GetRelationMap(ctx context.Context, mid int64, targetMids []int64, ip string) (map[int64]*accmdl.RelationReply, error) {
- if len(targetMids) == 0 {
- return _emptyRelations, nil
- }
- relations, err := s.acc.Relations3(ctx, &accmdl.RelationsReq{Mid: mid, Owners: targetMids, RealIp: ip})
- if err != nil {
- log.Error("s.acc.Relations2(%v, %v) error(%v)", mid, targetMids, err)
- return nil, err
- }
- return relations.Relations, nil
- }
- // GetBlacklistMap GetBlacklistMap
- func (s *Service) GetBlacklistMap(ctx context.Context,
- mid int64, ip string) (map[int64]bool, error) {
- if mid == 0 {
- return _emptyBlackList, nil
- }
- args := &accmdl.MidReq{Mid: mid}
- blacklistMap, err := s.acc.Blacks3(ctx, args)
- if err != nil {
- log.Error("s.acc.Blacks(%v) error(%v)", args, err)
- return nil, err
- }
- return blacklistMap.BlackList, nil
- }
- // GetPendingReply GetPendingReply
- func (s *Service) GetPendingReply(ctx context.Context, mid int64, oid int64, typ int8) (map[int64]*model.Reply, error) {
- // WARNING: here we assume that pending replies have no children
- // otherwise, we need to change logic here
- pendingIDs, err := s.dao.Redis.UserAuditReplies(ctx, mid, oid, typ)
- if err != nil {
- return nil, err
- }
- pendingIDReplyMap, err := s.GetReplyByIDs(ctx, oid, typ, pendingIDs)
- if err != nil {
- return nil, err
- }
- return pendingIDReplyMap, nil
- }
- // GetSubReplyListByCursor GetSubReplyListByCursor
- func (s *Service) GetSubReplyListByCursor(ctx context.Context, params *model.CursorParams) (*model.RootReplyList, error) {
- var (
- hasFolded bool
- )
- sub, err := s.Subject(ctx, params.Oid, params.OTyp)
- if err != nil {
- return nil, err
- }
- rp, err := s.ReplyContent(ctx, params.Oid, params.RootID, params.OTyp)
- if err != nil {
- return nil, err
- }
- if rp.IsRoot() && rp.HasFolded() {
- hasFolded = true
- }
- if rp.Root != 0 {
- params.RootID = rp.Root
- root, _ := s.reply(ctx, 0, params.Oid, rp.Root, params.OTyp)
- if root != nil && rp.IsRoot() && rp.HasFolded() {
- hasFolded = true
- }
- }
- childrenIDs, err := s.GetChildrenIDsByCursor(ctx, sub, params.RootID, params.Sort, params.Cursor)
- if err != nil {
- return nil, err
- }
- // 这里是处理被折叠的评论的逻辑
- if params.ShowFolded && hasFolded {
- foldedRpIDs, _ := s.foldedRepliesCursor(ctx, sub, params.RootID, params.Cursor)
- if len(foldedRpIDs) > 0 {
- childrenIDs = append(childrenIDs, foldedRpIDs...)
- sort.Slice(childrenIDs, func(x, y int) bool { return childrenIDs[x] < childrenIDs[y] })
- length := len(childrenIDs)
- if length > params.Cursor.Len() {
- if params.Cursor.Descrease() {
- // 往楼层小的地方翻页, 对于子评论就是往上翻页,这个时候要从后往前截断
- childrenIDs = childrenIDs[length-params.Cursor.Len():]
- } else {
- childrenIDs = childrenIDs[:params.Cursor.Len()]
- }
- }
- }
- }
- parentChildrenIDRelation := map[int64][]int64{params.RootID: childrenIDs}
- idReplyMap, err := s.IDReplyMap(ctx, sub, parentChildrenIDRelation)
- if err != nil {
- return nil, err
- }
- if NeedInsertPendingReply(params, sub) {
- var pendingIDReplyMap map[int64]*model.Reply
- pendingIDReplyMap, err = s.GetPendingReply(ctx, params.Mid, sub.Oid, sub.Type)
- if err != nil {
- return nil, err
- }
- for id, r := range pendingIDReplyMap {
- if _, ok := idReplyMap[r.Root]; ok {
- parentChildrenIDRelation[r.Root] = InsertInto(parentChildrenIDRelation[r.Root], id, defaultChildrenSize, model.OrderASC)
- sub.ACount++
- idReplyMap[id] = r
- }
- }
- }
- rootReply := assemble(idReplyMap, parentChildrenIDRelation)[params.RootID]
- if rootReply == nil || rootReply.IsDeleted() {
- return nil, ecode.ReplyNotExist
- }
- max, min, err := cursorRange(rootReply.Replies, params.Sort)
- if err != nil {
- return nil, err
- }
- s.FillRootReplies(ctx, []*model.Reply{rootReply}, params.Mid, params.IP, params.HTMLEscape, sub)
- return &model.RootReplyList{
- Subject: sub,
- Roots: []*model.Reply{rootReply},
- CursorRangeMax: max,
- CursorRangeMin: min,
- }, nil
- }
- func cursorRange(rs []*model.Reply, sort int8) (max, min int64, err error) {
- if len(rs) > 0 {
- switch sort {
- case model.SortByFloor:
- // NOTE("这里是为了12月13号给bishi搞零时置顶子评论做的ios兼容逻辑")
- var head int64
- if rs[0].RpID != 1237270231 {
- head = int64(rs[0].Floor)
- } else {
- if len(rs) > 1 {
- head = int64(rs[1].Floor)
- } else {
- head = int64(1)
- }
- }
- tail := int64(rs[len(rs)-1].Floor)
- if model.OrderDESC(head, tail) {
- max, min = head, tail
- } else {
- max, min = tail, head
- }
- return
- default:
- err = errors.New("unsupported cursor type")
- log.Error("%v", err)
- return 0, 0, err
- }
- }
- return
- }
- // GetRootReplyListByCursor GetRootReplyListByCursor
- func (s *Service) GetRootReplyListByCursor(ctx context.Context, params *model.CursorParams) (*model.RootReplyList, error) {
- params.HotSize = s.hotNum(params.Oid, params.OTyp)
- sub, err := s.Subject(ctx, params.Oid, params.OTyp)
- if err != nil {
- return nil, err
- }
- roots, err := s.RootReplyListByCursor(ctx, sub, params)
- if err != nil {
- return nil, err
- }
- max, min, err := cursorRange(roots, params.Sort)
- if err != nil {
- return nil, err
- }
- // WARN: rootIDs AND hotIDs may be overlapped
- var allRootReply []*model.Reply
- allRootReply = append(allRootReply, roots...)
- var header *model.RootReplyListHeader
- if needHeader(params.Cursor, len(roots)) {
- header, err = s.GetRootReplyListHeader(ctx, sub, params)
- if err != nil {
- return nil, err
- }
- allRootReply = append(allRootReply, header.Hots...)
- if header.TopAdmin != nil {
- allRootReply = append(allRootReply, header.TopAdmin)
- }
- if header.TopUpper != nil {
- allRootReply = append(allRootReply, header.TopUpper)
- }
- }
- s.FillRootReplies(ctx, allRootReply, params.Mid, params.IP, params.HTMLEscape, sub)
- return &model.RootReplyList{
- Subject: sub,
- Roots: roots,
- Header: header,
- CursorRangeMax: max,
- CursorRangeMin: min,
- }, nil
- }
- // DialogMaxMinFloor return max and min floor in dialog
- func (s *Service) DialogMaxMinFloor(c context.Context, oid int64, tp int8, root, dialog int64) (maxFloor, minFloor int, err error) {
- var (
- ok bool
- )
- if ok, err = s.dao.Redis.ExpireDialogIndex(c, dialog); err != nil {
- log.Error("s.dao.Redis.ExpireDialogIndex error (%v)", err)
- return
- }
- if ok {
- minFloor, maxFloor, err = s.dao.Redis.DialogMinMaxFloor(c, dialog)
- } else {
- minFloor, maxFloor, err = s.dao.Reply.GetDialogMinMaxFloor(c, oid, tp, root, dialog)
- }
- return
- }
- // DialogByCursor ...
- func (s *Service) DialogByCursor(c context.Context, mid, oid int64, tp int8, root, dialog int64, cursor *model.Cursor) (rps []*model.Reply, dialogCursor *model.DialogCursor, dialogMeta *model.DialogMeta, err error) {
- var (
- ok bool
- rpIDs []int64
- rpMap map[int64]*model.Reply
- )
- dialogCursor = new(model.DialogCursor)
- dialogMeta = new(model.DialogMeta)
- dialogMeta.MaxFloor, dialogMeta.MinFloor, err = s.DialogMaxMinFloor(c, oid, tp, root, dialog)
- if err != nil {
- log.Error("get max and min floor for dialog from redis or db error", err)
- return
- }
- if (cursor.Max() != 0 && cursor.Max() > int64(dialogMeta.MaxFloor)) || (cursor.Min() != 0 && cursor.Min() < int64(dialogMeta.MinFloor)) {
- log.Warn("cursor max %d min %d, dialogmeta max %d min %d", cursor.Max(), cursor.Min(), dialogMeta.MinFloor, dialogMeta.MinFloor)
- err = ecode.RequestErr
- return
- }
- if ok, err = s.dao.Redis.ExpireDialogIndex(c, dialog); err != nil {
- log.Error("s.dao.Redis.ExpireDialogIndex error (%v)", err)
- return
- }
- if ok {
- rpIDs, err = s.dao.Redis.DialogByCursor(c, dialog, cursor)
- } else {
- s.dao.Databus.RecoverDialogIdx(c, oid, tp, root, dialog)
- if cursor.Latest() {
- rpIDs, err = s.dao.Reply.GetIDsByDialogAsc(c, oid, tp, root, dialog, int64(dialogMeta.MinFloor), cursor.Len())
- } else if cursor.Descrease() {
- rpIDs, err = s.dao.Reply.GetIDsByDialogDesc(c, oid, tp, root, dialog, cursor.Current(), cursor.Len())
- } else if cursor.Increase() {
- rpIDs, err = s.dao.Reply.GetIDsByDialogAsc(c, oid, tp, root, dialog, cursor.Current(), cursor.Len())
- } else {
- err = ecode.RequestErr
- }
- }
- if err != nil {
- log.Error("dialog by cursor from redis or db error (%v)", err)
- return
- }
- rpMap, err = s.repliesMap(c, oid, tp, rpIDs)
- if err != nil {
- return
- }
- for _, rpid := range rpIDs {
- if r, ok := rpMap[rpid]; ok {
- rps = append(rps, r)
- }
- }
- if !sort.SliceIsSorted(rps, func(i, j int) bool { return rps[i].Floor < rps[j].Floor }) {
- sort.Slice(rps, func(i, j int) bool { return rps[i].Floor < rps[j].Floor })
- }
- sub, err := s.Subject(c, oid, tp)
- if err != nil {
- log.Error("s.dao.Subject.Get(%d, %d) error(%v)", oid, tp, err)
- return
- }
- if err = s.buildReply(c, sub, rps, mid, false); err != nil {
- return
- }
- dialogCursor.Size = len(rps)
- if dialogCursor.Size == 0 {
- return
- }
- dialogCursor.MinFloor = rps[0].Floor
- dialogCursor.MaxFloor = rps[dialogCursor.Size-1].Floor
- return
- }
- // ...
- func (s *Service) foldedRepliesCursor(c context.Context, sub *model.Subject, root int64, cursor *model.Cursor) (foldedRpIDs []int64, err error) {
- var (
- xcursor = new(xmodel.Cursor)
- max = int(cursor.Max())
- min = int(cursor.Min())
- )
- xcursor.Ps = cursor.Len()
- // 针对子评论的情况
- if cursor.Increase() {
- xcursor.Prev = min
- } else if cursor.Descrease() {
- xcursor.Next = max
- }
- return s.foldedReplies(c, sub, root, xcursor)
- }
|