bilibili-backup/app/interface/main/reply/service/list.go
2019-04-22 02:59:20 +00:00

910 lines
25 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}