389 lines
10 KiB
Go
389 lines
10 KiB
Go
|
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
|
||
|
}
|