bilibili-backup/app/interface/main/reply/service/list.go

910 lines
25 KiB
Go
Raw Permalink Normal View History

2019-04-22 10:59:20 +08:00
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
}