You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
909 lines
25 KiB
909 lines
25 KiB
package service |
|
|
|
import ( |
|
"context" |
|
"encoding/json" |
|
"sort" |
|
"strconv" |
|
|
|
model "go-common/app/interface/main/reply/model/reply" |
|
accmdl "go-common/app/service/main/account/api" |
|
"go-common/library/ecode" |
|
"go-common/library/log" |
|
"go-common/library/net/metadata" |
|
|
|
"go-common/library/sync/errgroup.v2" |
|
) |
|
|
|
const ( |
|
// hot count web |
|
_webHotCnt = 3 |
|
// 热评列表中至少点赞数 |
|
_hotLikes = 3 |
|
// 取5条,因为有可能管理员置顶和up置顶同时存在 |
|
_hotFilters = 5 |
|
) |
|
|
|
func withinFloor(rpIDs []int64, rpID int64, pn, ps int, asc bool) bool { |
|
if len(rpIDs) == 0 || (pn == 1 && len(rpIDs) < ps) { |
|
return true |
|
} |
|
first := rpIDs[0] |
|
last := rpIDs[len(rpIDs)-1] |
|
if asc { |
|
if (first < rpID && last > rpID) || (len(rpIDs) < ps && last < rpID) { |
|
return true |
|
} |
|
} else { |
|
if (pn == 1 && first < rpID) || (first > rpID && last < rpID) || (len(rpIDs) < ps && last > rpID) { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
// SecondReplies return second replies. |
|
func (s *Service) SecondReplies(c context.Context, mid, oid, rootID, jumpID int64, tp int8, pn, ps int, escape bool) (seconds []*model.Reply, root *model.Reply, upMid int64, toPn int, err error) { |
|
var ( |
|
ok bool |
|
sub *model.Subject |
|
jump *model.Reply |
|
) |
|
if !model.LegalSubjectType(tp) { |
|
err = ecode.ReplyIllegalSubType |
|
return |
|
} |
|
if sub, err = s.Subject(c, oid, tp); err != nil { |
|
return |
|
} |
|
// jump to the child reply page list |
|
if jumpID > 0 { |
|
if jump, err = s.ReplyContent(c, oid, jumpID, tp); err != nil { |
|
return |
|
} |
|
if jump.Root == 0 { |
|
root = jump |
|
pn = 1 |
|
} else { |
|
if root, err = s.ReplyContent(c, oid, jump.Root, tp); err != nil { |
|
return |
|
} |
|
if pos := s.getReplyPosByRoot(c, root, jump); pos > ps { |
|
pn = (pos-1)/ps + 1 |
|
} else { |
|
pn = 1 |
|
} |
|
} |
|
} else { |
|
if root, err = s.ReplyContent(c, oid, rootID, tp); err != nil { |
|
return |
|
} |
|
if root.Root != 0 { |
|
if root, err = s.ReplyContent(c, oid, root.Root, tp); err != nil { |
|
return |
|
} |
|
} |
|
} |
|
if root.IsDeleted() { |
|
err = ecode.ReplyDeleted |
|
return |
|
} |
|
upMid = sub.Mid |
|
toPn = pn |
|
// get reply second reply content |
|
rootMap := make(map[int64]*model.Reply, 1) |
|
rootMap[root.RpID] = root |
|
secondMap, _, _ := s.secondReplies(c, sub, rootMap, mid, pn, ps) |
|
if seconds, ok = secondMap[root.RpID]; !ok { |
|
seconds = _emptyReplies |
|
} |
|
// get reply dependency info |
|
rs := make([]*model.Reply, 0, len(seconds)+1) |
|
rs = append(rs, root) |
|
rs = append(rs, seconds...) |
|
if err = s.buildReply(c, sub, rs, mid, escape); err != nil { |
|
return |
|
} |
|
return |
|
} |
|
|
|
// JumpReplies jump to page by reply id. |
|
func (s *Service) JumpReplies(c context.Context, mid, oid, rpID int64, tp int8, ps, sndPs int, escape bool) (roots, hots []*model.Reply, topAdmin, topUpper *model.Reply, sub *model.Subject, pn, sndPn, total int, err error) { |
|
var ( |
|
rootPos, sndPos int |
|
rootRp, rp *model.Reply |
|
fixedSeconds []*model.Reply |
|
) |
|
if !model.LegalSubjectType(tp) { |
|
err = ecode.ReplyIllegalSubType |
|
return |
|
} |
|
if sub, err = s.Subject(c, oid, tp); err != nil { |
|
return |
|
} |
|
if rp, err = s.ReplyContent(c, oid, rpID, tp); err != nil { |
|
return |
|
} |
|
if rp.Root == 0 && rp.Parent == 0 { |
|
rootPos = s.getReplyPos(c, sub, rp) |
|
} else { |
|
if rootRp, err = s.ReplyContent(c, oid, rp.Root, tp); err != nil { |
|
return |
|
} |
|
rootPos = s.getReplyPos(c, sub, rootRp) |
|
sndPos = s.getReplyPosByRoot(c, rootRp, rp) |
|
} |
|
// root page number |
|
pn = (rootPos-1)/ps + 1 |
|
// second page number |
|
if sndPos > sndPs { |
|
sndPn = (sndPos-1)/sndPs + 1 |
|
} else { |
|
sndPn = 1 |
|
} |
|
// get reply content |
|
roots, seconds, total, err := s.rootReplies(c, sub, mid, model.SortByFloor, pn, ps, 1, sndPs) |
|
if err != nil { |
|
return |
|
} |
|
if rootRp != nil && rootRp.RCount > 0 { |
|
if fixedSeconds, err = s.repliesByRoot(c, oid, rootRp.RpID, tp, sndPn, sndPs); err != nil { |
|
return |
|
} |
|
for _, rp := range roots { |
|
if rp.RpID == rootRp.RpID { |
|
rp.Replies = fixedSeconds |
|
} |
|
} |
|
} |
|
// top and hots |
|
topAdmin, topUpper, hots, hseconds, err := s.topAndHots(c, sub, mid, true, true) |
|
if err != nil { |
|
log.Error("s.topAndHots(%d,%d,%d) error(%v)", oid, tp, mid, err) |
|
err = nil // degrade |
|
} |
|
rs := make([]*model.Reply, 0, len(roots)+len(seconds)+len(hseconds)+len(fixedSeconds)+2) |
|
rs = append(rs, roots...) |
|
rs = append(rs, seconds...) |
|
rs = append(rs, hseconds...) |
|
rs = append(rs, hots...) |
|
rs = append(rs, fixedSeconds...) |
|
if topAdmin != nil { |
|
rs = append(rs, topAdmin) |
|
} |
|
if topUpper != nil { |
|
rs = append(rs, topUpper) |
|
} |
|
if err = s.buildReply(c, sub, rs, mid, escape); err != nil { |
|
return |
|
} |
|
return |
|
} |
|
|
|
// RootReplies return a page list of reply. |
|
func (s *Service) RootReplies(c context.Context, params *model.PageParams) (page *model.PageResult, err error) { |
|
if !model.LegalSubjectType(params.Type) { |
|
err = ecode.ReplyIllegalSubType |
|
return |
|
} |
|
sub, err := s.Subject(c, params.Oid, params.Type) |
|
if err != nil { |
|
return |
|
} |
|
topAdmin, topUpper, hots, hseconds, err := s.topAndHots(c, sub, params.Mid, params.NeedHot, params.NeedSecond) |
|
if err != nil { |
|
log.Error("s.topAndHots(%+v) error(%v)", params, err) |
|
err = nil // degrade |
|
} |
|
roots, seconds, total, err := s.rootReplies(c, sub, params.Mid, params.Sort, params.PageNum, params.PageSize, 1, s.sndDefCnt) |
|
if err != nil { |
|
return |
|
} |
|
rs := make([]*model.Reply, 0, len(roots)+len(hots)+len(hseconds)+len(seconds)+2) |
|
rs = append(rs, hots...) |
|
rs = append(rs, roots...) |
|
rs = append(rs, hseconds...) |
|
rs = append(rs, seconds...) |
|
if topAdmin != nil { |
|
rs = append(rs, topAdmin) |
|
} |
|
if topUpper != nil { |
|
rs = append(rs, topUpper) |
|
} |
|
if err = s.buildReply(c, sub, rs, params.Mid, params.Escape); err != nil { |
|
return |
|
} |
|
page = &model.PageResult{ |
|
Subject: sub, |
|
TopAdmin: topAdmin, |
|
TopUpper: topUpper, |
|
Hots: hots, |
|
Roots: roots, |
|
Total: total, |
|
AllCount: sub.ACount, |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) topAndHots(c context.Context, sub *model.Subject, mid int64, needNot, needSnd bool) (topAdmin, topUpper *model.Reply, hots, seconds []*model.Reply, err error) { |
|
var ( |
|
ok bool |
|
hotIDs []int64 |
|
rootMap map[int64]*model.Reply |
|
secondMap map[int64][]*model.Reply |
|
) |
|
// get hot replies |
|
if needNot { |
|
if hotIDs, _, err = s.rootReplyIDs(c, sub, model.SortByLike, 1, s.hotNumWeb(sub.Oid, sub.Type)+2, false); err != nil { |
|
return |
|
} |
|
if rootMap, err = s.repliesMap(c, sub.Oid, sub.Type, hotIDs); err != nil { |
|
return |
|
} |
|
} |
|
// get top replies |
|
if topAdmin, err = s.topReply(c, sub, model.SubAttrAdminTop); err != nil { |
|
return |
|
} |
|
if topUpper, err = s.topReply(c, sub, model.SubAttrUpperTop); err != nil { |
|
return |
|
} |
|
if rootMap == nil { |
|
rootMap = make(map[int64]*model.Reply) |
|
} |
|
if topAdmin != nil { |
|
rootMap[topAdmin.RpID] = topAdmin |
|
} |
|
if topUpper != nil { |
|
if !topUpper.IsNormal() && sub.Mid != mid { |
|
topUpper = nil |
|
} else { |
|
rootMap[topUpper.RpID] = topUpper |
|
} |
|
} |
|
// get second replies |
|
if needSnd { |
|
if secondMap, seconds, err = s.secondReplies(c, sub, rootMap, mid, 1, s.sndDefCnt); err != nil { |
|
return |
|
} |
|
if topAdmin != nil { |
|
if topAdmin.Replies, ok = secondMap[topAdmin.RpID]; !ok { |
|
topAdmin.Replies = _emptyReplies |
|
} |
|
} |
|
if topUpper != nil { |
|
if topUpper.Replies, ok = secondMap[topUpper.RpID]; !ok { |
|
topUpper.Replies = _emptyReplies |
|
} |
|
} |
|
} |
|
if len(hotIDs) == 0 { |
|
hots = _emptyReplies |
|
return |
|
} |
|
hotSize := s.hotNumWeb(sub.Oid, sub.Type) |
|
for _, rootID := range hotIDs { |
|
if hotSize != _hotSizeWeb && len(hots) >= _hotSizeWeb { |
|
break |
|
} else if len(hots) >= hotSize { |
|
break |
|
} |
|
if rp, ok := rootMap[rootID]; ok && rp.Like >= _hotLikes && !rp.IsTop() { |
|
if rp.Replies, ok = secondMap[rp.RpID]; !ok { |
|
rp.Replies = _emptyReplies |
|
} |
|
hots = append(hots, rp) |
|
} |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) rootReplies(c context.Context, sub *model.Subject, mid int64, msort int8, pn, ps, secondPn, secondPs int) (roots, seconds []*model.Reply, total int, err error) { |
|
var ( |
|
rootMap map[int64]*model.Reply |
|
) |
|
// get root replies |
|
rootIDs, total, err := s.rootReplyIDs(c, sub, msort, pn, ps, true) |
|
if err != nil { |
|
return |
|
} |
|
if len(rootIDs) > 0 { |
|
if rootMap, err = s.repliesMap(c, sub.Oid, sub.Type, rootIDs); err != nil { |
|
return |
|
} |
|
} |
|
// get pending audit replies |
|
if msort == model.SortByFloor && sub.AttrVal(model.SubAttrAudit) == model.AttrYes { |
|
var ( |
|
pendingTotal int |
|
pendingIDs []int64 |
|
rootPendingMap map[int64]*model.Reply |
|
) |
|
if rootPendingMap, _, pendingTotal, err = s.userAuditReplies(c, mid, sub.Oid, sub.Type); err != nil { |
|
err = nil // degrade |
|
} |
|
if rootMap == nil { |
|
rootMap = make(map[int64]*model.Reply) |
|
} |
|
for _, rp := range rootPendingMap { |
|
if withinFloor(rootIDs, rp.RpID, pn, ps, false) { |
|
rootMap[rp.RpID] = rp |
|
pendingIDs = append(pendingIDs, rp.RpID) |
|
} |
|
} |
|
if len(pendingIDs) > 0 { |
|
rootIDs = append(rootIDs, pendingIDs...) |
|
sort.Sort(model.DescFloors(rootIDs)) |
|
} |
|
sub.ACount += pendingTotal |
|
} |
|
if len(rootIDs) == 0 { |
|
roots = _emptyReplies |
|
return |
|
} |
|
// get second replies |
|
secondMap, seconds, err := s.secondReplies(c, sub, rootMap, mid, secondPn, secondPs) |
|
if err != nil { |
|
return |
|
} |
|
for _, rootID := range rootIDs { |
|
if rp, ok := rootMap[rootID]; ok { |
|
if rp.Replies, ok = secondMap[rp.RpID]; !ok { |
|
rp.Replies = _emptyReplies |
|
} |
|
if msort != model.SortByFloor { |
|
//if not sort by floor,can't contain the top comment |
|
if rp.IsTop() { |
|
continue |
|
} |
|
} |
|
roots = append(roots, rp) |
|
} |
|
} |
|
if roots == nil { |
|
roots = _emptyReplies |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) secondReplies(c context.Context, sub *model.Subject, rootMap map[int64]*model.Reply, mid int64, pn, ps int) (res map[int64][]*model.Reply, rs []*model.Reply, err error) { |
|
var ( |
|
rootIDs, secondIDs []int64 |
|
secondIdxMap map[int64][]int64 |
|
secondMap map[int64]*model.Reply |
|
) |
|
for rootID, info := range rootMap { |
|
if info.RCount > 0 { |
|
rootIDs = append(rootIDs, rootID) |
|
} |
|
} |
|
if len(rootIDs) > 0 { |
|
if secondIdxMap, secondIDs, err = s.getIdsByRoots(c, sub.Oid, rootIDs, sub.Type, pn, ps); err != nil { |
|
return |
|
} |
|
if secondMap, err = s.repliesMap(c, sub.Oid, sub.Type, secondIDs); err != nil { |
|
return |
|
} |
|
} |
|
// get pending audit replies |
|
if sub.AttrVal(model.SubAttrAudit) == model.AttrYes { |
|
var secondPendings map[int64][]*model.Reply |
|
if _, secondPendings, _, err = s.userAuditReplies(c, mid, sub.Oid, sub.Type); err != nil { |
|
err = nil // degrade |
|
} |
|
if secondIdxMap == nil { |
|
secondIdxMap = make(map[int64][]int64) |
|
} |
|
if secondMap == nil { |
|
secondMap = make(map[int64]*model.Reply) |
|
} |
|
for rootID, rs := range secondPendings { |
|
var pendingIDs []int64 |
|
if r, ok := rootMap[rootID]; ok { |
|
for _, r := range rs { |
|
if withinFloor(secondIdxMap[rootID], r.RpID, pn, ps, true) { |
|
secondMap[r.RpID] = r |
|
pendingIDs = append(pendingIDs, r.RpID) |
|
} |
|
} |
|
r.RCount += len(rs) |
|
} |
|
if len(pendingIDs) > 0 { |
|
secondIdxMap[rootID] = append(secondIdxMap[rootID], pendingIDs...) |
|
sort.Sort(model.AscFloors(secondIdxMap[rootID])) |
|
} |
|
} |
|
} |
|
res = make(map[int64][]*model.Reply, len(secondIdxMap)) |
|
for root, idxs := range secondIdxMap { |
|
seconds := make([]*model.Reply, 0, len(idxs)) |
|
for _, rpid := range idxs { |
|
if r, ok := secondMap[rpid]; ok { |
|
seconds = append(seconds, r) |
|
} |
|
} |
|
res[root] = seconds |
|
rs = append(rs, seconds...) |
|
} |
|
return |
|
} |
|
|
|
// FilDelReply delete reply which is deleted |
|
func (s *Service) FilDelReply(rps []*model.Reply) (filtedRps []*model.Reply) { |
|
for _, rp := range rps { |
|
if !rp.IsDeleted() { |
|
var childs []*model.Reply |
|
for _, child := range rp.Replies { |
|
if !child.IsDeleted() { |
|
childs = append(childs, child) |
|
} |
|
} |
|
rp.Replies = childs |
|
filtedRps = append(filtedRps, rp) |
|
} |
|
} |
|
return |
|
} |
|
|
|
// EmojiReplaceI EmojiReplace international |
|
func (s *Service) EmojiReplaceI(mobiAPP string, build int64, roots ...*model.Reply) { |
|
if mobiAPP == "android_i" && build > 1125000 && build < 2005000 { |
|
for _, root := range roots { |
|
if root != nil { |
|
if root.Content != nil { |
|
if emoCodes := _emojiCode.FindAllString(root.Content.Message, -1); len(emoCodes) > 0 { |
|
root.Content.Message = RepressEmotions(root.Content.Message, emoCodes) |
|
} |
|
} |
|
for _, rp := range root.Replies { |
|
if rp.Content != nil { |
|
if emoCodes := _emojiCode.FindAllString(rp.Content.Message, -1); len(emoCodes) > 0 { |
|
rp.Content.Message = RepressEmotions(rp.Content.Message, emoCodes) |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// EmojiReplace EmojiReplace |
|
func (s *Service) EmojiReplace(plat int8, build int64, roots ...*model.Reply) { |
|
if (plat == model.PlatIPad || plat == model.PlatPadHd || plat == model.PlatIPhone) && build <= 8170 { |
|
for _, root := range roots { |
|
if root != nil { |
|
for _, rp := range root.Replies { |
|
if rp.Content != nil { |
|
if emoCodes := _emojiCode.FindAllString(rp.Content.Message, -1); len(emoCodes) > 0 { |
|
rp.Content.Message = RepressEmotions(rp.Content.Message, emoCodes) |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
func (s *Service) buildReply(c context.Context, sub *model.Subject, replies []*model.Reply, mid int64, escape bool) (err error) { |
|
var ( |
|
ok bool |
|
assistMap map[int64]int |
|
actionMap map[int64]int8 |
|
blackedMap map[int64]bool |
|
attetionMap map[int64]*accmdl.RelationReply |
|
rpIDs = make([]int64, 0, len(replies)) |
|
mids = make([]int64, 0, len(replies)) |
|
uniqMids = make(map[int64]struct{}, len(replies)) |
|
fansMap map[int64]*model.FansDetail |
|
accMap map[int64]*accmdl.Card |
|
) |
|
if len(replies) == 0 { |
|
return |
|
} |
|
for _, rp := range replies { |
|
if rp.Content != nil { |
|
for _, mid := range rp.Content.Ats { |
|
uniqMids[mid] = struct{}{} |
|
} |
|
} |
|
uniqMids[rp.Mid] = struct{}{} |
|
rpIDs = append(rpIDs, rp.RpID) |
|
} |
|
for mid := range uniqMids { |
|
mids = append(mids, mid) |
|
} |
|
g := errgroup.WithContext(c) |
|
if mid > 0 { |
|
g.Go(func(c context.Context) error { |
|
actionMap, _ = s.actions(c, mid, sub.Oid, rpIDs) |
|
return nil |
|
}) |
|
g.Go(func(c context.Context) error { |
|
attetionMap, _ = s.getAttentions(c, mid, mids) |
|
return nil |
|
}) |
|
g.Go(func(c context.Context) error { |
|
blackedMap, _ = s.GetBlacklist(c, mid) |
|
return nil |
|
}) |
|
} |
|
g.Go(func(c context.Context) error { |
|
accMap, _ = s.getAccInfo(c, mids) |
|
return nil |
|
}) |
|
if !s.IsWhiteAid(sub.Oid, sub.Type) { |
|
g.Go(func(c context.Context) error { |
|
fansMap, _ = s.FetchFans(c, mids, sub.Mid) |
|
return nil |
|
}) |
|
g.Go(func(c context.Context) error { |
|
assistMap = s.getAssistList(c, sub.Mid) |
|
return nil |
|
}) |
|
} |
|
g.Wait() |
|
// set reply info |
|
for _, r := range replies { |
|
r.FillFolder() |
|
r.FillStr(escape) |
|
if r.Content != nil { |
|
r.Content.FillAts(accMap) |
|
} |
|
r.Action = actionMap[r.RpID] |
|
// member info and degrade |
|
r.Member = new(model.Member) |
|
var card *accmdl.Card |
|
if card, ok = accMap[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 _, ok = blackedMap[r.Mid]; ok { |
|
r.State = model.ReplyStateBlacklist |
|
} |
|
if _, ok = assistMap[r.Mid]; ok { |
|
r.Assist = 1 |
|
} |
|
if attetion, ok := attetionMap[r.Mid]; ok { |
|
if attetion.Following { |
|
r.Member.Following = 1 |
|
} |
|
} |
|
// temporary fix situation: rcount < 0 |
|
if r.RCount < 0 { |
|
r.RCount = 0 |
|
} |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) rootReplyIDs(c context.Context, sub *model.Subject, sort int8, pn, ps int, loadCount bool) (rpIDs []int64, count int, err error) { |
|
var ( |
|
ok bool |
|
start = (pn - 1) * ps |
|
end = start + ps - 1 |
|
) |
|
if count = sub.RCount; start >= count { |
|
return |
|
} |
|
if sort == model.SortByLike { |
|
mid := metadata.Int64(c, metadata.Mid) |
|
res, err := s.replyHotFeed(c, mid, sub.Oid, int(sub.Type), pn, ps) |
|
if err == nil && res.RpIDs != nil && len(res.RpIDs) > 0 { |
|
log.Info("reply-feed(test): reply abtest mid(%d) oid(%d) type (%d) test name(%s) rpIDs(%v)", mid, sub.Oid, sub.Type, res.Name, res.RpIDs) |
|
rpIDs = res.RpIDs |
|
if loadCount { |
|
count = int(res.Count) |
|
} |
|
return rpIDs, count, nil |
|
} |
|
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)", mid, sub.Oid, sub.Type, res.Name, res.RpIDs) |
|
} |
|
} |
|
// expire the index cache |
|
if ok, err = s.dao.Redis.ExpireIndex(c, sub.Oid, sub.Type, sort); err != nil { |
|
log.Error("s.dao.Redis.ExpireIndex(%d,%d,%d) error(%v)", sub.Oid, sub.Type, sort, err) |
|
return |
|
} |
|
if !ok { |
|
// here we can get that Serviceub.RCount > 0 |
|
switch sort { |
|
case model.SortByFloor: |
|
s.dao.Databus.RecoverFloorIdx(c, sub.Oid, sub.Type, end+1, false) |
|
rpIDs, err = s.dao.Reply.GetIdsSortFloor(c, sub.Oid, sub.Type, start, ps) |
|
case model.SortByCount: |
|
s.dao.Databus.RecoverIndex(c, sub.Oid, sub.Type, sort) |
|
rpIDs, err = s.dao.Reply.GetIdsSortCount(c, sub.Oid, sub.Type, start, ps) |
|
case model.SortByLike: |
|
s.dao.Databus.RecoverIndex(c, sub.Oid, sub.Type, sort) |
|
if rpIDs, err = s.dao.Reply.GetIdsSortLike(c, sub.Oid, sub.Type, start, ps); err != nil { |
|
return |
|
} |
|
if loadCount { |
|
count, err = s.dao.Reply.CountLike(c, sub.Oid, sub.Type) |
|
} |
|
} |
|
if err != nil { |
|
log.Error("s.rootIDs(%d,%d,%d,%d,%d) error(%v)", sub.Oid, sub.Type, sort, start, ps, err) |
|
return |
|
} |
|
} else { |
|
var isEnd bool |
|
if rpIDs, isEnd, err = s.dao.Redis.Range(c, sub.Oid, sub.Type, sort, start, end); err != nil { |
|
log.Error("s.dao.Redis.Range(%d,%d,%d,%d,%d) error(%v)", sub.Oid, sub.Type, sort, start, end, err) |
|
return |
|
} |
|
if (sort == model.SortByLike || sort == model.SortByCount) && loadCount { |
|
if count, err = s.dao.Redis.CountReplies(c, sub.Oid, sub.Type, sort); err != nil { |
|
log.Error("s.dao.Redis.CountLike(%d,%d,%d) error(%v)", sub.Oid, sub.Type, sort, err) |
|
} |
|
} |
|
if sort == model.SortByFloor && len(rpIDs) < ps && !isEnd { |
|
//The addition and deletion of comments may result in the display of duplicate entries |
|
rpIDs, err = s.dao.Reply.GetIdsSortFloor(c, sub.Oid, sub.Type, start, ps) |
|
if err != nil { |
|
log.Error("s.rootIDs(%d,%d,%d,%d,%d) error(%v)", sub.Oid, sub.Type, sort, start, ps, err) |
|
return |
|
} |
|
s.dao.Databus.RecoverFloorIdx(c, sub.Oid, sub.Type, end+1, false) |
|
} |
|
} |
|
return |
|
} |
|
|
|
// topReply return top replies from cache. |
|
func (s *Service) topReply(c context.Context, sub *model.Subject, top uint32) (rp *model.Reply, err error) { |
|
if top != model.ReplyAttrUpperTop && top != model.ReplyAttrAdminTop { |
|
return |
|
} |
|
if sub.AttrVal(top) == model.AttrYes && sub.Meta != "" { |
|
var meta model.SubjectMeta |
|
err = json.Unmarshal([]byte(sub.Meta), &meta) |
|
if err != nil { |
|
log.Error("s.topReply(%d,%d,%d) unmarshal error(%v)", sub.Oid, sub.Type, top, err) |
|
return |
|
} |
|
var rpid int64 |
|
if top == model.SubAttrAdminTop && meta.AdminTop != 0 { |
|
rpid = meta.AdminTop |
|
|
|
} else if top == model.SubAttrUpperTop && meta.UpperTop != 0 { |
|
rpid = meta.UpperTop |
|
} |
|
if rpid != 0 { |
|
rp, err = s.ReplyContent(c, sub.Oid, rpid, sub.Type) |
|
if err != nil { |
|
log.Error("s.GetReply(%d,%d,%d) error(%v)", sub.Oid, sub.Type, rpid, err) |
|
return |
|
} |
|
if rp == nil { |
|
log.Error("s.GetReply(%d,%d,%d) is nil", sub.Oid, sub.Type, rpid) |
|
} |
|
return |
|
} |
|
} |
|
|
|
if sub.AttrVal(top) == model.AttrYes { |
|
if rp, err = s.dao.Mc.GetTop(c, sub.Oid, sub.Type, top); err != nil { |
|
log.Error("s.dao.Mc.GetTop(%d,%d,%d) error(%v)", sub.Oid, sub.Type, top, err) |
|
return |
|
} |
|
if rp == nil { |
|
s.dao.Databus.AddTop(c, sub.Oid, sub.Type, top) |
|
} |
|
} |
|
return |
|
} |
|
|
|
func (s *Service) userAuditReplies(c context.Context, mid, oid int64, tp int8) (rootMap map[int64]*model.Reply, secondMap map[int64][]*model.Reply, total int, err error) { |
|
rpIDs, err := s.dao.Redis.UserAuditReplies(c, mid, oid, tp) |
|
if err != nil { |
|
log.Error("s.dao.Redis.Range(%d,%d,%d) error(%v)", oid, tp, mid, err) |
|
return |
|
} |
|
rpMap, err := s.repliesMap(c, oid, tp, rpIDs) |
|
if err != nil { |
|
return |
|
} |
|
total = len(rpMap) |
|
rootMap = make(map[int64]*model.Reply) |
|
secondMap = make(map[int64][]*model.Reply) |
|
for _, rp := range rpMap { |
|
if rp.Root == 0 { |
|
if !rp.IsTop() { |
|
rootMap[rp.RpID] = rp |
|
} |
|
} else { |
|
secondMap[rp.Root] = append(secondMap[rp.Root], rp) |
|
} |
|
} |
|
return |
|
} |
|
|
|
// repliesMap multi get reply from cache or db when missed and fill content. |
|
func (s *Service) repliesMap(c context.Context, oid int64, tp int8, rpIDs []int64) (res map[int64]*model.Reply, err error) { |
|
if len(rpIDs) == 0 { |
|
return |
|
} |
|
res, missIDs, err := s.dao.Mc.GetMultiReply(c, rpIDs) |
|
if err != nil { |
|
log.Error("s.dao.Mc.GetMultiReply(%d,%d,%d) error(%v)", oid, tp, rpIDs, err) |
|
err = nil |
|
res = make(map[int64]*model.Reply, len(rpIDs)) |
|
missIDs = rpIDs |
|
} |
|
if len(missIDs) > 0 { |
|
var ( |
|
mrp map[int64]*model.Reply |
|
mrc map[int64]*model.Content |
|
) |
|
if mrp, err = s.dao.Reply.GetByIds(c, oid, tp, missIDs); err != nil { |
|
log.Error("s.reply.GetByIds(%d,%d,%d) error(%v)", oid, tp, rpIDs, err) |
|
return |
|
} |
|
if mrc, err = s.dao.Content.GetByIds(c, oid, missIDs); err != nil { |
|
log.Error("s.content.GetByIds(%d,%d) error(%v)", oid, rpIDs, err) |
|
return |
|
} |
|
rs := make([]*model.Reply, 0, len(missIDs)) |
|
for _, rpID := range missIDs { |
|
if rp, ok := mrp[rpID]; ok { |
|
rp.Content = mrc[rpID] |
|
res[rpID] = rp |
|
rs = append(rs, rp.Clone()) |
|
} |
|
} |
|
// asynchronized add reply cache |
|
select { |
|
case s.replyChan <- replyChan{rps: rs}: |
|
default: |
|
log.Warn("s.replyChan is full") |
|
} |
|
} |
|
return |
|
} |
|
|
|
// ReplyContent get reply and content. |
|
func (s *Service) ReplyContent(c context.Context, oid, rpID int64, tp int8) (r *model.Reply, err error) { |
|
if r, err = s.dao.Mc.GetReply(c, rpID); err != nil { |
|
log.Error("replyCacheDao.GetReply(%d, %d, %d) error(%v)", oid, rpID, tp, err) |
|
} |
|
if r == nil { |
|
if r, err = s.dao.Reply.Get(c, oid, rpID); err != nil { |
|
log.Error("s.reply.GetReply(%d, %d) error(%v)", oid, rpID, err) |
|
return nil, err |
|
} |
|
if r == nil { |
|
return nil, ecode.ReplyNotExist |
|
} |
|
if r.Content, err = s.dao.Content.Get(c, oid, rpID); err != nil { |
|
return nil, err |
|
} |
|
if err = s.dao.Mc.AddReply(c, r); err != nil { |
|
log.Error("mc.AddReply(%d,%d,%d) error(%v)", oid, rpID, tp, err) |
|
err = nil |
|
} |
|
} |
|
if r.Oid != oid || r.Type != tp { |
|
log.Warn("reply dismatches with parameter, oid: %d, rpID: %d, tp: %d, actual: %d, %d, %d", oid, rpID, tp, r.Oid, r.RpID, r.Type) |
|
return nil, ecode.RequestErr |
|
} |
|
return r, nil |
|
} |
|
|
|
func (s *Service) repliesByRoot(c context.Context, oid, root int64, tp int8, pn, ps int) (res []*model.Reply, err error) { |
|
var ( |
|
cache bool |
|
rpIDs []int64 |
|
start = (pn - 1) * ps |
|
end = start + ps - 1 |
|
) |
|
if cache, err = s.dao.Redis.ExpireIndexByRoot(c, root); err != nil { |
|
return |
|
} |
|
if cache { |
|
if rpIDs, err = s.dao.Redis.RangeByRoot(c, root, start, end); err != nil { |
|
log.Error("s.dao.Redis.RangeByRoots() err(%v)", err) |
|
return |
|
} |
|
} else { |
|
if rpIDs, err = s.dao.Reply.GetIdsByRoot(c, oid, root, tp, start, ps); err != nil { |
|
log.Error("s.dao.Reply.GetIdsByRoot(oid %d,tp %d,root %d) err(%v)", oid, tp, root, err) |
|
} |
|
s.dao.Databus.RecoverIndexByRoot(c, oid, root, tp) |
|
} |
|
rs, err := s.repliesMap(c, oid, tp, rpIDs) |
|
if err != nil { |
|
return |
|
} |
|
for _, rpID := range rpIDs { |
|
if rp, ok := rs[rpID]; ok { |
|
res = append(res, rp) |
|
} |
|
} |
|
return |
|
} |
|
|
|
// ReplyHots return the hot replies. |
|
func (s *Service) ReplyHots(c context.Context, oid int64, typ int8, pn, ps int) (sub *model.Subject, res []*model.Reply, err error) { |
|
if !model.LegalSubjectType(typ) { |
|
err = ecode.ReplyIllegalSubType |
|
return |
|
} |
|
if sub, err = s.Subject(c, oid, typ); err != nil { |
|
log.Error("s.Subject(%d,%d) error(%v)", oid, typ, err) |
|
return |
|
} |
|
hotIDs, _, err := s.rootReplyIDs(c, sub, model.SortByLike, pn, ps, false) |
|
if err != nil { |
|
return |
|
} |
|
rootMap, err := s.repliesMap(c, sub.Oid, sub.Type, hotIDs) |
|
if err != nil { |
|
return |
|
} |
|
for _, rpID := range hotIDs { |
|
if rp, ok := rootMap[rpID]; ok { |
|
res = append(res, rp) |
|
} |
|
} |
|
if len(res) == 0 { |
|
res = _emptyReplies |
|
} |
|
return |
|
} |
|
|
|
// Dialog ... |
|
func (s *Service) Dialog(c context.Context, mid, oid int64, tp int8, root, dialog int64, pn, ps int, escape bool) (rps []*model.Reply, err error) { |
|
var ( |
|
start = (pn - 1) * ps |
|
end = start + ps - 1 |
|
ok bool |
|
rpIDs []int64 |
|
) |
|
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.RangeRpsByDialog(c, dialog, start, end) |
|
} else { |
|
s.dao.Databus.RecoverDialogIdx(c, oid, tp, root, dialog) |
|
rpIDs, err = s.dao.Reply.GetIDsByDialog(c, oid, tp, root, dialog, start, ps) |
|
} |
|
if err != nil { |
|
log.Error("range replies by dialog 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) |
|
} |
|
} |
|
|
|
sub, err := s.Subject(c, oid, tp) |
|
if err != nil { |
|
return |
|
} |
|
for _, rp := range rps { |
|
rp.DialogStr = strconv.FormatInt(rp.Dialog, 10) |
|
} |
|
if err = s.buildReply(c, sub, rps, mid, escape); err != nil { |
|
return |
|
} |
|
return |
|
}
|
|
|