package service import ( "context" "sort" "go-common/app/interface/main/reply/model/reply" xmodel "go-common/app/interface/main/reply/model/xreply" "go-common/library/sync/errgroup.v2" ) const ( _secondRepliesPn = 1 _secondRepliesPs = 3 _hotSize = 5 _hotSizeWeb = 3 ) func fillHot(rs []*reply.Reply, sub *reply.Subject, maxSize int) (hots []*reply.Reply) { for _, r := range rs { if r.Like >= 3 && !r.IsTop() && !r.IsDeleted() { hots = append(hots, r) } } if hots == nil { hots = _emptyReplies } else if len(hots) > maxSize { hots = hots[:maxSize] } if sub.RCount <= 20 && len(hots) > 0 { hots = _emptyReplies } return hots } // buildResCursor ... func buildResCursor(reqCursor *xmodel.Cursor, replies []*reply.Reply, cursorMode int) (resCursor xmodel.CursorRes) { length := len(replies) switch cursorMode { case xmodel.CursorModePage: // next和prev只可能一个不为0 curPage := reqCursor.Next + reqCursor.Prev if reqCursor.Latest() { resCursor.IsBegin = true if length == 0 { resCursor.Next = curPage resCursor.Prev = curPage resCursor.IsEnd = true } else { // 下一页是第二页 resCursor.Next = 2 resCursor.Prev = 1 if length < reqCursor.Ps { resCursor.IsEnd = true } } } else if reqCursor.Forward() { resCursor.Next = curPage + 1 resCursor.Prev = curPage if length < reqCursor.Ps { resCursor.IsEnd = true } } else if reqCursor.Backward() { resCursor.Next = curPage resCursor.Prev = curPage - 1 if length < reqCursor.Ps { resCursor.IsBegin = true } } case xmodel.CursorModeCursor: if reqCursor.Latest() { resCursor.IsBegin = true // Latest, 点进来默认访问的情况 if length == 0 { resCursor.Next = reqCursor.Next resCursor.Prev = reqCursor.Prev resCursor.IsEnd = true } else { resCursor.Next = replies[length-1].Floor resCursor.Prev = replies[0].Floor if length < reqCursor.Ps { resCursor.IsEnd = true } } } else if reqCursor.Forward() { if length == 0 { resCursor.Next = reqCursor.Next resCursor.Prev = reqCursor.Prev resCursor.IsEnd = true } else { resCursor.Next = replies[length-1].Floor resCursor.Prev = replies[0].Floor if length < reqCursor.Ps { resCursor.IsEnd = true } } } else if reqCursor.Backward() { if length == 0 { resCursor.Next = reqCursor.Next resCursor.Prev = reqCursor.Prev resCursor.IsBegin = true } else { resCursor.Next = replies[length-1].Floor resCursor.Prev = replies[0].Floor if length < reqCursor.Ps { resCursor.IsBegin = true } } } } return } // replyMisc ... func (s *Service) replyCommonRes(ctx context.Context, mid, oid int64, tp int8, sub *reply.Subject) (res xmodel.CommonRes) { if sub == nil { return } g := errgroup.WithContext(ctx) g.Go(func(ctx context.Context) error { if s.RelationBlocked(ctx, sub.Mid, mid) { res.Blacklist = 1 } return nil }) g.Go(func(ctx context.Context) error { if ok, _ := s.CheckAssist(ctx, sub.Mid, mid); ok { res.Assist = 1 } return nil }) // 默认都是展示 res.Config.ShowAdmin, res.Config.ShowEntry, res.Config.ShowFloor = 1, 1, 1 g.Go(func(ctx context.Context) error { if cfg, _ := s.GetReplyLogConfig(ctx, sub, 1); cfg != nil { res.Config.ShowAdmin, res.Config.ShowEntry = cfg.ShowAdmin, cfg.ShowEntry } // 特殊稿件不显示楼层 if !s.ShowFloor(oid, tp) { res.Config.ShowFloor = 0 } return nil }) g.Wait() res.Upper.Mid = sub.Mid return } // fillXreplyRes ... func (s *Service) fillXreplyRes(ctx context.Context, sub *reply.Subject, req *xmodel.ReplyReq, res *xmodel.ReplyRes) { res.CommonRes = s.replyCommonRes(ctx, req.Mid, req.Oid, req.Type, sub) res.Notice = s.RplyNotice(ctx, req.Plat, req.Build, req.MobiApp, req.Buvid) // 过滤掉可能的脏数据 res.Replies = s.FilDelReply(res.Replies) res.Hots = s.FilDelReply(res.Hots) // 过滤掉赞数小于3的 res.Hots = fillHot(res.Hots, sub, s.hotNum(req.Oid, req.Type)) res.Cursor.AllCount = sub.ACount res.Folder = sub.Folder() } // Xreply ... func (s *Service) Xreply(ctx context.Context, req *xmodel.ReplyReq) (res *xmodel.ReplyRes, err error) { var ( sub *reply.Subject cursor *reply.Cursor cursorRes *reply.RootReplyList pageRes *reply.PageResult ) res = new(xmodel.ReplyRes) mode, supportMode := req.ModeInfo(s.sortByHot, s.sortByTime) switch mode { case xmodel.ModeOrigin, xmodel.ModeTime: // 这里原来用的闭区间,所以这里有坑 if req.Cursor.Forward() { if req.Cursor.Next > 1 { req.Cursor.Next-- } } else if req.Cursor.Backward() { req.Cursor.Prev++ } if cursor, err = reply.NewCursor(int64(req.Cursor.Next), int64(req.Cursor.Prev), req.Cursor.Ps, reply.OrderDESC); err != nil { return } if req.Cursor.Backward() { if cursor, err = reply.NewCursor(int64(req.Cursor.Next), int64(req.Cursor.Prev), req.Cursor.Ps, reply.OrderASC); err != nil { return } } cursorParams := &reply.CursorParams{ IP: req.IP, Oid: req.Oid, OTyp: req.Type, Sort: reply.SortByFloor, HTMLEscape: false, Cursor: cursor, HotSize: s.hotNum(req.Oid, req.Type), Mid: req.Mid, } if cursorRes, err = s.GetRootReplyListByCursor(ctx, cursorParams); err != nil { return } sub = cursorRes.Subject res.Replies = cursorRes.Roots if cursorRes.Header != nil { res.Top.Admin = cursorRes.Header.TopAdmin res.Top.Upper = cursorRes.Header.TopUpper res.Hots = cursorRes.Header.Hots // 纯按楼层排序 去掉热评 if mode == xmodel.ModeTime { res.Hots = _emptyReplies } } if !sort.SliceIsSorted(res.Replies, func(i, j int) bool { return res.Replies[i].Floor > res.Replies[j].Floor }) { sort.Slice(res.Replies, func(i, j int) bool { return res.Replies[i].Floor > res.Replies[j].Floor }) } // 这里原来用的闭区间,所以这里有坑 if req.Cursor.Next == 1 { res.Replies = _emptyReplies } // 由服务端来控制翻页逻辑 res.Cursor = buildResCursor(&xmodel.Cursor{Prev: req.Cursor.Prev, Next: req.Cursor.Next, Ps: req.Cursor.Ps}, res.Replies, xmodel.CursorModeCursor) case xmodel.ModeHot: var curPage = req.Cursor.Prev + req.Cursor.Next if req.Cursor.Latest() { curPage = 1 } pageParams := &reply.PageParams{ Mid: req.Mid, Oid: req.Oid, Type: req.Type, Sort: reply.SortByLike, PageNum: curPage, PageSize: req.Cursor.Ps, NeedSecond: true, Escape: false, NeedHot: false, } if pageRes, err = s.RootReplies(ctx, pageParams); err != nil { return } sub = pageRes.Subject res.Replies = pageRes.Roots res.Top.Admin = pageRes.TopAdmin res.Top.Upper = pageRes.TopUpper // 按页码翻页控制返回页码 res.Cursor = buildResCursor(&xmodel.Cursor{Prev: req.Cursor.Prev, Next: req.Cursor.Next, Ps: req.Cursor.Ps}, res.Replies, xmodel.CursorModePage) } res.Cursor.Mode = mode res.Cursor.SupportMode = supportMode s.fillXreplyRes(ctx, sub, req, res) return } // SubFoldedReply ... func (s *Service) SubFoldedReply(ctx context.Context, req *xmodel.SubFolderReq) (res *xmodel.SubFolderRes, err error) { var ( rpIDs []int64 rootMap map[int64]*reply.Reply childrenMap map[int64][]*reply.Reply rootRps []*reply.Reply childrenRps []*reply.Reply sub *reply.Subject ) res = new(xmodel.SubFolderRes) if req.Cursor.Backward() { return } cursor := &xmodel.Cursor{ Ps: req.Cursor.Ps, Next: req.Cursor.Next, } if sub, err = s.Subject(ctx, req.Oid, req.Type); err != nil { return } if rpIDs, err = s.foldedReplies(ctx, sub, 0, cursor); err != nil { return } if rootMap, err = s.repliesMap(ctx, req.Oid, req.Type, rpIDs); err != nil { return } if childrenMap, childrenRps, err = s.secondReplies(ctx, sub, rootMap, req.Mid, _secondRepliesPn, _secondRepliesPs); err != nil { return } for _, rpID := range rpIDs { if r, ok := rootMap[rpID]; ok { if children, hasChild := childrenMap[rpID]; hasChild { r.Replies = children childrenRps = append(childrenRps, children...) } else { r.Replies = _emptyReplies } rootRps = append(rootRps, r) } } if rootRps != nil { res.Replies = rootRps } else { res.Replies = _emptyReplies } var rps []*reply.Reply rps = append(rps, rootRps...) rps = append(rps, childrenRps...) if err = s.buildReply(ctx, sub, rps, req.Mid, false); err != nil { return } res.Cursor = buildResCursor(&xmodel.Cursor{Prev: req.Cursor.Prev, Next: req.Cursor.Next, Ps: req.Cursor.Ps}, res.Replies, xmodel.CursorModeCursor) res.CommonRes = s.replyCommonRes(ctx, req.Mid, req.Oid, req.Type, sub) return } // RootFoldedReply ... func (s *Service) RootFoldedReply(ctx context.Context, req *xmodel.RootFolderReq) (res *xmodel.RootFolderRes, err error) { var ( rpIDs []int64 childrenMap map[int64]*reply.Reply childrenRps []*reply.Reply sub *reply.Subject ) res = new(xmodel.RootFolderRes) if req.Cursor.Backward() { return } cursor := &xmodel.Cursor{ Ps: req.Cursor.Ps, Next: req.Cursor.Next, } if sub, err = s.Subject(ctx, req.Oid, req.Type); err != nil { return } if rpIDs, err = s.foldedReplies(ctx, sub, req.Root, cursor); err != nil { return } if childrenMap, err = s.repliesMap(ctx, req.Oid, req.Type, rpIDs); err != nil { return } for _, rpID := range rpIDs { if r, ok := childrenMap[rpID]; ok { childrenRps = append(childrenRps, r) } } if childrenRps != nil { res.Replies = childrenRps } else { res.Replies = _emptyReplies } if err = s.buildReply(ctx, sub, res.Replies, req.Mid, false); err != nil { return } // 只有往下翻 res.Cursor = buildResCursor(&xmodel.Cursor{Prev: req.Cursor.Prev, Next: req.Cursor.Next, Ps: req.Cursor.Ps}, res.Replies, xmodel.CursorModeCursor) res.CommonRes = s.replyCommonRes(ctx, req.Mid, req.Oid, req.Type, sub) return } // foldedReplies ... func (s *Service) foldedReplies(ctx context.Context, sub *reply.Subject, root int64, cursor *xmodel.Cursor) (rpIDs []int64, err error) { var ( kind string ID int64 ok bool ) if root == 0 { kind = xmodel.FolderKindSub ID = sub.Oid } else { kind = xmodel.FolderKindRoot ID = root } if ok, err = s.dao.Redis.ExpireFolder(ctx, kind, ID); err != nil { return } if ok { if rpIDs, err = s.dao.Redis.FolderByCursor(ctx, kind, ID, cursor); err != nil { return } } else { if rpIDs, err = s.dao.Reply.FoldedRepliesCursor(ctx, sub.Oid, sub.Type, root, cursor); err != nil { return } s.cache.Do(ctx, func(ctx context.Context) { s.dao.Databus.RecoverFolderIdx(ctx, sub.Oid, sub.Type, root) }) } return }