910 lines
25 KiB
Go
910 lines
25 KiB
Go
|
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
|
|||
|
}
|