380 lines
11 KiB
Go
380 lines
11 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"sort"
|
||
"time"
|
||
|
||
model "go-common/app/job/main/reply/model/reply"
|
||
"go-common/library/log"
|
||
)
|
||
|
||
const (
|
||
_replySliceNum = 20000
|
||
)
|
||
|
||
func dialogMapByRoot(rootID int64, rps []*model.RpItem, oid int64, tp int8) (dialogMap map[int64][]*model.RpItem) {
|
||
length := len(rps)
|
||
dialogMap = make(map[int64][]*model.RpItem)
|
||
// 根评论下没有评论
|
||
if length == 0 {
|
||
return
|
||
}
|
||
|
||
// 这里由于种种原因, 可能子评论的ID比父评论大,故按Floor排序
|
||
// 按Floor严格排序,保证父评论也几乎是升序排列,提高后续的命中率
|
||
sort.Slice(rps, func(i, j int) bool {
|
||
return rps[i].Floor < rps[j].Floor
|
||
})
|
||
|
||
for i := 0; i < length; i++ {
|
||
if rps[i] == nil {
|
||
return
|
||
}
|
||
if rps[i].Parent == rootID {
|
||
// 对话根评论
|
||
continue
|
||
}
|
||
// i-1 must >= 0, 因为第一个元素必然是对话根评论which parrent=root, 出现这种情况说明数据是脏的
|
||
if i-1 < 0 {
|
||
log.Error("invalid data, rootID(%d), rpID(%d), parrent(%d) oid(%d) type(%d)", rootID, rps[i].ID, rps[i].Parent, oid, tp)
|
||
return
|
||
}
|
||
|
||
if rps[i].Parent == rps[i-1].Parent {
|
||
// 和上一条评论回复同一条评论的情况,按ID排序,所以这种情况的概率会很高
|
||
rps[i].Next = rps[i-1].Next
|
||
} else {
|
||
var j int
|
||
if sort.IsSorted(model.RpItems(rps)) {
|
||
// 和上一条评论回复不同评论的情况, 其父评论一定在它之前
|
||
j = sort.Search(i, func(n int) bool {
|
||
return rps[n].ID >= rps[i].Parent
|
||
})
|
||
} else {
|
||
for index := range rps[:i] {
|
||
if rps[index].ID == rps[i].Parent {
|
||
j = index
|
||
break
|
||
}
|
||
}
|
||
}
|
||
// search 如果返回j==i说明没搜索,或者遍历到了最后一个, 这种情况说明数据是脏的
|
||
if j == i {
|
||
log.Error("invalid data, rootID(%d), rpID(%d), parrent(%d) oid(%d) type(%d)", rootID, rps[i].ID, rps[i].Parent, oid, tp)
|
||
return nil
|
||
}
|
||
rps[i].Next = rps[j]
|
||
}
|
||
}
|
||
tmp := new(struct {
|
||
ID int64
|
||
DiaglogID int64
|
||
})
|
||
for i := 0; i < length; i++ {
|
||
if rps[i] == nil {
|
||
return
|
||
}
|
||
|
||
next := rps[i].Next
|
||
if next == nil {
|
||
// 如果是对话根评论
|
||
dialogMap[rps[i].ID] = append(dialogMap[rps[i].ID], rps[i])
|
||
} else if next.ID == tmp.ID {
|
||
// 这里tmp缓存了上一个评论的父评论, 减少查找的次数
|
||
// 如果跟上一条评论评论的是同一条评论,则可以直接加进上一个dialog
|
||
dialogMap[tmp.DiaglogID] = append(dialogMap[tmp.DiaglogID], rps[i])
|
||
} else {
|
||
depth := 0
|
||
for next.Next != nil {
|
||
next = next.Next
|
||
depth++
|
||
if depth > 10000 {
|
||
for i := range rps {
|
||
log.Error("rp: %v", rps[i])
|
||
}
|
||
log.Error("recursive reach max depth")
|
||
return nil
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return
|
||
}
|
||
|
||
func (s *Service) setDialogByRoot(c context.Context, oid int64, tp int8, rootID int64) (err error) {
|
||
// 循环获取某个根评论下的所有子评论
|
||
rps, err := s.dao.Reply.FixDialogGetRepliesByRoot(c, oid, tp, rootID)
|
||
if err != nil {
|
||
log.Error("fix dialog error (%v)", err)
|
||
return
|
||
}
|
||
//根据所有子评论构造 key为二级父评论(即对话根评论), value为二级父评论下的所有子评论的map
|
||
dialogMap := dialogMapByRoot(rootID, rps, oid, tp)
|
||
for k, v := range dialogMap {
|
||
ids := make([]int64, len(v))
|
||
for i := range v {
|
||
ids[i] = v[i].ID
|
||
}
|
||
s.dao.Reply.FixDialogSetDialogBatch(c, oid, k, ids)
|
||
}
|
||
return
|
||
}
|
||
|
||
// actionRecoverFixDialog fix dialog
|
||
func (s *Service) actionRecoverFixDialog(c context.Context, msg *consumerMsg) {
|
||
var (
|
||
err error
|
||
)
|
||
var d struct {
|
||
Oid int64 `json:"oid"`
|
||
Tp int8 `json:"tp"`
|
||
Root int64 `json:"root"`
|
||
}
|
||
if err = json.Unmarshal([]byte(msg.Data), &d); err != nil {
|
||
log.Error("json.Unmarshal() error(%v)", err)
|
||
return
|
||
}
|
||
s.setDialogByRoot(c, d.Oid, d.Tp, d.Root)
|
||
}
|
||
|
||
func (s *Service) actionRecoverDialog(c context.Context, msg *consumerMsg) {
|
||
var (
|
||
ok bool
|
||
err error
|
||
)
|
||
var d struct {
|
||
Oid int64 `json:"oid"`
|
||
Tp int8 `json:"tp"`
|
||
Root int64 `json:"root"`
|
||
Dialog int64 `json:"dialog"`
|
||
}
|
||
if err = json.Unmarshal([]byte(msg.Data), &d); err != nil {
|
||
log.Error("json.Unmarshal() error(%v)", err)
|
||
return
|
||
}
|
||
if ok, err = s.dao.Redis.ExpireDialogIndex(c, d.Dialog); err == nil && !ok {
|
||
rps, err := s.dao.Reply.GetByDialog(c, d.Oid, d.Tp, d.Root, d.Dialog)
|
||
if err != nil {
|
||
return
|
||
}
|
||
err = s.dao.Redis.AddDialogIndex(c, d.Dialog, rps)
|
||
if err != nil {
|
||
log.Error("s.dao.Redis.AddDialogIndex() error (%v)", err)
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
func (s *Service) acionRecoverFloorIdx(c context.Context, msg *consumerMsg) {
|
||
var (
|
||
err error
|
||
rCount int
|
||
limit int
|
||
ok bool
|
||
sub *model.Subject
|
||
)
|
||
var d struct {
|
||
Oid int64 `json:"oid"`
|
||
Tp int8 `json:"tp"`
|
||
Count int `json:"count"`
|
||
Floor int `json:"floor"`
|
||
}
|
||
if err = json.Unmarshal([]byte(msg.Data), &d); err != nil {
|
||
log.Error("json.Unmarshal() error(%v)", err)
|
||
return
|
||
}
|
||
sub, err = s.getSubject(c, d.Oid, d.Tp)
|
||
if err != nil || sub == nil {
|
||
log.Error("s.getSubject(%d,%d) failed!err:=%v", d.Oid, d.Tp, err)
|
||
return
|
||
}
|
||
if sub.RCount == 0 {
|
||
return
|
||
}
|
||
startFloor := sub.Count + 1
|
||
if ok, err = s.dao.Redis.ExpireIndex(c, d.Oid, d.Tp, model.SortByFloor); err == nil && ok {
|
||
startFloor, err = s.dao.Redis.MinScore(c, d.Oid, d.Tp, model.SortByFloor)
|
||
if err != nil {
|
||
log.Error("s.dao.Redis.MinScore(%d,%d) failed!err:=%v", d.Oid, d.Tp, err)
|
||
return
|
||
}
|
||
if startFloor <= 1 {
|
||
if startFloor != -1 {
|
||
if err = s.dao.Redis.AddFloorIndexEnd(c, d.Oid, d.Tp); err != nil {
|
||
log.Error("s.dao.Redis.AddFloorIndexEnd(%d, %d) error(%v)", d.Oid, d.Tp, err)
|
||
}
|
||
}
|
||
return
|
||
}
|
||
if d.Count > 0 {
|
||
rCount, err = s.dao.Redis.CountReplies(c, d.Oid, d.Tp, model.SortByFloor)
|
||
if err != nil {
|
||
log.Error("s.dao.Redis.CountReplies(%d,%d) failed!err:=%v", d.Oid, d.Tp, err)
|
||
return
|
||
}
|
||
}
|
||
}
|
||
if d.Count > 0 {
|
||
limit = d.Count - rCount
|
||
} else if d.Floor > 0 {
|
||
limit = startFloor - d.Floor
|
||
} else {
|
||
log.Warn("RecoverFloorByCount(%d,%d) count(%d) or floor(%d) invalid!", d.Oid, d.Tp, d.Floor, d.Count)
|
||
return
|
||
}
|
||
limit += s.batchNumber
|
||
if limit < (s.batchNumber / 2) {
|
||
return
|
||
} else if limit < s.batchNumber {
|
||
limit = s.batchNumber
|
||
}
|
||
rs, err := s.dao.Reply.GetByFloorLimit(c, d.Oid, d.Tp, startFloor, limit)
|
||
if err != nil {
|
||
log.Error("s.dao.Reply.GetByFloorLimit(%d,%d) failed!err:=%v", d.Oid, d.Tp, err)
|
||
return
|
||
}
|
||
if err = s.dao.Redis.AddFloorIndex(c, d.Oid, d.Tp, rs...); err != nil {
|
||
log.Error("s.dao.Redis.AddFloorIndex(%d, %d) error(%v)", d.Oid, d.Tp, err)
|
||
}
|
||
if len(rs) < limit {
|
||
if err = s.dao.Redis.AddFloorIndexEnd(c, d.Oid, d.Tp); err != nil {
|
||
log.Error("s.dao.Redis.AddFloorIndexEnd(%d, %d) error(%v)", d.Oid, d.Tp, err)
|
||
}
|
||
return
|
||
}
|
||
}
|
||
|
||
// actionRecoverIndex recover index of archive's reply
|
||
func (s *Service) actionRecoverIndex(c context.Context, msg *consumerMsg) {
|
||
var (
|
||
err error
|
||
ok bool
|
||
sub *model.Subject
|
||
rs []*model.Reply
|
||
)
|
||
var d struct {
|
||
Oid int64 `json:"oid"`
|
||
Tp int8 `json:"tp"`
|
||
Sort int8 `json:"sort"`
|
||
}
|
||
if err = json.Unmarshal([]byte(msg.Data), &d); err != nil {
|
||
log.Error("json.Unmarshal() error(%v)", err)
|
||
return
|
||
}
|
||
if d.Oid <= 0 || !model.CheckSort(d.Sort) {
|
||
log.Error("The structure of doActionRecoverIndex msg.Data(%s) was wrong", msg.Data)
|
||
return
|
||
}
|
||
if ok, err = s.dao.Redis.ExpireIndex(c, d.Oid, d.Tp, d.Sort); err == nil && !ok {
|
||
sub, err = s.getSubject(c, d.Oid, d.Tp)
|
||
if err != nil || sub == nil {
|
||
log.Error("s.getSubject failed , oid(%d,%d) err(%v)", d.Oid, d.Tp, err)
|
||
return
|
||
}
|
||
if d.Sort == model.SortByFloor {
|
||
rs, err = s.dao.Reply.GetAllInSlice(c, d.Oid, d.Tp, sub.Count, _replySliceNum)
|
||
if err != nil {
|
||
log.Error("dao.Reply.GetAllInSlice(%d, %d) error(%v)", d.Oid, d.Tp, err)
|
||
return
|
||
}
|
||
// floor index
|
||
if ok, err = s.dao.Redis.ExpireIndex(c, d.Oid, d.Tp, model.SortByFloor); err == nil && !ok {
|
||
if err = s.dao.Redis.AddFloorIndex(c, d.Oid, d.Tp, rs...); err != nil {
|
||
log.Error("s.dao.Redis.AddFloorIndex(%d, %d) error(%v)", d.Oid, d.Tp, err)
|
||
}
|
||
if err = s.dao.Redis.AddFloorIndexEnd(c, d.Oid, d.Tp); err != nil {
|
||
log.Error("s.dao.Redis.AddFloorIndexEnd(%d, %d) error(%v)", d.Oid, d.Tp, err)
|
||
}
|
||
}
|
||
} else if d.Sort == model.SortByLike {
|
||
rs, err = s.dao.Reply.GetByLikeLimit(c, d.Oid, d.Tp, 30000)
|
||
if err != nil {
|
||
log.Error("dao.Reply.GetAllInSlice(%d, %d) error(%v)", d.Oid, d.Tp, err)
|
||
return
|
||
}
|
||
// like index
|
||
if ok, err = s.dao.Redis.ExpireIndex(c, d.Oid, d.Tp, model.SortByLike); err == nil && !ok {
|
||
rpts, _ := s.dao.Report.GetMapByOid(c, d.Oid, d.Tp)
|
||
if err = s.dao.Redis.AddLikeIndexBatch(c, d.Oid, d.Tp, rpts, rs...); err != nil {
|
||
log.Error("s.dao.Redis.AddLikeIndexBatch(%d, %d) error(%v)", d.Oid, d.Tp, err)
|
||
}
|
||
}
|
||
} else if d.Sort == model.SortByCount {
|
||
rs, err = s.dao.Reply.GetByCountLimit(c, d.Oid, d.Tp, 20000)
|
||
if err != nil {
|
||
log.Error("dao.Reply.GetAllInSlice(%d, %d) error(%v)", d.Oid, d.Tp, err)
|
||
return
|
||
}
|
||
// count index
|
||
if ok, err = s.dao.Redis.ExpireIndex(c, d.Oid, d.Tp, model.SortByCount); err == nil && !ok {
|
||
if err = s.dao.Redis.AddCountIndexBatch(c, d.Oid, d.Tp, rs...); err != nil {
|
||
log.Error("s.dao.Redis.AddCountIndex(%d, %d) error(%v)", d.Oid, d.Tp, err)
|
||
}
|
||
}
|
||
}
|
||
// 回源index时把top缓存初始化,减少attr扫表慢查询
|
||
if n := sub.TopCount(); n > 0 {
|
||
for _, r := range rs {
|
||
if r.IsTop() {
|
||
top := model.SubAttrAdminTop
|
||
if r.IsUpTop() {
|
||
top = model.SubAttrUpperTop
|
||
}
|
||
err = sub.TopSet(r.RpID, top, 1)
|
||
if err == nil {
|
||
_, err = s.dao.Subject.UpMeta(c, d.Oid, d.Tp, sub.Meta, time.Now())
|
||
if err != nil {
|
||
log.Error("s.dao.Subject.UpMeta(%d,%d,%d) failed!err:=%v ", r.RpID, r.Oid, d.Tp, err)
|
||
}
|
||
s.dao.Mc.AddSubject(c, sub)
|
||
}
|
||
// get reply with content
|
||
var rp *model.Reply
|
||
rp, err = s.getReply(c, d.Oid, r.RpID)
|
||
if err == nil && rp != nil {
|
||
s.dao.Mc.AddTop(c, rp)
|
||
}
|
||
n--
|
||
}
|
||
if n == 0 {
|
||
break
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// actionRecoverRootIndex recover index of root reply
|
||
func (s *Service) actionRecoverRootIndex(c context.Context, msg *consumerMsg) {
|
||
var (
|
||
err error
|
||
ok bool
|
||
rs []*model.Reply
|
||
)
|
||
var d struct {
|
||
Oid int64 `json:"oid"`
|
||
Tp int8 `json:"tp"`
|
||
Root int64 `json:"root"`
|
||
}
|
||
if err = json.Unmarshal([]byte(msg.Data), &d); err != nil {
|
||
log.Error("json.Unmarshal() error(%v)", err)
|
||
return
|
||
}
|
||
if d.Oid <= 0 || d.Root <= 0 {
|
||
log.Error("The structure of doActionRecoverRootIndex msg.Data(%s) was wrong", msg.Data)
|
||
return
|
||
}
|
||
if ok, err = s.dao.Redis.ExpireNewChildIndex(c, d.Root); err == nil && !ok {
|
||
if rs, err = s.dao.Reply.GetAllByRoot(c, d.Oid, d.Root, d.Tp); err != nil {
|
||
log.Error("dao.Reply.GetAllReply(%d, %d) error(%v)", d.Oid, d.Tp, err)
|
||
return
|
||
}
|
||
if err = s.dao.Redis.AddNewChildIndex(c, d.Root, rs...); err != nil {
|
||
log.Error("s.dao.Redis.AddFloorIndexByRoot(%d, %d) error(%v)", d.Oid, d.Tp, err)
|
||
return
|
||
}
|
||
}
|
||
}
|