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.
379 lines
11 KiB
379 lines
11 KiB
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 |
|
} |
|
} |
|
}
|
|
|