Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"index_test.go",
"reply_test.go",
"service_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/reply/conf:go_default_library",
"//vendor/github.com/go-sql-driver/mysql:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"admin.go",
"business.go",
"fold.go",
"index.go",
"notify.go",
"reply.go",
"service.go",
"spam.go",
"upper.go",
],
importpath = "go-common/app/job/main/reply/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/openplatform/article/model:go_default_library",
"//app/interface/openplatform/article/rpc/client:go_default_library",
"//app/job/main/reply/conf:go_default_library",
"//app/job/main/reply/dao/message:go_default_library",
"//app/job/main/reply/dao/notice:go_default_library",
"//app/job/main/reply/dao/reply:go_default_library",
"//app/job/main/reply/dao/search:go_default_library",
"//app/job/main/reply/dao/spam:go_default_library",
"//app/job/main/reply/dao/stat:go_default_library",
"//app/job/main/reply/model/reply:go_default_library",
"//app/service/main/account/api:go_default_library",
"//app/service/main/archive/api:go_default_library",
"//app/service/main/archive/api/gorpc:go_default_library",
"//app/service/main/archive/model/archive:go_default_library",
"//app/service/main/assist/model/assist:go_default_library",
"//app/service/main/assist/rpc/client:go_default_library",
"//app/service/main/relation/model:go_default_library",
"//app/service/openplatform/pgc-season/api/grpc/episode/v1:go_default_library",
"//library/database/elastic:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/queue/databus:go_default_library",
"//library/sync/pipeline/fanout:go_default_library",
"//library/time:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,894 @@
package service
import (
"context"
"encoding/json"
"fmt"
"strconv"
"time"
"go-common/app/job/main/reply/model/reply"
model "go-common/app/job/main/reply/model/reply"
xsql "go-common/library/database/sql"
"go-common/library/log"
xtime "go-common/library/time"
)
const (
_eventReportAdd = "report_add"
_eventReportDel = "report_del"
_eventReportIgnore = "report_ignore"
_eventReportRecover = "report_recover"
)
func (s *Service) actionAdmin(c context.Context, msg *consumerMsg) {
var d struct {
Op string `json:"op"`
Adid int64 `json:"adid"`
AdName string `json:"adname"`
Oid int64 `json:"oid"`
RpID int64 `json:"rpid"`
Mid int64 `json:"mid"`
Tp int8 `json:"tp"`
Action uint32 `json:"action"`
Moral int `json:"moral"`
Notify bool `json:"notify"`
Remark string `json:"remark"`
MTime xtime.Time `json:"mtime"`
Ftime int64 `json:"ftime"`
Audit int8 `json:"audit"`
Reason int8 `json:"reason"`
Content string `json:"content"`
FReason int8 `json:"freason"`
Assist bool `json:"assist"`
State int8 `json:"state"`
}
if err := json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
if d.Oid <= 0 || d.RpID <= 0 {
log.Error("The structure of msg.Data(%s) was wrong", msg.Data)
return
}
rp, err := s.getReply(c, d.Oid, d.RpID)
if err != nil {
log.Error("s.getReply failed , oid(%d), RpID(%d) err(%v)", d.Oid, d.RpID, err)
return
}
if rp == nil {
log.Error("getReply nil oid(%d) RpID(%d)", d.Oid, d.RpID)
return
}
switch {
case d.Op == "del":
s.adminDel(c, rp, d.Adid, d.Mid, d.Ftime, d.Moral, d.AdName, d.Remark, d.MTime, d.Notify, d.Reason, d.FReason)
case d.Op == "del_rpt":
s.reportDel(c, rp, d.Adid, d.Mid, d.Ftime, d.Moral, d.AdName, d.Remark, d.MTime, d.Notify, d.Audit, d.Reason, d.Content, d.FReason)
case d.Op == "del_up":
s.userDel(c, rp, d.Mid, d.MTime, d.Remark, d.Assist)
case d.Op == "re", d.Op == "pass":
s.passReply(c, rp, d.MTime, d.Adid, d.Remark, d.Op)
case d.Op == "edit":
s.addSearchUp(c, rp.State, rp, nil)
case d.Op == "ignore":
s.reportIgnore(c, rp, d.Audit, d.MTime, d.Adid, d.Remark)
case d.Op == "transfer":
s.reportTransfer(c, rp, d.Audit, d.MTime, d.Adid, d.Remark)
case d.Op == "stateset":
s.reportStateSet(c, rp, d.State, d.MTime, d.Adid, d.Remark)
case d.Op == "top_add":
err := s.topAdd(c, rp, d.MTime, d.Action, model.SubAttrAdminTop)
if err != nil {
log.Error("s.topAdd(oid:%d,tp:%d err(%v))", rp.Oid, rp.Type, err)
return
}
// s.dao.Redis.AddTopOid(c, rp.Oid, rp.Type)
s.adminLog(c, rp, d.Adid, model.AdminIsReport, model.AdminOperSubTop, "管理员置顶评论", d.Remark)
s.addSearchUp(c, rp.State, rp, nil)
case d.Op == "rpt_re":
s.reportRecover(c, rp, d.Audit, d.MTime, d.Adid, d.Remark)
}
}
func (s *Service) reportStateSet(c context.Context, rp *model.Reply, state int8, mtime xtime.Time, adid int64, remark string) (err error) {
var (
op int8
result string
oldState = rp.State
)
rpt, err := s.dao.Report.Get(c, rp.Oid, rp.RpID)
if err != nil || rpt == nil {
log.Error("dao.Report.GetReport(%d, %d) met error (%v)", rp.Oid, rp.RpID, err)
return
}
rpt.State = state
rpt.MTime = mtime
op = model.AdminOperRptTransferArbitration
if adid == 0 {
result = "系统自动移交至风纪委"
} else {
result = "管理员移交至风纪委"
}
if _, err = s.dao.Report.Update(c, rpt); err != nil {
log.Error("dao.Report.Update(%v) error(%v)", rpt, err)
return
}
// admin log
s.adminLog(c, rp, adid, model.AdminIsReport, op, result, remark)
s.addSearchUp(c, oldState, rp, rpt)
return
}
func (s *Service) reportTransfer(c context.Context, rp *model.Reply, audit int8, mtime xtime.Time, adid int64, remark string) (err error) {
var (
op int8
result string
oldState = rp.State
)
rpt, err := s.dao.Report.Get(c, rp.Oid, rp.RpID)
if err != nil || rpt == nil {
log.Error("dao.Report.GetReport(%d, %d) met error (%v)", rp.Oid, rp.RpID, err)
return
}
if rpt.IsTransferred() {
log.Error("report(%v) has been transfferd before!", *rpt)
return
}
if audit == model.AuditTypeOne {
rpt.State = model.ReportStateNew
op = model.AdminOperRptTransfer1
result = "二审转一审"
} else if audit == model.AuditTypeTwo {
rpt.State = model.ReportStateNewTwo
op = model.AdminOperRptTransfer2
result = "一审转二审"
}
rpt.MTime = mtime
rpt.SetTransferred()
if _, err = s.dao.Report.Update(c, rpt); err != nil {
log.Error("dao.Report.Update(%v) error(%v)", rpt, err)
return
}
// admin log
s.adminLog(c, rp, adid, model.AdminIsReport, op, result, remark)
s.addSearchUp(c, oldState, rp, rpt)
return
}
func (s *Service) reportIgnore(c context.Context, rp *model.Reply, audit int8, mtime xtime.Time, adid int64, remark string) (err error) {
var (
op int8
result string
oldState = rp.State
)
rpt, err := s.dao.Report.Get(c, rp.Oid, rp.RpID)
if err != nil || rpt == nil {
log.Error("dao.Report.GetReport(%d, %d) met error (%v)", rp.Oid, rp.RpID, err)
return
}
if audit == model.AuditTypeOne {
rpt.State = model.ReportStateIgnoreOne
op = model.AdminOperRptIgnore1
result = "一审忽略"
} else if audit == model.AuditTypeTwo {
rpt.State = model.ReportStateIgnoreTwo
op = model.AdminOperRptIgnore2
result = "二审忽略"
} else {
if rpt.State == model.ReportStateNew {
rpt.State = model.ReportStateIgnoreOne
} else {
rpt.State = model.ReportStateIgnoreTwo
}
op = model.AdminOperIgnoreReport
result = "已忽略举报"
}
rpt.MTime = mtime
if _, err = s.dao.Report.Update(c, rpt); err != nil {
log.Error("dao.Report.Update(%v) error(%v)", rpt, err)
return
}
// admin log
s.adminLog(c, rp, adid, model.AdminIsReport, op, result, remark)
s.addSearchUp(c, oldState, rp, rpt)
sub, err := s.getSubject(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.getSubject(%v,%v) err(%v) or sub nil", sub.Oid, sub.Type, err)
return
}
if err = s.dao.PubEvent(c, _eventReportIgnore, rpt.Mid, sub, rp, rpt); err != nil {
return
}
return
}
func (s *Service) reportRecover(c context.Context, rp *model.Reply, audit int8, mtime xtime.Time, adid int64, remark string) (err error) {
var (
ok bool
op int8
result string
rootRp *model.Reply
oldState = rp.State
)
isRoot := rp.Root == 0 && rp.Parent == 0
rpt, _ := s.dao.Report.Get(c, rp.Oid, rp.RpID)
if err != nil || rpt == nil {
log.Error("dao.Report.GetReport(%d, %d) met error (%v)", rp.Oid, rp.RpID, err)
return
}
sub, err := s.getSubject(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.getSubject(%v,%v) err(%v) or sub nil", sub.Oid, sub.Type, err)
return
}
if audit == model.AuditTypeOne {
// 一审移除,恢复到待一审忽略,评论正常
rpt.State = model.ReportStateIgnoreOne
op = model.AdminOperRptRecover1
result = "一审恢复评论"
} else if audit == model.AuditTypeTwo {
// 二审移除,恢复到二审忽略,评论正常
rpt.State = model.ReportStateIgnoreTwo
op = model.AdminOperRptRecover2
result = "二审恢复评论"
} else {
log.Error("reportRecover unsupport audit: %d", audit)
return
}
rpt.MTime = mtime
if _, err = s.dao.Report.Update(c, rpt); err != nil {
log.Error("dao.Report.Update(%v) error(%v) or row==0", rpt, err)
return
}
// 只恢复管理员删除的评论,不恢复用户删除的
if rp.State == model.ReplyStateAdminDel {
rp.MTime = mtime
rp.State = model.ReplyStateNormal
if err = s.tranRecover(c, rp, model.ReplyStateNormal, isRoot); err != nil {
log.Error("Transaction recover reply failed err(%v)", err)
return
}
}
// add cache
if isRoot {
if err = s.dao.Mc.AddReply(c, rp); err != nil {
log.Error("s.dao.Mc.AddReply failed , RpID(%d), err(%v)", rp.RpID, err)
}
if err = s.dao.Redis.AddIndex(c, rp.Oid, rp.Type, rpt, rp, true); err != nil {
log.Error("s.dao.Redis.AddIndex(%d, %d) error(%v)", rp.Oid, rp.Type, err)
}
} else {
if ok, err = s.dao.Redis.ExpireNewChildIndex(c, rp.Root); err == nil && ok {
if err = s.dao.Redis.AddNewChildIndex(c, rp.Root, rp); err != nil {
log.Error("s.dao.Redis.AddFloorIndexByRoot failed , rproot(%d), err(%v)", rp.Root, err)
}
}
if rootRp, err = s.getReplyCache(c, rp.Oid, rp.Root); err != nil {
log.Error("s.getReply failed , oid(%d), root(%d) err(%v)", rp.Oid, rp.Root, err)
} else if rootRp != nil {
rootRp.RCount++
if err = s.dao.Mc.AddReply(c, rootRp); err != nil {
log.Error("s.dao.Mc.AddReply failed , RpID(%d) err(%v)", rootRp.RpID, err)
}
}
}
// log
s.adminLog(c, rp, adid, model.AdminIsReport, op, result, remark)
// notify
s.addSearchUp(c, oldState, rp, rpt)
s.upAcount(c, sub.Oid, sub.Type, sub.ACount, rp.CTime.Time())
if err = s.dao.PubEvent(c, _eventReportRecover, rpt.Mid, sub, rp, rpt); err != nil {
return
}
return
}
// topAdd add top reply
func (s *Service) topAdd(c context.Context, rp *model.Reply, ts xtime.Time, act uint32, tp uint32) (err error) {
sub, err := s.getSubject(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.getsubject(%v,%v) err(%v) or sub nil", rp.Oid, rp.Type, err)
return
}
if act == 1 && sub.AttrVal(tp) == 1 {
log.Error("Repeat to add top reply(%d,%d,%d,%d) ", rp.RpID, rp.Oid, tp, sub.Attr)
return
}
err = sub.TopSet(rp.RpID, tp, act)
if err != nil {
log.Error("sub.TopSet(%d,%d,%d) failed!err:=%v ", rp.RpID, rp.Oid, tp, err)
return
}
sub.AttrSet(act, tp)
rp.AttrSet(act, tp)
tx, err := s.beginTran(c)
if err != nil {
log.Error("s.beginTran() err(%v)", err)
return
}
var rows int64
//rp.State = model.ReplyStateTop
if rows, err = s.dao.Reply.TxUpAttr(tx, rp.Oid, rp.RpID, rp.Attr, ts.Time()); err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Reply.UpState(%v, %d) error(%v) or row==0", rp, rp.State, err)
return
}
if rows, err = s.dao.Subject.TxUpMeta(tx, sub.Oid, sub.Type, sub.Meta, ts.Time()); err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.TxUpMeta(oid:%d,tp:%d) err(%v) rows(%d)", sub.Oid, sub.Type, err, rows)
return
}
if rows, err = s.dao.Subject.TxUpAttr(tx, sub.Oid, sub.Type, sub.Attr, ts.Time()); err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Upattr(oid:%d,tp:%d) err(%v) rows(%d)", sub.Oid, sub.Type, err, rows)
return
}
tx.Commit()
s.dao.Mc.AddSubject(c, sub)
if act == 1 {
s.dao.Redis.DelIndexBySortType(c, rp, reply.SortByCount)
s.dao.Redis.DelIndexBySortType(c, rp, reply.SortByLike)
} else if rp.IsNormal() {
if ok, err := s.dao.Redis.ExpireIndex(c, sub.Oid, sub.Type, model.SortByCount); err == nil && ok {
if err = s.dao.Redis.AddCountIndex(c, sub.Oid, sub.Type, rp); err != nil {
log.Error("s.dao.Redis.AddCountIndex failed , oid(%d) type(%d) err(%v)", sub.Oid, sub.Type, err)
}
}
if ok, err := s.dao.Redis.ExpireIndex(c, sub.Oid, sub.Type, model.SortByLike); err == nil && ok {
rpts := make(map[int64]*reply.Report, 1)
if rpt, _ := s.dao.Report.Get(c, rp.Oid, rp.RpID); rpt != nil {
rpts[rp.RpID] = rpt
}
if err = s.dao.Redis.AddLikeIndex(c, sub.Oid, sub.Type, rpts, rp); err != nil {
log.Error("s.dao.Redis.AddLikeIndex failed , oid(%d) type(%d) err(%v)", sub.Oid, sub.Type, err)
}
}
}
s.dao.Mc.AddReply(c, rp)
s.dao.Mc.AddTop(c, rp)
if act == 1 {
s.dao.PubEvent(c, "top", 0, sub, rp, nil)
} else if act == 0 {
s.dao.PubEvent(c, "untop", 0, sub, rp, nil)
}
// 折叠评论被置顶自动取消折叠
if rp.IsFolded() && act == 1 {
rp.State = model.ReplyStateNormal
s.marker.Do(c, func(ctx context.Context) {
if _, err := s.dao.Reply.UpState(ctx, rp.Oid, rp.RpID, rp.State, time.Now()); err == nil {
if ok, err := s.dao.Redis.ExpireIndex(c, rp.Oid, rp.Type, model.SortByFloor); err == nil && ok {
s.dao.Redis.AddFloorIndex(c, rp.Oid, rp.Type, rp)
}
s.handleFolded(ctx, rp)
}
})
}
return
}
func (s *Service) userDel(c context.Context, rp *model.Reply, mid int64, mtime xtime.Time, remark string, assist bool) (err error) {
var (
state int8
sub *model.Subject
oldState = rp.State
isRoot = rp.Root == 0 && rp.Parent == 0
isFolded = rp.IsFolded()
)
if rp.IsDeleted() {
s.addSearchUp(c, oldState, rp, nil)
return
}
if sub, err = s.dao.Subject.Get(c, rp.Oid, rp.Type); err != nil || sub == nil {
log.Error("s.dao.Subject.Get(%d,%d) error(%v)", rp.Oid, rp.Type, err)
return
}
if rp.Mid == mid {
state = model.ReplyStateUserDel
} else if assist {
state = model.ReplyStateAssistDel
} else if mid > 0 {
state = model.ReplyStateUpDel
} else {
state = model.ReplyStateAdminDel
}
rp.MTime = mtime
if err = s.tranDel(c, rp, state, sub, isRoot); err != nil {
log.Error("reportDel tranDel(%d, %d) error(%v)", rp.Oid, rp.RpID, err)
return
}
if isFolded {
s.marker.Do(c, func(ctx context.Context) {
s.handleFolded(ctx, rp)
})
}
if err = s.clearReplyCache(c, rp); err != nil {
log.Error("reportDel clearReplyCache(%d, %d) error(%v)", rp.Oid, rp.RpID, err)
}
rp.State = state
if rp.Mid == mid {
s.adminLog(c, rp, mid, model.AdminIsNotReport, model.AdminOperDeleteUser, "由本人删除", remark)
s.searchDao.DelReply(c, rp.RpID, sub.Oid, mid, state)
} else if assist {
s.adminLog(c, rp, mid, model.AdminIsNotReport, model.AdminOperDeleteAssist, "由UP主协管员删除", remark)
s.addAssistLog(c, sub.Mid, mid, sub.Oid, 1, 1, strconv.FormatInt(rp.RpID, 10), rp.Content.Message)
} else if mid > 0 {
s.adminLog(c, rp, mid, model.AdminIsNotReport, model.AdminOperDeleteUp, "由up主删除", remark)
} else {
s.adminLog(c, rp, 0, model.AdminIsNotReport, model.AdminOperDelete, "由系统删除", remark)
}
s.dao.PubEvent(c, "reply_del", rp.Mid, sub, rp, nil)
return
}
func (s *Service) adminDel(c context.Context, rp *model.Reply, adid, mid, ftime int64, moral int, adName, remark string, mtime xtime.Time, notify bool, reason, freason int8) (err error) {
var (
sub *model.Subject
report *model.Report
oldState = rp.State
isRoot = rp.Root == 0 && rp.Parent == 0
isFolded = rp.IsFolded()
)
if rp.IsDeleted() {
s.addSearchUp(c, oldState, rp, nil)
return
}
if sub, err = s.dao.Subject.Get(c, rp.Oid, rp.Type); err != nil || sub == nil {
log.Error("s.dao.Subject.Get(%d,%d) error(%v)", rp.Oid, rp.Type, err)
return
}
rp.MTime = mtime
if err = s.tranDel(c, rp, model.ReplyStateAdminDel, sub, isRoot); err != nil {
log.Error("reportDel tranDel(%d, %d) error(%v)", rp.Oid, rp.RpID, err)
return
}
if isFolded {
s.marker.Do(c, func(ctx context.Context) {
s.handleFolded(ctx, rp)
})
}
if err = s.clearReplyCache(c, rp); err != nil {
log.Error("reportDel clearReplyCache(%d, %d) error(%v)", rp.Oid, rp.RpID, err)
}
if report, err = s.dao.Report.Get(c, rp.Oid, rp.RpID); err != nil {
log.Error("reportDel getReport(%d,%d) error(%v)", rp.Oid, rp.RpID, err)
return
}
if report != nil {
if report.State == model.ReportStateNew || report.State == model.ReportStateNewTwo {
if report.State == model.ReportStateNew {
report.State = model.ReportStateDeleteOne
} else if report.State == model.ReportStateNewTwo {
report.State = model.ReportStateDeleteTwo
}
report.MTime = mtime
if _, err = s.dao.Report.Update(c, report); err != nil {
log.Error("reportDel updateReport(%d, %d) error(%v)", report.Oid, report.ID, err)
return
}
s.addSearchUp(c, oldState, rp, report)
s.dao.PubEvent(c, _eventReportDel, 0, sub, rp, report)
}
}
rp.State = model.ReplyStateAdminDel
// add moral and notify
s.moralAndNotify(c, rp, moral, notify, mid, adid, adName, remark, reason, freason, ftime, false)
// forbidden tip
forbidDay := strconv.FormatInt(ftime, 10) + "天"
if ftime == -1 {
forbidDay = "永久"
}
s.adminLog(c, rp, adid, model.AdminIsNotReport, model.AdminOperDelete, fmt.Sprintf("已删除并封禁%s/扣除%d节操", forbidDay, moral), remark)
s.addSearchUp(c, oldState, rp, nil)
if report == nil {
s.dao.PubEvent(c, "reply_del", 0, sub, rp, nil)
}
return
}
func (s *Service) reportDel(c context.Context, rp *model.Reply, adid, mid, ftime int64, moral int, adName, remark string, mtime xtime.Time, notify bool, audit int8, reason int8, content string, freason int8) (err error) {
var (
op int8
isPunish bool
sub *model.Subject
report *model.Report
oldState = rp.State
)
if sub, err = s.dao.Subject.Get(c, rp.Oid, rp.Type); err != nil || sub == nil {
log.Error("s.dao.Subject.Get(%d,%d) error(%v)", rp.Oid, rp.Type, err)
return
}
if report, err = s.dao.Report.Get(c, rp.Oid, rp.RpID); err != nil || report == nil {
log.Error("reportDel getReport(%d,%d) error(%v)", rp.Oid, rp.RpID, err)
return
}
report.MTime = mtime
// 一审、二审操作
switch audit {
case model.AuditTypeOne:
report.State = model.ReportStateDeleteOne
op = model.AdminOperRptDel1
case model.AuditTypeTwo:
report.State = model.ReportStateDeleteTwo
op = model.AdminOperRptDel2
default:
report.State = model.ReportStateDelete
op = model.AdminOperDeleteByReport
}
report.Reason = reason
report.Content = content
if _, err = s.dao.Report.Update(c, report); err != nil {
log.Error("reportDel updateReport(%d, %d) error(%v)", report.Oid, report.ID, err)
return
}
if !rp.IsDeleted() {
isRoot := rp.Root == 0 && rp.Parent == 0
rp.MTime = mtime
if err = s.tranDel(c, rp, model.ReplyStateAdminDel, sub, isRoot); err != nil {
log.Error("reportDel tranDel(%d, %d) error(%v)", rp.Oid, rp.RpID, err)
return
}
if err = s.clearReplyCache(c, rp); err != nil {
log.Error("reportDel clearReplyCache(%d, %d) error(%v)", rp.Oid, rp.RpID, err)
}
rp.State = model.ReplyStateAdminDel
} else {
isPunish = true
}
// add moral and notify
s.moralAndNotify(c, rp, moral, notify, mid, adid, adName, remark, reason, freason, ftime, isPunish)
// forbidden tip
forbidDay := strconv.FormatInt(ftime, 10) + "天"
if ftime == -1 {
forbidDay = "永久"
}
s.adminLog(c, rp, adid, model.AdminIsReport, op, fmt.Sprintf("已通过举报删除并封禁%s/扣除%d节操", forbidDay, moral), remark)
s.addSearchUp(c, oldState, rp, report)
if err = s.dao.PubEvent(c, _eventReportDel, report.Mid, sub, rp, report); err != nil {
return
}
return
}
func (s *Service) clearReplyCache(c context.Context, rp *model.Reply) (err error) {
var (
sub *model.Subject
rootRp *model.Reply
isRoot = rp.Root == 0 && rp.Parent == 0
)
if !isRoot && rp.IsNormal() {
// update root cache for count.
if rootRp, err = s.getReplyCache(c, rp.Oid, rp.Root); err != nil {
return
}
if rootRp != nil {
rootRp.RCount--
if err = s.addReplyCache(c, rootRp); err != nil {
log.Error("s.dao.Mc.addReplyCache(%d,%d,%d) error(%v)", rp.Oid, rp.Type, rp.RpID, err)
}
}
}
if rp.IsAdminTop() {
s.dao.Mc.DeleteTop(c, rp, model.ReplyAttrAdminTop)
}
if rp.IsUpTop() {
s.dao.Mc.DeleteTop(c, rp, model.ReplyAttrUpperTop)
}
if err = s.dao.Mc.DeleteReply(c, rp.RpID); err != nil {
log.Error("s.dao.Mc.DeleteReply failed , RpID(%d), err(%v)", rp.RpID, err)
}
if err = s.dao.Redis.DelIndex(c, rp); err != nil {
log.Error("s.dao.Redis.DelIndex failed , RpID(%d), err(%v)", rp.RpID, err)
}
if rp.State == model.ReplyStateAudit {
s.eraseAuditIndex(c, rp)
}
// update reply count
sub, err = s.dao.Subject.Get(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.dao.Subject.Get(%d,%d) error(%v)", rp.Oid, rp.Type, err)
return
}
if err = s.dao.Mc.AddSubject(c, sub); err != nil {
log.Error("s.dao.Mc.AddSubject failed , oid(%d), err(%v)", sub.Oid, err)
}
s.upAcount(c, sub.Oid, sub.Type, sub.ACount, rp.CTime.Time())
return
}
func (s *Service) passReply(c context.Context, rp *model.Reply, mtime xtime.Time, adid int64, remark, op string) {
var (
err error
ok bool
rootRp *model.Reply
oldState = rp.State
isRoot = rp.Root == 0 && rp.Parent == 0
)
sub, err := s.dao.Subject.Get(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.dao.Subject.Get(%d,%d) error(%v)", rp.Oid, rp.Type, err)
return
}
if rp.State <= model.ReplyStateHidden {
s.addSearchUp(c, oldState, rp, nil)
return
}
rp.MTime = mtime
rp.State = model.ReplyStateNormal
if op == "re" || oldState == model.ReplyStateAudit {
if err = s.tranRecover(c, rp, model.ReplyStateNormal, isRoot); err != nil {
log.Error("Transaction recover reply failed err(%v)", err)
return
}
} else if op == "pass" {
var row int64
var tx *xsql.Tx
tx, err = s.dao.BeginTran(c)
if err != nil {
return
}
if row, err = s.dao.Reply.TxUpState(tx, rp.Oid, rp.RpID, model.ReplyStateNormal, mtime.Time()); err != nil || row == 0 {
tx.Rollback()
log.Error("dao.Reply.TxUpState(%v, %d) error(%v) or row==0", rp, model.ReplyStateNormal, err)
return
}
if rp.State == model.ReplyStateAudit || rp.State == model.ReplyStateMonitor {
if _, err = s.dao.Subject.TxDecrMCount(tx, rp.Oid, rp.Type, mtime.Time()); err != nil {
tx.Rollback()
log.Error("dao.Reply.TxDecrMCount(%v) error(%v)", rp, err)
return
}
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit error(%v)", err)
return
}
}
if isRoot {
if ok, err = s.dao.Redis.ExpireIndex(c, rp.Oid, rp.Type, model.SortByFloor); err == nil && ok {
var min int
min, err = s.dao.Redis.MinScore(c, rp.Oid, rp.Type, reply.SortByFloor)
if err != nil {
log.Error("s.dao.Redis.AddFloorIndex failed , oid(%d) type(%d) err(%v)", rp.Oid, rp.Type, err)
} else if rp.Floor > min {
if err = s.dao.Redis.AddFloorIndex(c, rp.Oid, rp.Type, rp); err != nil {
log.Error("s.dao.Redis.AddFloorIndex failed , RpID(%d), err(%v)", rp.RpID, err)
}
}
}
if ok, err = s.dao.Redis.ExpireIndex(c, rp.Oid, rp.Type, model.SortByCount); err == nil && ok {
if err = s.dao.Redis.AddCountIndex(c, rp.Oid, rp.Type, rp); err != nil {
log.Error("s.dao.Redis.AddCountIndex failed , RpID(%d), err(%v)", rp.RpID, err)
}
}
if ok, err = s.dao.Redis.ExpireIndex(c, rp.Oid, rp.Type, model.SortByLike); err == nil && ok {
rpts := make(map[int64]*reply.Report, 1)
if rpt, _ := s.dao.Report.Get(c, rp.Oid, rp.RpID); rpt != nil {
rpts[rp.RpID] = rpt
}
if err = s.dao.Redis.AddLikeIndex(c, rp.Oid, rp.Type, rpts, rp); err != nil {
log.Error("s.dao.Redis.AddLikeIndex failed , RpID(%d), err(%v)", rp.RpID, err)
}
}
} else {
if ok, err = s.dao.Redis.ExpireDialogIndex(c, rp.Dialog); err == nil && ok {
if err = s.dao.Redis.AddDialogIndex(c, rp.Dialog, []*model.Reply{rp}); err != nil {
log.Error("s.dao.Redis.AddDialogINdex Error (%v)", err)
}
}
if ok, err = s.dao.Redis.ExpireNewChildIndex(c, rp.Root); err == nil && ok {
if err = s.dao.Redis.AddNewChildIndex(c, rp.Root, rp); err != nil {
log.Error("s.dao.Redis.AddFloorIndexByRoot failed , rproot(%d), err(%v)", rp.Root, err)
}
}
if op == "re" || oldState == model.ReplyStateAudit {
if rootRp, err = s.getReplyCache(c, rp.Oid, rp.Root); err != nil {
log.Error("s.getReply failed , oid(%d), root(%d) err(%v)", rp.Oid, rp.Root, err)
} else if rootRp != nil {
rootRp.RCount++
if err = s.addReplyCache(c, rootRp); err != nil {
log.Error("s.addReplyCache oid(%d), rpid(%d) err(%v)", rootRp.Oid, rootRp.RpID, err)
}
}
}
}
if err = s.addReplyCache(c, rp); err != nil {
log.Error("s.addReplyCache oid(%d), rpid(%d) err(%v)", rp.Oid, rp.RpID, err)
}
if oldState == model.ReplyStateAudit {
s.dao.Redis.DelAuditIndexs(c, rp)
}
// update reply count
s.upAcount(c, sub.Oid, sub.Type, sub.ACount, rp.CTime.Time())
if err = s.dao.Mc.AddSubject(c, sub); err != nil {
log.Error("s.dao.Mc.AddSubject failed , oid(%d) err(%v)", sub.Oid, err)
}
// admin log
if op == "re" {
s.adminLog(c, rp, adid, model.AdminIsNotReport, model.AdminOperRecover, "已恢复评论", remark)
} else if op == "pass" {
s.adminLog(c, rp, adid, model.AdminIsNotReport, model.AdminOperPass, "已通过评论", remark)
}
s.addSearchUp(c, oldState, rp, nil)
}
func (s *Service) tranRecover(c context.Context, rp *model.Reply, state int8, isRoot bool) error {
var (
err error
rootReply *model.Reply
count int
rows int64
tx *xsql.Tx
)
if tx, err = s.beginTran(c); err != nil {
return err
}
mtime := rp.MTime
if rp, err = s.dao.Reply.GetForUpdate(tx, rp.Oid, rp.RpID); err != nil || rp == nil {
tx.Rollback()
return fmt.Errorf("s.dao.Reply.GetForUpdate(%d,%d) error(%v) or is nil)", rp.Oid, rp.RpID, err)
}
if mtime != 0 {
rp.MTime = mtime
}
if rp.IsNormal() {
tx.Rollback()
return fmt.Errorf("reply(%d,%d) already is normal", rp.Oid, rp.RpID)
}
rows, err = s.dao.Reply.TxUpState(tx, rp.Oid, rp.RpID, state, rp.MTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("TxUpState error(%v) or rows(%d)", err, rows)
}
if isRoot {
count = rp.RCount + 1
} else {
rootReply, err = s.dao.Reply.Get(c, rp.Oid, rp.Root)
if err != nil {
tx.Rollback()
return err
}
count = 1
}
if isRoot {
rows, err = s.dao.Subject.TxIncrRCount(tx, rp.Oid, rp.Type, rp.MTime.Time())
} else {
rows, err = s.dao.Reply.TxIncrRCount(tx, rp.Oid, rp.Root, rp.MTime.Time())
}
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("TxIncrRCount error(%v) or rows(%d)", err, rows)
}
if isRoot || rootReply != nil && rootReply.IsNormal() {
rows, err = s.dao.Subject.TxIncrACount(tx, rp.Oid, rp.Type, count, rp.MTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("TxIncrACount error(%v) or rows(%d)", err, rows)
}
}
return tx.Commit()
}
func (s *Service) tranDel(c context.Context, rp *model.Reply, state int8, sub *model.Subject, isRoot bool) error {
var (
count int
rows int64
rootReply *model.Reply
err error
tx *xsql.Tx
)
if tx, err = s.beginTran(c); err != nil {
return err
}
mtime := rp.MTime
if rp, err = s.dao.Reply.GetForUpdate(tx, rp.Oid, rp.RpID); err != nil || rp == nil {
tx.Rollback()
return fmt.Errorf("s.dao.Reply.GetForUpdate(%d,%d) error(%v) or is nil)", rp.Oid, rp.RpID, err)
}
if mtime != 0 {
rp.MTime = mtime
}
if rp.IsDeleted() || rp.AttrVal(reply.ReplyAttrAdminTop) == 1 {
tx.Rollback()
return fmt.Errorf("reply(%d,%d) already deleted", rp.Oid, rp.RpID)
}
rows, err = s.dao.Reply.TxUpState(tx, rp.Oid, rp.RpID, state, rp.MTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("error(%v) or rows(%d)", err, rows)
}
if rp.IsNormal() {
if isRoot {
count = rp.RCount + 1
rows, err = s.dao.Subject.TxDecrACount(tx, rp.Oid, rp.Type, count, rp.MTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("error(%v) or rows(%d)", err, rows)
}
rows, err = s.dao.Subject.TxDecrCount(tx, rp.Oid, rp.Type, rp.MTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("SubjectTxDecrCount error(%v) or rows(%d)", err, rows)
}
} else {
if rootReply, err = s.dao.Reply.GetForUpdate(tx, rp.Oid, rp.Root); err != nil {
tx.Rollback()
return err
}
if rootReply != nil {
if rootReply.IsNormal() {
rows, err = s.dao.Subject.TxDecrACount(tx, rp.Oid, rp.Type, 1, rp.MTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("error(%v) or rows(%d)", err, rows)
}
}
rows, err = s.dao.Reply.TxDecrCount(tx, rp.Oid, rp.Root, rp.MTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("ReplyTxDecrCount error(%v) or rows(%d)", err, rows)
}
}
}
}
if rp.AttrVal(model.ReplyAttrUpperTop) == 1 {
rp.AttrSet(0, model.ReplyAttrUpperTop)
sub.AttrSet(0, model.SubAttrUpperTop)
sub.TopSet(0, model.SubAttrUpperTop, 0)
if rows, err = s.dao.Subject.TxUpMeta(tx, sub.Oid, sub.Type, sub.Meta, rp.MTime.Time()); err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.TxUpMeta(oid:%d,tp:%d) err(%v) rows(%d)", sub.Oid, sub.Type, err, rows)
return fmt.Errorf("dao.TxUpMeta(oid:%d,tp:%d) err(%v) rows(%d)", sub.Oid, sub.Type, err, rows)
}
if rows, err = s.dao.Subject.TxUpAttr(tx, sub.Oid, sub.Type, sub.Attr, rp.MTime.Time()); err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("dao.Upattr(oid:%d,tp:%d) err(%v) rows(%d)", sub.Oid, sub.Type, err, rows)
}
//rp.State = model.ReplyStateTop
if rows, err = s.dao.Reply.TxUpAttr(tx, rp.Oid, rp.RpID, rp.Attr, rp.MTime.Time()); err != nil || rows == 0 {
tx.Rollback()
return fmt.Errorf("dao.Reply.UpState(%v, %d) error(%v) or rows(%d)", rp, rp.State, err, rows)
}
}
if rp.State == model.ReplyStateMonitor || rp.State == model.ReplyStateAudit {
if _, err = s.dao.Subject.TxDecrMCount(tx, rp.Oid, rp.Type, rp.MTime.Time()); err != nil {
tx.Rollback()
log.Error("dao.Reply.TxDecrMCount(%v) error(%v)", rp, err)
return fmt.Errorf("dao.Reply.TxDecrMCount error(%v)", err)
}
}
return tx.Commit()
}
func (s *Service) eraseAuditIndex(c context.Context, rp *model.Reply) (err error) {
var rs []*model.Reply
if rp.Root == 0 && rp.Parent == 0 {
if rs, err = s.dao.Reply.GetsByRoot(c, rp.Oid, rp.RpID, rp.Type, model.ReplyStateAudit); err != nil {
return
}
}
if err = s.dao.Redis.DelAuditIndexs(c, append(rs, rp)...); err != nil {
return
}
return
}
func (s *Service) addReplyCache(c context.Context, rp *model.Reply) (err error) {
var isRoot = rp.Root == 0 && rp.Parent == 0
if err = s.dao.Mc.AddReply(c, rp); err != nil {
log.Error("s.dao.Mc.AddReply(%d,%d,%d) error(%v)", rp.Oid, rp.RpID, rp.Type, err)
}
if isRoot && rp.IsTop() {
if err = s.dao.Mc.AddTop(c, rp); err != nil {
log.Error("s.dao.Mc.AddTop(%d,%d,%d) error(%v)", rp.Oid, rp.RpID, rp.Type, err)
}
}
return
}

View File

@@ -0,0 +1,12 @@
package service
import (
"context"
"go-common/app/job/main/reply/model/reply"
)
// ListBusiness return all non-deleted business record.
func (s *Service) ListBusiness(c context.Context) (business []*reply.Business, err error) {
return s.dao.Business.ListBusiness(c)
}

View File

@@ -0,0 +1,121 @@
package service
import (
"context"
"encoding/json"
"time"
"go-common/app/job/main/reply/model/reply"
"go-common/library/database/sql"
"go-common/library/log"
)
func (s *Service) folderHanlder(ctx context.Context, msg *consumerMsg) {
var d struct {
Op string `json:"op"`
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
}
switch d.Op {
case "re_idx":
s.recoverFolderIdx(ctx, d.Oid, d.Tp, d.Root)
// case "marker":
// 删除折叠评论后需要查询是否需要取消标记
// s.marker.Do(ctx, func(ctx context.Context) {
// s.handleHasFoldedMark(ctx, d.Oid, d.Tp, d.Root)
// })
default:
return
}
}
// handleFolded ...
func (s *Service) handleFolded(ctx context.Context, rp *reply.Reply) {
sub, root, err := s.handleHasFoldedMark(ctx, rp.Oid, rp.Type, rp.Root)
if err != nil {
return
}
if sub != nil {
s.dao.Mc.DeleteSub(ctx, sub.Oid, sub.Type)
}
if root != nil {
s.dao.Mc.DeleteReply(ctx, root.RpID)
}
s.remFoldedCache(ctx, rp)
}
func (s *Service) recoverFolderIdx(ctx context.Context, oid int64, tp int8, root int64) {
rps, err := s.dao.Reply.FoldedReplies(ctx, oid, tp, root)
if err != nil || len(rps) == 0 {
return
}
// 折叠根评论
if root == 0 {
s.dao.Redis.AddFolderBatch(ctx, reply.FolderKindSub, oid, rps)
} else {
s.dao.Redis.AddFolderBatch(ctx, reply.FolderKindRoot, root, rps)
}
}
func (s *Service) handleHasFoldedMark(ctx context.Context, oid int64, tp int8, root int64) (sub *reply.Subject, reply *reply.Reply, err error) {
var (
tx *sql.Tx
count int
)
if tx, err = s.dao.BeginTran(ctx); err != nil {
return
}
// 锁subject表
if sub, err = s.dao.Subject.GetForUpdate(tx, oid, tp); err != nil {
tx.Rollback()
return
}
if count, err = s.dao.Reply.TxCountFoldedReplies(tx, oid, tp, root); err != nil || count > 0 {
tx.Rollback()
return
}
// 折叠根评论
if root == 0 {
if !sub.HasFolded() {
tx.Rollback()
return
}
sub.UnmarkHasFolded()
if _, err = s.dao.Subject.TxUpAttr(tx, oid, tp, sub.Attr, time.Now()); err != nil {
tx.Rollback()
return
}
} else {
if reply, err = s.dao.Reply.GetForUpdate(tx, oid, root); err != nil {
tx.Rollback()
return
}
if !reply.HasFolded() {
tx.Rollback()
return
}
reply.UnmarkHasFolded()
if _, err = s.dao.Reply.TxUpAttr(tx, oid, root, reply.Attr, time.Now()); err != nil {
tx.Rollback()
return
}
}
if err = tx.Commit(); err != nil {
return
}
return
}
// remFoldedCache ...
func (s *Service) remFoldedCache(ctx context.Context, rp *reply.Reply) {
if rp.IsRoot() {
s.dao.Redis.RemFolder(ctx, reply.FolderKindSub, rp.Oid, rp.RpID)
} else {
s.dao.Redis.RemFolder(ctx, reply.FolderKindRoot, rp.Root, rp.RpID)
}
}

View File

@@ -0,0 +1,379 @@
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
}
}
}

View File

@@ -0,0 +1,26 @@
package service
import (
"context"
"encoding/json"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestRecoverIdx(t *testing.T) {
var c = context.Background()
Convey("recover idx", t, WithService(func(s *Service) {
var d struct {
Oid int64 `json:"oid"`
Tp int8 `json:"tp"`
Count int `json:"count"`
Floor int `json:"floor"`
}
d.Oid = 78
d.Tp = 2
d.Count = 20
content, _ := json.Marshal(&d)
s.acionRecoverFloorIdx(c, &consumerMsg{Data: content})
}))
}

View File

@@ -0,0 +1,568 @@
package service
import (
"context"
"encoding/json"
"fmt"
"net/url"
"strconv"
artmdl "go-common/app/interface/openplatform/article/model"
model "go-common/app/job/main/reply/model/reply"
accmdl "go-common/app/service/main/account/api"
"go-common/app/service/main/archive/api"
arcmdl "go-common/app/service/main/archive/model/archive"
epmdl "go-common/app/service/openplatform/pgc-season/api/grpc/episode/v1"
"go-common/library/log"
)
const (
_mcReply = "1_1_1"
_mcCntArticle = "1_1_4"
_mcCntDynamic = "1_1_5"
_mcCntClip = "1_1_6"
_mcCntAlbum = "1_1_7"
_mcCntArchive = "1_1_8"
_msgTitleSize = 40
_msgContentSize = 80
)
func (s *Service) notifyReply(c context.Context, sub *model.Subject, rp *model.Reply) {
s.notify.Do(c, func(c context.Context) {
if len(rp.Content.Ats) > 0 {
title, link, jump, nativeJump, msg := s.messageInfo(c, rp)
if link != "" {
atmt := fmt.Sprintf("#{%s}{\"%s\"}评论中@了你", title, link)
cont := fmt.Sprintf("#{%s}{\"%s\"}", msg, jump)
if err := s.messageDao.At(c, rp.Mid, rp.Content.Ats, atmt, cont, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.At failed , mid(%d) err(%v)", rp.Mid, err)
}
}
}
if err := s.notifyCnt(c, sub, rp); err != nil {
log.Error("s.notifyCnt(%v,%v) error(%v)", sub, rp, err)
}
})
}
func (s *Service) notifyReplyReply(c context.Context, sub *model.Subject, rootRp, parentRp, rp *model.Reply) {
s.notify.Do(c, func(c context.Context) {
if err := s.notifyCnt(c, sub, rp); err != nil {
log.Error("s.notifyCnt(%v,%v) error(%v)", sub, rp, err)
}
// notify parent reply
if rp.Mid == rootRp.Mid && rp.Root == rp.Parent && len(rp.Content.Ats) == 0 {
return
}
title, link, jump, nativeJump, msg := s.messageInfo(c, rp)
if title == "" || link == "" {
return
}
// 5.29 改为根评论内容推送
//rpmt = fmt.Sprintf("#{%s}{\"%s\"}评论中回复了你", title, link)
rpmt := []rune(parentRp.Content.Message)
if len(rpmt) > _msgContentSize {
rpmt = rpmt[:_msgContentSize]
}
atmt := fmt.Sprintf("#{%s}{\"%s\"}评论中@了你", title, link)
cont := fmt.Sprintf("#{%s}{\"%s\"}", msg, jump)
// notify
if rp.Mid != rootRp.Mid && !s.getBlackListRelation(c, rootRp.Mid, rp.Mid) {
if err := s.messageDao.Reply(c, _mcReply, "", rp.Mid, rootRp.Mid, string(rpmt), cont, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.Reply failed , mid(%d) Parent(%d), err(%v)", rp.Mid, rootRp.Mid, err)
}
}
if rp.Root != rp.Parent {
if parentRp != nil && rootRp.Mid != parentRp.Mid && rp.Mid != parentRp.Mid && !s.getBlackListRelation(c, parentRp.Mid, rp.Mid) {
if err := s.messageDao.Reply(c, _mcReply, "", rp.Mid, parentRp.Mid, string(rpmt), cont, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.Reply failed , mid(%d) Parent(%d), err(%v)", rp.Mid, parentRp.Mid, err)
}
}
}
var ats []int64
for _, mid := range rp.Content.Ats {
if mid != parentRp.Mid {
ats = append(ats, mid)
}
}
if len(ats) > 0 {
if err := s.messageDao.At(c, rp.Mid, ats, atmt, cont, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.At failed , mid(%d), err(%v)", rp.Mid, err)
}
}
})
}
func (s *Service) notifyLike(c context.Context, mid int64, rp *model.Reply) {
s.notify.Do(c, func(c context.Context) {
if ok, num := s.notifyLikeNum(c, rp, mid); ok {
_, _, jump, nativeJump, msg := s.messageInfo(c, rp)
if jump == "" {
return
}
// NOTE content and title is opposite
cont := fmt.Sprintf("等%d人赞了你的回复", num)
rpmt := fmt.Sprintf("#{%s}{\"%s\"}", msg, jump)
if err := s.messageDao.Like(c, mid, rp.Mid, rpmt, cont, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.Reply failed , mid(%d) Parent(%d), err(%v)", mid, rp.Mid, err)
}
} else {
log.Warn("Didn't satify notify condition, omit notify!")
}
})
}
// notifyLike check if need notify user when receive like
func (s *Service) notifyLikeNum(c context.Context, rp *model.Reply, mid int64) (ok bool, num int64) {
if rp.Mid == mid || rp.Like <= 0 {
ok = false
return
}
num = int64(rp.Like)
// NOTE if num >1000 send when num%1000==0
if num < 10 || (num < 100 && num%10 == 0) || (num < 1000 && num%100 == 0) || num%1000 == 0 {
ok = true
}
return
}
func (s *Service) notifyCnt(c context.Context, sub *model.Subject, rp *model.Reply) (err error) {
max, err := s.dao.Redis.NotifyCnt(c, sub.Oid, sub.Type)
if err != nil {
log.Error("redis.NotifyCnt(%d,%d) error(%v)", sub.Oid, sub.Type, err)
return
}
if sub.ACount <= max {
log.Warn("notifyCnt ignore oid:%d type:%d current:%d max:%d", sub.Oid, sub.Type, sub.ACount, max)
return
}
if err = s.dao.Redis.SetNotifyCnt(c, sub.Oid, sub.Type, sub.ACount); err != nil {
log.Error("redis.SetNotifyCnt(%d,%d,%d) error(%v)", sub.Oid, sub.Type, sub.ACount, err)
return
}
switch sub.Type {
case model.SubTypeVideo:
return s.notifyArchiveCnt(c, sub, rp)
case model.SubTypeArticle:
return s.notifyArticleCnt(c, sub, rp)
case model.SubTypeDynamic:
return s.notifyDynamicCnt(c, sub, rp, _mcCntDynamic)
case model.SubTypeLiveVideo:
return s.notifyDynamicCnt(c, sub, rp, _mcCntClip)
case model.SubTypeLivePicture:
return s.notifyDynamicCnt(c, sub, rp, _mcCntAlbum)
default:
return
}
}
func (s *Service) notifyDynamicCnt(c context.Context, sub *model.Subject, rp *model.Reply, mc string) (err error) {
if !shouldNotifyLow(sub.ACount) {
return
}
title, link, _, nativeJump, msg := s.messageInfo(c, rp)
if title == "" || link == "" {
return
}
resID := fmt.Sprintf("%d_%d", rp.Oid, rp.Type)
notifyTitle := fmt.Sprintf("#{%s}{\"%s\"}收到了第%d条评论", title, link, sub.ACount)
notifyContent := fmt.Sprintf("#{%s}{\"%s\"}", msg, link)
if err = s.messageDao.Reply(c, mc, resID, rp.Mid, sub.Mid, notifyTitle, notifyContent, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.Reply(mid:%d,oid:%d,type:%d,acount:%d) error(%v)", rp.Mid, rp.Oid, rp.Type, sub.ACount, err)
}
return
}
func (s *Service) notifyArchiveCnt(c context.Context, sub *model.Subject, rp *model.Reply) (err error) {
if !shouldNotifyLow(sub.ACount) {
return
}
title, link, _, nativeJump, msg := s.messageInfo(c, rp)
if title == "" || link == "" {
return
}
resID := fmt.Sprintf("%d_%d", rp.Oid, rp.Type)
notifyTitle := fmt.Sprintf("你的投稿收到了第%d条评论", sub.ACount)
notifyContent := fmt.Sprintf("你投稿的视频“#{%s}{\"%s\"}”收到了第%d条评论『%s』", title, link, sub.ACount, msg)
if err = s.messageDao.System(c, _mcCntArchive, resID, sub.Mid, notifyTitle, notifyContent, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.System(mid:%d,oid:%d,type:%d,acount:%d) error(%v)", rp.Mid, rp.Oid, rp.Type, sub.ACount, err)
}
return
}
func (s *Service) notifyArticleCnt(c context.Context, sub *model.Subject, rp *model.Reply) (err error) {
if !shouldNotifyMiddle(sub.ACount) {
return
}
title, link, _, nativeJump, _ := s.messageInfo(c, rp)
if title == "" || link == "" {
return
}
resID := fmt.Sprintf("%d_%d", rp.Oid, rp.Type)
notifyTitle := fmt.Sprintf("你的专栏文章评论数达到了%d", sub.ACount)
notifyContent := fmt.Sprintf("你投稿的专栏文章“#{%s}{\"%s\"}”评论数达到了%d去回应一下大家的评论吧 #{点击前往}{\"%s\"}", title, link, sub.ACount, link)
if err = s.messageDao.System(c, _mcCntArticle, resID, sub.Mid, notifyTitle, notifyContent, extraInfo(nativeJump), rp.CTime.Time()); err != nil {
log.Error("s.messageDao.System(mid:%d,oid:%d,type:%d,acount:%d) error(%v)", rp.Mid, rp.Oid, rp.Type, sub.ACount, err)
}
return
}
func shouldNotifyLow(n int) (ok bool) {
switch {
case n <= 0:
ok = false
case n == 1 || n == 10 || n == 30 || n == 50:
ok = true
case n <= 1000:
ok = (n%100 == 0)
default:
ok = (n%10000 == 0)
}
return
}
func shouldNotifyMiddle(n int) (ok bool) {
switch {
case n <= 0:
ok = false
case n <= 10:
ok = true
case n <= 100:
ok = (n%10 == 0)
case n <= 1000:
ok = (n%100 == 0)
default:
ok = (n%10000 == 0)
}
return
}
// filterViolationMsg every two characters, the third character processing for *.
func filterViolationMsg(msg string) string {
s := []rune(msg)
for i := 0; i < len(s); i++ {
if i%3 != 0 {
s[i] = '*'
}
}
return string(s)
}
// moralAndNotify del moral and notify user.
func (s *Service) moralAndNotify(c context.Context, rp *model.Reply, moral int, notify bool, rptMid, adid int64, adname, remark string, reason, freason int8, ftime int64, isPunish bool) (err error) {
title, link, _, _, msg := s.messageInfo(c, rp)
smsg := []rune(msg)
if len(smsg) > 50 {
smsg = smsg[:50]
}
if moral > 0 {
reason := "发布的评论违规并被管理员删除 - " + string(smsg)
if rptMid > 0 {
reason = "发布的评论被举报并被管理员删除 - " + string(smsg)
}
arg := &accmdl.MoralReq{
Mid: rp.Mid,
Moral: -float64(moral),
Oper: adname,
Reason: reason,
Remark: remark,
}
if _, err = s.accSrv.AddMoral3(c, arg); err != nil {
log.Error("s.accSrv.AddMoral3(%d) error(%v)", rp.Mid, err)
}
}
msg = filterViolationMsg(msg)
if title != "" && link != "" && rptMid > 0 {
if err = s.reportNotify(c, rp, title, link, msg, ftime, reason, freason, isPunish); err != nil {
log.Error("s.reportNotify(%d,%d,%d) error(%v)", rp.Oid, rp.Type, rp.RpID, err)
}
}
if !notify {
return
}
if title != "" && link != "" {
// notify message
mt := "评论违规处理通知"
mc := fmt.Sprintf("您好,您在#{%s}{\"%s\"}下的评论 『%s』 ", title, link, msg)
if rptMid > 0 {
mc = fmt.Sprintf("您好,根据用户举报,您在#{%s}{\"%s\"}下的评论 『%s』 ", title, link, msg)
}
if isPunish {
mc += ",已被处罚"
} else {
mc += ",已被移除"
}
// forbidden
if ftime > 0 {
mc += fmt.Sprintf(",并被封禁%d天。", ftime)
} else if ftime == -1 {
mc += ",并被永久封禁。"
} else {
mc += "。"
}
// forbid reason
if ar, ok := model.ForbidReason[freason]; ok {
mc += "理由:" + ar + "。"
// community rules
switch {
case freason == model.ForbidReasonSpoiler || freason == model.ForbidReasonAd || freason == model.ForbidReasonUnlimitedSign || freason == model.ForbidReasonMeaningless:
mc += model.NotifyComRules
case freason == model.ForbidReasonProvoke || freason == model.ForbidReasonAttack:
mc += model.NotifyComProvoke
default:
mc += model.NofityComProhibited
}
} else { // report reason
if ar, ok := model.ReportReason[reason]; ok {
mc += "理由:" + ar + "。"
}
// community rules
switch {
case reason == model.ReportReasonSpoiler || reason == model.ReportReasonAd || reason == model.ReportReasonUnlimitedSign || reason == model.ReportReasonMeaningless:
mc += model.NotifyComRules
case reason == model.ReportReasonUnrelated:
mc += model.NotifyComUnrelated
case reason == model.ReportReasonProvoke || reason == model.ReportReasonAttack:
mc += model.NotifyComProvoke
default:
mc += model.NofityComProhibited
}
}
// send the message
if err = s.messageDao.DeleteReply(c, rp.Mid, mt, mc, rp.MTime.Time()); err != nil {
log.Error("s.messageDao.DeleteReply failed, (%d) error(%v)", rp.Mid, err)
}
log.Info("notify oid:%d type:%d rpID:%d reason:%d content:%s", rp.Oid, rp.Type, rp.RpID, reason, mc)
} else {
log.Warn("no notify oid:%d type:%d rpid:%d", rp.Oid, rp.Type, rp.RpID)
}
return
}
func (s *Service) reportNotify(c context.Context, rp *model.Reply, title, link, msg string, ftime int64, reason, freason int8, isPunish bool) (err error) {
var (
rptUser *model.ReportUser
rptUsers map[int64]*model.ReportUser
)
mt := "举报处理结果通知"
mc := fmt.Sprintf("您好,您在#{%s}{\"%s\"}下举报的评论 『%s』 ", title, link, msg)
if isPunish {
mc += "已被处罚"
} else {
mc += "已被移除"
}
// forbidden
if ftime > 0 {
mc += fmt.Sprintf(",并被封禁%d天。", ftime)
} else if ftime == -1 {
mc += ",该用户已被永久封禁。"
} else {
mc += "。"
}
// forbid reason
if ar, ok := model.ForbidReason[freason]; ok {
mc += "理由:" + ar + "。"
} else if ar, ok := model.ReportReason[reason]; ok {
mc += "理由:" + ar + "。"
}
// community rules
mc += model.NotifyComRulesReport
if rptUsers, err = s.dao.Report.GetUsers(c, rp.Oid, rp.Type, rp.RpID); err != nil {
log.Error("reportUser.GetUsers(%d,%d,%d) error(%v)", rp.Oid, rp.Type, rp.RpID, err)
return
}
for _, rptUser = range rptUsers {
// send the message
if err = s.messageDao.AcceptReport(c, rptUser.Mid, mt, mc, rp.MTime.Time()); err != nil {
log.Error("s.messageDao.DeleteReply failed, (%d) error(%v)", rp.Mid, err)
}
}
if _, err = s.dao.Report.SetUserReported(c, rp.Oid, rp.Type, rp.RpID, rp.MTime.Time()); err != nil {
log.Error("s.dao.Report.SetUserReported(%d, %d, %d) error(%v)", rp.Oid, rp.Type, rp.RpID)
}
return
}
func (s *Service) messageInfo(c context.Context, rp *model.Reply) (title, link, jump, nativeJump, msg string) {
var (
err error
native bool
subType int
extraIntentID int64
)
switch rp.Type {
case model.SubTypeVideo:
var (
m *api.Arc
uri *url.URL
)
arg := &arcmdl.ArgAid2{
Aid: rp.Oid,
}
m, err = s.arcSrv.Archive3(c, arg)
if err != nil || m == nil {
log.Error("s.arcSrv.Archive3(%v) ret:%v error(%v)", arg, m, err)
return
}
if m.AttrVal(arcmdl.AttrBitIsBangumi) == 1 {
req := &epmdl.EpAidReq{
Aids: []int32{int32(rp.Oid)},
}
resp, err1 := s.bangumiSrv.ListByAids(c, req)
if err1 != nil {
log.Error("s.bangumiSrv.ListByAids(%v, %v) error(%v)", c, req, err1)
return
}
if resp.Infos[int32(rp.Oid)] != nil {
extraIntentID = int64(resp.Infos[int32(rp.Oid)].EpisodeId)
}
subType = 1
}
if m.RedirectURL != "" {
// NOTE mobile jump
if uri, err = url.Parse(m.RedirectURL); err == nil {
q := uri.Query()
q.Set("aid", strconv.FormatInt(rp.Oid, 10))
uri.RawQuery = q.Encode()
link = uri.String()
}
} else {
link = fmt.Sprintf("http://www.bilibili.com/video/av%d/", rp.Oid)
}
title = m.Title
native = true
case model.SubTypeTopic:
if title, link, err = s.noticeDao.Topic(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Topic(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeDrawyoo:
if title, link, err = s.noticeDao.Drawyoo(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Drawyoo(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeActivity:
if title, link, err = s.noticeDao.Activity(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Activity(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeForbiden:
title, link, err = s.noticeDao.Ban(c, rp.Oid)
if err != nil {
return
}
case model.SubTypeNotice:
title, link, err = s.noticeDao.Notice(c, rp.Oid)
if err != nil {
return
}
case model.SubTypeActArc:
if title, link, err = s.noticeDao.ActivitySub(c, rp.Oid); err != nil {
log.Error("s.noticeDao.ActivitySub(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeArticle:
var m map[int64]*artmdl.Meta
arg := &artmdl.ArgAids{
Aids: []int64{rp.Oid},
}
m, err = s.articleSrv.ArticleMetas(c, arg)
if err != nil || m == nil {
log.Error("s.articleSrv.ArticleMetas(%v) ret:%v error(%v)", arg, m, err)
return
}
if meta, ok := m[rp.Oid]; ok {
title = meta.Title
link = fmt.Sprintf("http://www.bilibili.com/read/cv%d", rp.Oid)
}
case model.SubTypeLiveVideo:
if title, link, err = s.noticeDao.LiveSmallVideo(c, rp.Oid); err != nil {
log.Error("s.noticeDao.LiveSmallVideo(%d) error(%v)", rp.Oid, err)
return
}
native = true
case model.SubTypeLiveAct:
if title, link, err = s.noticeDao.LiveActivity(c, rp.Oid); err != nil {
log.Error("s.noticeDao.LiveActivity(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeLiveNotice:
//if title, link, err = s.noticeDao.LiveNotice(c, rp.Oid); err != nil {
// log.Error("s.noticeDao.LiveNotice(%d) error(%v)", rp.Oid, err)
// return
//}
// NOTE 忽略直播公告跳转链接
return
case model.SubTypeLivePicture:
if title, link, err = s.noticeDao.LivePicture(c, rp.Oid); err != nil {
log.Error("s.noticeDao.LivePiture(%d) error(%v)", rp.Oid, err)
return
}
native = true
case model.SubTypeCredit:
if title, link, err = s.noticeDao.Credit(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Credit(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeDynamic:
if title, link, err = s.noticeDao.Dynamic(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Dynamic(%d) error(%v)", rp.Oid, err)
return
}
native = true
case model.SubTypeAudio:
if title, link, err = s.noticeDao.Audio(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Audio(%d) error(%v)", rp.Oid, err)
return
}
native = true
case model.SubTypeAudioPlaylist:
if title, link, err = s.noticeDao.AudioPlayList(c, rp.Oid); err != nil {
log.Error("s.noticeDao.AudioPlayList(%d) error(%v)", rp.Oid, err)
return
}
native = true
default:
return
}
tmp := []rune(title)
if len(tmp) > _msgTitleSize {
title = string(tmp[:_msgTitleSize])
}
jump = fmt.Sprintf("%s#reply%d", link, rp.RpID)
tmp = []rune(rp.Content.Message)
if len(tmp) > _msgContentSize {
msg = string(tmp[:_msgContentSize])
} else {
msg = rp.Content.Message
}
if native {
rootID := rp.Root
if rootID == 0 {
rootID = rp.RpID
}
nativeJump = fmt.Sprintf("bilibili://comment/detail/%d/%d/%d/?subType=%d&anchor=%d&showEnter=1&extraIntentId=%d", rp.Type, rp.Oid, rootID, subType, rp.RpID, extraIntentID)
}
return
}
func extraInfo(newJump string) string {
var a = struct {
CmNewURL struct {
Title string `json:"title"`
Content string `json:"content"`
} `json:"cm_new_url"`
}{
CmNewURL: struct {
Title string `json:"title"`
Content string `json:"content"`
}{
Title: newJump,
Content: newJump,
},
}
b, _ := json.Marshal(a)
return string(b)
}

View File

@@ -0,0 +1,760 @@
package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"go-common/library/database/elastic"
"net/url"
"regexp"
"strings"
"time"
"go-common/app/job/main/reply/conf"
"go-common/app/job/main/reply/model/reply"
model "go-common/app/job/main/reply/model/reply"
accmdl "go-common/app/service/main/account/api"
assmdl "go-common/app/service/main/assist/model/assist"
relmdl "go-common/app/service/main/relation/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
xhttp "go-common/library/net/http/blademaster"
)
var (
_atReg = regexp.MustCompile(`@([^\s^:^,^@]+)`)
_topicReg = regexp.MustCompile(`#([^\n^@^#^\x{1F000}-\x{1F02F}^\x{1F0A0}-\x{1F0FF}^\x{1F100}-\x{1F64F}^\x{1F680}-\x{1F6FF}^\x{1F910}-\x{1F96B}^\x{1F980}-\x{1F9E0}]{1,32})#`)
_urlReg = regexp.MustCompile(`(((http:\/\/|https:\/\/)[a-z0-9A-Z]+\.(bilibili|biligame)\.com[a-z0-9A-Z\/\.\$\*\?~=#!%@&-]*)|((http:\/\/|https:\/\/)(acg|b23)\.tv[a-z0-9A-Z\/\.\$\*\?~=#!@&]*))`)
_avReg = regexp.MustCompile(`#(cv\d+)|#(av\d+)|#(vc\d+)`)
searchHTTPClient *xhttp.Client
errReplyContentNotFound = errors.New("reply content not found")
)
const (
_appIDReply = "reply"
_appIDReport = "replyreport"
timeFormat = "2006-01-02 15:03:04"
// event
_eventReply = "reply"
_eventHate = "hate"
_eventLike = "like"
_eventLikeCancel = "like_cancel"
_eventHateCancel = "hate_cancel"
)
func (s *Service) beginTran(c context.Context) (*xsql.Tx, error) {
return s.dao.BeginTran(c)
}
func (s *Service) actionAdd(c context.Context, msg *consumerMsg) {
var rp *model.Reply
if err := json.Unmarshal([]byte(msg.Data), &rp); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
if rp.RpID == 0 || rp.Oid == 0 || rp.Content == nil {
log.Error("The structure of reply(%s) from rpCh was wrong", msg.Data)
return
}
if rp.Root == 0 && rp.Parent == 0 {
s.addReply(c, rp)
} else {
s.addReplyReply(c, rp)
}
}
func (s *Service) tranAdd(c context.Context, rp *model.Reply, is bool) (err error) {
tx, err := s.beginTran(c)
if err != nil {
log.Error("reply(%s) beginTran error(%v)", rp, err)
return
}
var rows int64
defer func() {
if err == nil && rows == 0 {
err = errors.New("sql: transaction add reply failed")
}
}()
if is {
if rp.IsNormal() {
rows, err = s.dao.Subject.TxIncrCount(tx, rp.Oid, rp.Type, rp.CTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Subject.TxIncrCount(%v) error(%v) or rows==0", rp, err)
return
}
} else {
rows, err = s.dao.Subject.TxIncrFCount(tx, rp.Oid, rp.Type, rp.CTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Subject.TxIncrCount(%v) error(%v) or rows==0", rp, err)
return
}
}
} else {
var rootReply *model.Reply
if rootReply, err = s.dao.Reply.GetForUpdate(tx, rp.Oid, rp.Root); err != nil {
tx.Rollback()
return err
}
if rootReply.IsDeleted() {
return fmt.Errorf("the root reply is deleted(%d,%d,%d)", rp.Oid, rp.Type, rp.Root)
}
if rp.IsNormal() {
rows, err = s.dao.Reply.TxIncrCount(tx, rp.Oid, rp.Root, rp.CTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Reply.TxIncrCount(%v) error(%v) or rows==0", rp, err)
return
}
rows, err = s.dao.Subject.TxIncrACount(tx, rp.Oid, rp.Type, 1, rp.CTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Subject.TxIncrACount(%v) error(%v) or rows==0", rp, err)
return
}
} else {
rows, err = s.dao.Reply.TxIncrFCount(tx, rp.Oid, rp.Root, rp.CTime.Time())
if err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Reply.TxIncrCount(%v) error(%v) or rows==0", rp, err)
return
}
}
}
if rp.State == model.ReplyStateAudit || rp.State == model.ReplyStateMonitor {
if rows, err = s.dao.Subject.TxIncrMCount(tx, rp.Oid, rp.Type, rp.CTime.Time()); err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Subject.TxIncrMCount(%v) error(%v) or rows==0", rp, err)
return
}
}
rows, err = s.dao.Content.TxInsert(tx, rp.Oid, rp.Content)
if err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Content.TxInContent(%v) error(%v) or rows==0", rp, err)
return
}
rows, err = s.dao.Reply.TxInsert(tx, rp)
if err != nil || rows == 0 {
tx.Rollback()
log.Error("dao.Reply.TxInReply(%v) error(%v) or rows==0", rp, err)
return
}
return tx.Commit()
}
func (s *Service) regTopic(c context.Context, msg string) (topics []string) {
msg = _urlReg.ReplaceAllString(msg, "")
msg = _avReg.ReplaceAllString(msg, "#")
ss := _topicReg.FindAllStringSubmatch(msg, -1)
if len(ss) == 0 {
return
}
for _, nns := range ss {
if len(nns) == 2 {
topic := strings.TrimSpace(nns[1])
if len(topic) > 0 {
topics = append(topics, topic)
}
}
if len(topics) >= 5 {
break
}
}
return
}
func (s *Service) regAt(c context.Context, msg string, over, self int64) (ats []int64) {
var err error
ss := _atReg.FindAllStringSubmatch(msg, 10)
if len(ss) == 0 {
return
}
names := make([]string, 0, len(ss))
for _, nns := range ss {
if len(nns) == 2 {
names = append(names, nns[1])
}
}
if len(names) == 0 {
return
}
us, err := s.accSrv.InfosByName3(c, &accmdl.NamesReq{Names: names})
if err != nil {
log.Error("s.accSrv.InfosByName2 failed, err(%v)", err)
return
}
ats = make([]int64, 0, len(us.Infos))
for mid := range us.Infos {
if mid != over && mid != self {
ats = append(ats, mid)
}
}
if len(ats) == 0 {
return
}
ats = s.getFilterBlacklist(c, self, ats)
return
}
func (s *Service) addReply(c context.Context, rp *model.Reply) {
var (
err error
ok bool
)
sub, err := s.getSubject(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.getSubject failed , oid(%d,%d) err(%v)", rp.Oid, rp.Type, err)
return
}
if sub == nil {
log.Error("get subject is nil oid(%d) type(%d)", rp.Oid, rp.Type)
return
}
// init some field
if rp.IsNormal() {
sub.RCount = sub.RCount + 1
sub.ACount = sub.ACount + 1
}
sub.Count = sub.Count + 1
rp.Floor = sub.Count
rp.MTime = rp.CTime
rp.Content.RpID = rp.RpID
rp.Content.CTime = rp.CTime
rp.Content.MTime = rp.MTime
if len(rp.Content.Ats) == 0 {
rp.Content.Ats = s.regAt(c, rp.Content.Message, 0, rp.Mid)
}
rp.Content.Topics = s.regTopic(c, rp.Content.Message)
// begin transaction
if err = s.tranAdd(c, rp, true); err != nil {
log.Error("Transaction add reply(%v) error(%v)", rp, err)
return
}
// add cache
if err = s.dao.Mc.AddSubject(c, sub); err != nil {
log.Error("s.dao.Mc.AddSubject failed , oid(%d) err(%v)", sub.Oid, err)
}
if err = s.dao.Mc.AddReply(c, rp); err != nil {
log.Error("s.dao.Mc.AddReply failed , RpID(%d) err(%v)", rp.RpID, err)
}
if rp.IsNormal() {
// update reply count
s.upAcount(c, sub.Oid, sub.Type, sub.ACount, rp.CTime.Time())
// add index cache
if ok, err = s.dao.Redis.ExpireIndex(c, sub.Oid, sub.Type, model.SortByFloor); err == nil && ok {
if err = s.dao.Redis.AddFloorIndex(c, sub.Oid, sub.Type, rp); err != nil {
log.Error("s.dao.Redis.AddFloorIndex failed , oid(%d) type(%d) err(%v)", sub.Oid, sub.Type, err)
}
}
if ok, err = s.dao.Redis.ExpireIndex(c, sub.Oid, sub.Type, model.SortByCount); err == nil && ok {
if err = s.dao.Redis.AddCountIndex(c, sub.Oid, sub.Type, rp); err != nil {
log.Error("s.dao.Redis.AddCountIndex failed , oid(%d) type(%d) err(%v)", sub.Oid, sub.Type, err)
}
}
if ok, err = s.dao.Redis.ExpireIndex(c, sub.Oid, sub.Type, model.SortByLike); err == nil && ok {
rpts := make(map[int64]*reply.Report, 1)
if rpt, _ := s.dao.Report.Get(c, rp.Oid, rp.RpID); rpt != nil {
rpts[rp.RpID] = rpt
}
if err = s.dao.Redis.AddLikeIndex(c, sub.Oid, sub.Type, rpts, rp); err != nil {
log.Error("s.dao.Redis.AddLikeIndex failed , oid(%d) type(%d) err(%v)", sub.Oid, sub.Type, err)
}
}
s.notifyReply(c, sub, rp)
} else if rp.State == model.ReplyStateAudit {
if err = s.dao.Redis.AddAuditIndex(c, rp); err != nil {
log.Error("s.dao.Redis.AddAUditIndex(%d,%d,%d) error(%v)", rp.Oid, rp.RpID, rp.Type, err)
}
}
if err = s.dao.PubEvent(c, _eventReply, rp.Mid, sub, rp, nil); err != nil {
return
}
}
func (s *Service) addReplyReply(c context.Context, rp *model.Reply) {
var (
err error
ok bool
)
sub, err := s.getSubject(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.getSubject failed , oid(%d,%d) err(%v)", rp.Oid, rp.Type, err)
return
}
// NOTE:depend on db,do not get from cache
rootRp, err := s.getReply(c, rp.Oid, rp.Root)
if err != nil {
log.Error("s.getReply failed , oid(%d), root(%d) err(%v)", rp.Oid, rp.Root, err)
return
}
if rootRp == nil {
log.Error("get reply is nil oid(%d) type(%d) rpid(%d)", rp.Oid, rp.Type, rp.Root)
return
}
var parentRp *model.Reply
if rp.Root != rp.Parent {
parentRp, err = s.getReply(c, rp.Oid, rp.Parent)
if err != nil {
log.Error("s.getReply failed , oid(%d), parent(%d) err(%v)", rp.Oid, rp.Parent, err)
return
}
if parentRp == nil {
log.Error("get reply is nil oid(%d) type(%d) rpid(%d)", rp.Oid, rp.Type, rp.Parent)
return
}
if parentRp.Dialog == 0 {
log.Warn("Dialog Need Migration oid(%d) type(%d) rootID(%d)", rp.Oid, rp.Type, rootRp.RpID)
// s.setDialogByRoot(context.Background(), rp.Oid, rp.Type, rp.Root)
}
rp.Dialog = parentRp.Dialog
} else {
parentRp = rootRp
if rp.Dialog != rp.RpID {
rp.Dialog = rp.RpID
}
}
// init some field
if rp.IsNormal() {
sub.ACount = sub.ACount + 1
rootRp.RCount = rootRp.RCount + 1
}
rootRp.Count = rootRp.Count + 1
rootRp.MTime = rp.CTime
rp.Floor = rootRp.Count
rp.MTime = rp.CTime
rp.Content.RpID = rp.RpID
rp.Content.CTime = rp.CTime
rp.Content.MTime = rp.MTime
if len(rp.Content.Ats) == 0 {
rp.Content.Ats = s.regAt(c, rp.Content.Message, 0, rp.Mid)
}
rp.Content.Topics = s.regTopic(c, rp.Content.Message)
// begin transaction
if err = s.tranAdd(c, rp, false); err != nil {
log.Error("Transaction add reply(%v) error(%v)", rp, err)
return
}
// add cache
if err = s.dao.Mc.AddSubject(c, sub); err != nil {
log.Error("s.dao.Mc.AddSubject failed , oid(%d), err(%v)", sub.Oid, err)
}
if err = s.dao.Mc.AddReply(c, rp); err != nil {
log.Error("s.dao.Mc.AddReply failed , RpID(%d), err(%v)", rp.RpID, err)
}
if err = s.dao.Mc.AddReply(c, rootRp); err != nil {
log.Error("s.dao.Mc.AddReply failed , RpID(%d), err(%v)", rootRp.RpID, err)
}
if rootRp.IsTop() {
if err = s.dao.Mc.AddTop(c, rootRp); err != nil {
log.Error("s.dao.Mc.AddReply failed , RpID(%d), err(%v)", rootRp.RpID, err)
}
} else if rootRp.IsNormal() {
if ok, err = s.dao.Redis.ExpireIndex(c, rp.Oid, rp.Type, model.SortByCount); err == nil && ok {
s.dao.Redis.AddCountIndex(c, rp.Oid, rp.Type, rootRp)
}
}
if rp.IsNormal() {
// update reply count
s.upAcount(c, sub.Oid, sub.Type, sub.ACount, rp.CTime.Time())
// add index cache
if ok, err = s.dao.Redis.ExpireNewChildIndex(c, rootRp.RpID); err == nil && ok {
if err = s.dao.Redis.AddNewChildIndex(c, rootRp.RpID, rp); err != nil {
log.Error("s.dao.Redis.AddFloorIndexByRoot failed , RpID(%d), err(%v)", rootRp.RpID, err)
}
}
// add dialog cache
if rp.Dialog != 0 {
if ok, err = s.dao.Redis.ExpireDialogIndex(c, rp.Dialog); err == nil && ok {
rps := []*model.Reply{rp}
if err = s.dao.Redis.AddDialogIndex(c, rp.Dialog, rps); err != nil {
log.Error("s.dao.Redis.AddDialogIndex failed , RpID(%d), Dialog(%d), Floor(%d) err(%v)", rp.RpID, rp.Dialog, rp.Floor, err)
}
}
}
s.notifyReplyReply(c, sub, rootRp, parentRp, rp)
} else if rp.State == model.ReplyStateAudit {
if err = s.dao.Redis.AddAuditIndex(c, rp); err != nil {
log.Error("s.dao.Redis.AddAUditIndex(%d,%d,%d) error(%v)", rp.Oid, rp.RpID, rp.Type, err)
}
}
if err = s.dao.PubEvent(c, _eventReply, rp.Mid, sub, rp, nil); err != nil {
return
}
}
func (s *Service) addTopCache(c context.Context, msg *consumerMsg) {
var (
err error
sub *model.Subject
rp *model.Reply
)
var d struct {
Oid int64 `json:"oid"`
Tp int8 `json:"tp"`
Top uint32 `json:"top"`
}
if err = json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
if rp, err = s.dao.Mc.GetTop(c, d.Oid, d.Tp, d.Top); err != nil {
log.Error("s.dao.Mc.GetTop(oid %v,top %v) err(%v)", d.Oid, d.Top, err)
return
} else if rp == nil {
if rp, err = s.dao.Reply.GetTop(c, d.Oid, d.Tp, d.Top); err != nil || rp == nil {
log.Error("s.dao.Reply.GetTop(%d, %d) error(%v)", d.Oid, d.Tp, err)
return
}
if rp.Content, err = s.dao.Content.Get(c, d.Oid, rp.RpID); err != nil {
return
}
s.dao.Mc.AddTop(c, rp)
sub, err = s.dao.Subject.Get(c, d.Oid, d.Tp)
if err != nil {
log.Error("s.dao.Subject.Get(%d, %d) error(%v)", d.Oid, d.Tp, err)
return
}
err = sub.TopSet(rp.RpID, d.Top, 1)
if err != nil {
return
}
_, 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 ", rp.RpID, rp.Oid, d.Tp, err)
return
}
s.dao.Mc.AddSubject(c, sub)
}
}
func (s *Service) actionRpt(c context.Context, msg *consumerMsg) {
var (
err error
ok bool
)
var d struct {
Oid int64 `json:"oid"`
RpID int64 `json:"rpid"`
Tp int8 `json:"tp"`
}
if err = json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
rp, err := s.getReplyCache(c, d.Oid, d.RpID)
if err != nil {
log.Error("s.getReply failed , oid(%d), RpID(%d) err(%v)", d.Oid, d.RpID, err)
return
}
if rp == nil {
return
}
sub, err := s.getSubject(c, d.Oid, d.Tp)
if err != nil || sub == nil {
log.Error("s.getSubject failed , oid(%d),tp(%d), RpID(%d) err(%v)", d.Oid, d.Tp, d.RpID, err)
return
}
// update like index
if rp.Root == 0 && rp.Parent == 0 && !rp.IsDeleted() {
if ok, err = s.dao.Redis.ExpireIndex(c, d.Oid, rp.Type, model.SortByLike); err == nil && ok {
rpts := make(map[int64]*reply.Report, 1)
if rpt, _ := s.dao.Report.Get(c, rp.Oid, rp.RpID); rpt != nil {
rpts[rp.RpID] = rpt
}
if err = s.dao.Redis.AddLikeIndex(c, d.Oid, rp.Type, rpts, rp); err != nil {
log.Error("s.dao.Redis.AddLikeIndex(%d, %d) error(%v)", d.Oid, rp.Type, err)
}
}
}
report, err := s.dao.Report.Get(c, d.Oid, d.RpID)
if err != nil || report == nil {
log.Error("dao.Report.GetReport(%d, %d) met error (%v)", rp.Oid, rp.RpID, err)
return
}
if err = s.dao.PubEvent(c, _eventReportAdd, report.Mid, sub, rp, report); err != nil {
return
}
}
func (s *Service) setLike(c context.Context, cmsg *StatMsg) {
var (
event string
)
rp, err := s.getReply(c, cmsg.Oid, cmsg.ID)
if err != nil || rp == nil || rp.Content == nil {
log.Error("s.getReply(%d, %d) reply:%+v error(%v)", cmsg.Oid, cmsg.ID, rp, err)
return
}
sub, err := s.getSubject(c, rp.Oid, rp.Type)
if err != nil || sub == nil {
log.Error("s.getSubject failed , oid(%d) type(%d) err(%v)", rp.Oid, rp.Type, err)
return
}
_, err = s.dao.Reply.UpLike(c, cmsg.Oid, cmsg.ID, cmsg.Count, cmsg.DislikeCount, time.Now())
if err != nil {
log.Error("s.dao.Reply.UpLike (%v) failed!err:=%v", cmsg, err)
return
}
if cmsg.Count > rp.Like {
event = _eventLike
var max int
if max, err = s.dao.Redis.MaxLikeCnt(c, rp.RpID); err == nil && cmsg.Count > max {
if err = s.dao.Redis.SetMaxLikeCnt(c, rp.RpID, int64(cmsg.Count)); err == nil {
rp.Like = cmsg.Count
rp.Hate = cmsg.DislikeCount
s.notifyLike(c, cmsg.Mid, rp)
}
}
} else if cmsg.DislikeCount > rp.Hate {
event = _eventHate
} else if cmsg.Count < rp.Like {
event = _eventLikeCancel
} else {
event = _eventHateCancel
}
rp.Like = cmsg.Count
rp.Hate = cmsg.DislikeCount
s.dao.Mc.AddReply(c, rp)
if rp.AttrVal(model.ReplyAttrAdminTop) == 1 || rp.AttrVal(model.ReplyAttrUpperTop) == 1 {
s.dao.Mc.AddTop(c, rp)
return
}
// if have root, then update root's index
if rp.Root == 0 && rp.IsNormal() {
var ok bool
if ok, err = s.dao.Redis.ExpireIndex(c, rp.Oid, rp.Type, model.SortByLike); err == nil && ok {
rpts := make(map[int64]*reply.Report, 1)
if rpt, _ := s.dao.Report.Get(c, rp.Oid, rp.RpID); rpt != nil {
rpts[rp.RpID] = rpt
}
if err = s.dao.Redis.AddLikeIndex(c, rp.Oid, rp.Type, rpts, rp); err != nil {
log.Error("s.dao.Redis.AddLikeIndex(%d, %d) error(%v)", rp.Oid, rp.Type, err)
}
}
}
if err = s.dao.PubEvent(c, event, cmsg.Mid, sub, rp, nil); err != nil {
return
}
}
func (s *Service) adminLog(c context.Context, rp *model.Reply, adid int64, isreport, state int8, result, remark string) {
// admin log
s.dao.Admin.UpIsNotNew(c, rp.RpID, time.Now())
s.dao.Admin.Insert(c, adid, rp.Oid, rp.RpID, rp.Type, result, remark, model.AdminIsNew, isreport, state, time.Now())
}
// getSubject get reply subject from mysql .
// NOTE : note get from mc,count must depend on mysql
func (s *Service) getSubject(c context.Context, oid int64, tp int8) (sub *model.Subject, err error) {
if sub, err = s.dao.Subject.Get(c, oid, tp); err != nil {
log.Error("dao.Subject.Get(%d, %d) error(%v)", oid, tp, err)
}
return
}
func (s *Service) getReply(c context.Context, oid, RpID int64) (rp *model.Reply, err error) {
if rp, err = s.dao.Reply.Get(c, oid, RpID); err != nil {
log.Error("s.dao.Reply.Get(%d, %d) error(%v)", oid, RpID, err)
return
} else if rp == nil {
return
}
if rp.Content, err = s.dao.Content.Get(c, rp.Oid, rp.RpID); err != nil {
log.Error("s.dao.Content.Get(%d,%d) error(%v)", rp.Oid, rp.RpID, err)
} else if rp.Content == nil {
err = errReplyContentNotFound
}
return
}
func (s *Service) getReplyCache(c context.Context, oid, RpID int64) (rp *model.Reply, err error) {
if rp, err = s.dao.Mc.GetReply(c, RpID); err != nil {
log.Error("replyCacheDao.GetReply(%d, %d) error(%v)", oid, RpID, err)
}
if rp != nil {
return
}
if rp, err = s.dao.Reply.Get(c, oid, RpID); err != nil {
log.Error("dao.Reply.GetReply(%d, %d) error(%v)", oid, RpID, err)
}
if rp != nil {
rp.Content, _ = s.dao.Content.Get(c, rp.Oid, rp.RpID)
// NOTE not add member info to cache
}
return
}
func (s *Service) upAcount(c context.Context, oid int64, tp int8, count int, now time.Time) {
s.statDao.Send(c, tp, oid, count)
}
func (s *Service) callSearchUp(c context.Context, res map[string]*searchFlush) (err error) {
var (
rps []*searchFlush
rpts []*searchFlush
)
for _, r := range res {
if r.Report != nil {
rpts = append(rpts, r)
} else {
rps = append(rps, r)
}
}
if len(rps) > 0 {
err = s.callSearch(c, rps, false)
}
if len(rpts) > 0 {
err = s.callSearch(c, rpts, true)
}
return
}
// callSearch update reply or report info to ES search.
func (s *Service) callSearch(c context.Context, params []*searchFlush, isRpt bool) (err error) {
var (
b []byte
ms []map[string]interface{}
p = url.Values{}
urlStr = conf.Conf.Host.Search + "/api/reply/internal/update"
res struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
)
// 更新搜索ES数据字段
if isRpt {
// report
p.Set("appid", _appIDReport)
for _, p := range params {
m := make(map[string]interface{})
m["id"] = fmt.Sprintf("%d_%d_%d", p.Reply.RpID, p.Reply.Oid, p.Reply.Type)
m["reply_state"] = fmt.Sprintf("%d", p.Reply.State)
m["reason"] = fmt.Sprintf("%d", p.Report.Reason)
m["content"] = p.Report.Content
m["state"] = fmt.Sprintf("%d", p.Report.State)
m["mtime"] = p.Report.MTime.Time().Format(timeFormat)
m["index_time"] = p.Report.CTime.Time().Format(timeFormat)
if p.Report.Attr == 1 {
m["attr"] = []int{1}
} else {
m["attr"] = []int{}
}
ms = append(ms, m)
}
if b, err = json.Marshal(ms); err != nil {
log.Error("json.Marshal(%v) error(%v)", ms, err)
return
}
p.Set("val", string(b))
if err = searchHTTPClient.Post(c, urlStr, "", p, &res); err != nil {
log.Error("xhttp.Post(%s) failed error(%v)", urlStr+"?"+p.Encode(), err)
}
log.Info("updateSearch: %s post:%s ret:%v", urlStr, p.Encode(), res)
} else {
// reply
var rps = make(map[int64]*model.Reply)
for _, p := range params {
rps[p.Reply.RpID] = p.Reply
}
err = s.UpSearchReply(c, rps)
}
return
}
// UpSearchReply update search reply index.
func (s *Service) UpSearchReply(c context.Context, rps map[int64]*model.Reply) (err error) {
if len(rps) <= 0 {
return
}
stales := s.es.NewUpdate("reply_list")
for _, rp := range rps {
m := make(map[string]interface{})
m["id"] = rp.RpID
m["state"] = rp.State
m["mtime"] = rp.MTime.Time().Format("2006-01-02 15:04:05")
m["oid"] = rp.Oid
m["type"] = rp.Type
if rp.Content != nil {
m["message"] = rp.Content.Message
}
stales = stales.AddData(s.es.NewUpdate("reply_list").IndexByTime("reply_list", elastic.IndexTypeWeek, rp.CTime.Time()), m)
}
err = stales.Do(c)
if err != nil {
log.Error("upSearchReply update stales(%s) failed!err:=%v", stales.Params(), err)
return
}
log.Info("upSearchReply:stale:%s ret:%+v", stales.Params(), err)
return
}
// getBlackListRelation check if the source user blacklisted the target user
func (s *Service) getBlackListRelation(c context.Context, srcID, targetID int64) (rel bool) {
relMap, err := s.accSrv.RichRelations3(c, &accmdl.RichRelationReq{Owner: srcID, Mids: []int64{targetID}, RealIp: ""})
if err != nil {
log.Error("s.acc.RichRelations2 sourceId(%v) targetId(%v)error(%v)", srcID, targetID, err)
err = nil
return false
}
if len(relMap.RichRelations) == 0 {
return false
}
if rel, ok := relMap.RichRelations[targetID]; ok && relmdl.Attr(uint32(rel)) == relmdl.AttrBlack {
return true
}
return false
}
// getFilterBlacklist filters the user list that the mid user can notify message for
func (s *Service) getFilterBlacklist(c context.Context, mid int64, targetIds []int64) (filterIds []int64) {
filterIds = make([]int64, 0, len(targetIds))
for _, tmp := range targetIds {
if !s.getBlackListRelation(c, tmp, mid) {
filterIds = append(filterIds, tmp)
}
}
return
}
func (s *Service) addAssistLog(c context.Context, mid, uid, subjectID, typeID, action int64, objectID, content string) (err error) {
if len(content) > 50 {
content = substr2(content, 0, 50) + "..."
}
arg := &assmdl.ArgAssistLogAdd{
Mid: mid,
AssistMid: uid,
Type: 1,
Action: 1,
SubjectID: subjectID,
ObjectID: objectID,
Detail: content,
RealIP: "",
}
if err = s.assistSrv.AssistLogAdd(c, arg); err != nil {
log.Error("s.assistSrv.Assist(%d, %d, %d, %d, %d) error(%v)", mid, uid, subjectID, typeID, action, err)
}
return
}
func substr2(str string, start int, subLength int) string {
rs := []rune(str)
length := len(rs)
if start < 0 || start > length {
start = 0
}
if subLength < 0 || subLength > length {
subLength = length
}
return string(rs[start:subLength])
}

View File

@@ -0,0 +1,71 @@
package service
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestFilterViolationMsg(t *testing.T) {
Convey("TestFilterViolationMsg", t, func() {
res := filterViolationMsg("123456789评论过虑违规内容评论过虑违规内容")
t.Log(res)
})
}
func TestTopicReg(t *testing.T) {
s := Service{}
c := context.Background()
Convey("TestAtReg", t, func() {
topics := s.regTopic(c, "#你懂 得##222#")
So(len(topics), ShouldEqual, 2)
So(topics[0], ShouldEqual, "你懂 得")
So(topics[1], ShouldEqual, "222")
topics = s.regTopic(c, "#你懂 \n得##22@有人艾特2#")
So(len(topics), ShouldEqual, 0)
topics = s.regTopic(c, "#你懂 \n得#哈哈哈#22@有人艾特2#")
So(len(topics), ShouldEqual, 1)
So(topics[0], ShouldEqual, "哈哈哈")
topics = s.regTopic(c, "# ## ##你懂得")
So(len(topics), ShouldEqual, 0)
topics = s.regTopic(c, "热热# ##%……&**&*……&……%……¥%##同一套##协助特大号哈哈哈嘎嘎协助特大号哈哈哈嘎嘎协助特大号哈哈哈ee120##协助特大号哈哈哈嘎嘎协助特大号哈哈哈嘎嘎协助特大号哈哈哈ee12##@1r##tet##899##5677#")
So(len(topics), ShouldEqual, 5)
topics = s.regTopic(c, "#我是大佬你是谁你是大佬嘛哈哈啊#123#")
So(len(topics), ShouldEqual, 1)
topics = s.regTopic(c, "#2😁3#123#3😁3##2😁3#")
So(len(topics), ShouldEqual, 1)
So(topics[0], ShouldEqual, "123")
topics = s.regTopic(c, " http://t.bilibili.com/av111111#reply#haha #didi")
So(len(topics), ShouldEqual, 0)
topics = s.regTopic(c, " http://t.bilibili.com/av111111#reply#haha #didi# http://t.baidu.com/av111111#reply#haha")
So(len(topics), ShouldEqual, 2)
So(topics[0], ShouldEqual, "didi")
So(topics[1], ShouldEqual, "reply")
topics = s.regTopic(c, "asdasd#av1000#33333#vc11111#44444#cv1111#55555#")
So(len(topics), ShouldEqual, 3)
})
}
func TestAtReg(t *testing.T) {
Convey("TestAtReg", t, func() {
ss := _atReg.FindAllStringSubmatch("@aa:hh@bb,cc", 10)
So(len(ss), ShouldEqual, 2)
So(ss[0][1], ShouldEqual, "aa")
So(ss[1][1], ShouldEqual, "bb")
ss = _atReg.FindAllStringSubmatch("@aa@bb", 10)
So(len(ss), ShouldEqual, 2)
So(ss[0][1], ShouldEqual, "aa")
So(ss[1][1], ShouldEqual, "bb")
ss = _atReg.FindAllStringSubmatch("@aa @bb", 10)
So(len(ss), ShouldEqual, 2)
So(ss[0][1], ShouldEqual, "aa")
So(ss[1][1], ShouldEqual, "bb")
ss = _atReg.FindAllStringSubmatch("@aa bb@cc;@dd:sa", 10)
So(len(ss), ShouldEqual, 3)
So(ss[0][1], ShouldEqual, "aa")
So(ss[1][1], ShouldEqual, "cc;")
So(ss[2][1], ShouldEqual, "dd")
})
}

View File

@@ -0,0 +1,366 @@
package service
import (
"context"
"encoding/json"
"fmt"
"strconv"
"sync"
"time"
artrpc "go-common/app/interface/openplatform/article/rpc/client"
"go-common/app/job/main/reply/conf"
"go-common/app/job/main/reply/dao/message"
"go-common/app/job/main/reply/dao/notice"
"go-common/app/job/main/reply/dao/reply"
"go-common/app/job/main/reply/dao/search"
"go-common/app/job/main/reply/dao/spam"
"go-common/app/job/main/reply/dao/stat"
model "go-common/app/job/main/reply/model/reply"
accrpc "go-common/app/service/main/account/api"
arcrpc "go-common/app/service/main/archive/api/gorpc"
assrpc "go-common/app/service/main/assist/rpc/client"
eprpc "go-common/app/service/openplatform/pgc-season/api/grpc/episode/v1"
es "go-common/library/database/elastic"
"go-common/library/log"
xhttp "go-common/library/net/http/blademaster"
"go-common/library/net/rpc/warden"
"go-common/library/queue/databus"
"go-common/library/sync/pipeline/fanout"
)
const (
_chLen = 2048
)
var (
_rpChs []chan *databus.Message
_likeChs []chan *databus.Message
)
// action the message struct of kafka
type consumerMsg struct {
Action string `json:"action"`
Data json.RawMessage `json:"data"`
}
type searchFlush struct {
OldState int8
Reply *model.Reply
Report *model.Report
}
func (s *searchFlush) Key() (key string) {
if s.Report != nil {
return fmt.Sprintf("%d%d", s.Report.RpID, s.Report.ID)
}
return fmt.Sprintf("%d", s.Reply.RpID)
}
// Service is reply-job service
type Service struct {
c *conf.Config
waiter *sync.WaitGroup
dataConsumer *databus.Databus
likeConsumer *databus.Databus
searchChan chan *searchFlush
// rpc client
accSrv accrpc.AccountClient
arcSrv *arcrpc.Service2
articleSrv *artrpc.Service
assistSrv *assrpc.Service
bangumiSrv eprpc.EpisodeClient
// depend
messageDao *message.Dao
// notice
noticeDao *notice.Dao
// stat
statDao *stat.Dao
// reply
dao *reply.Dao
// spam
spam *spam.Cache
// search
searchDao *search.Dao
batchNumber int
es *es.Elastic
notify *fanout.Fanout
typeMapping map[int32]string
aliasMapping map[string]int32
marker *fanout.Fanout
}
// New return new service
func New(c *conf.Config) (s *Service) {
if c.Job.BatchNumber <= 0 {
c.Job.BatchNumber = 2000
}
searchHTTPClient = xhttp.NewClient(c.HTTPClient)
wardenClient := warden.DefaultClient()
cc, err := wardenClient.Dial(context.Background(), "discovery://default/season.service")
if err != nil {
panic(err)
}
bangumiClient := eprpc.NewEpisodeClient(cc)
s = &Service{
c: c,
bangumiSrv: bangumiClient,
waiter: new(sync.WaitGroup),
searchChan: make(chan *searchFlush, 1024),
dataConsumer: databus.New(c.Databus.Consumer),
likeConsumer: databus.New(c.Databus.Like),
//rpc
arcSrv: arcrpc.New2(c.RPCClient2.Archive),
articleSrv: artrpc.New(c.RPCClient2.Article),
assistSrv: assrpc.New(c.RPCClient2.Assist),
messageDao: message.NewMessageDao(c),
searchDao: search.New(c),
noticeDao: notice.New(c),
// stat
statDao: stat.New(c),
// init reply dao
dao: reply.New(c),
// init spam cache
batchNumber: c.Job.BatchNumber,
spam: spam.NewCache(c.Redis.Config),
notify: fanout.New("cache", fanout.Worker(1), fanout.Buffer(2048)),
typeMapping: make(map[int32]string),
aliasMapping: make(map[string]int32),
es: es.NewElastic(c.Es),
marker: fanout.New("marker", fanout.Worker(1), fanout.Buffer(1024)),
}
accSvc, err := accrpc.NewClient(c.AccountClient)
if err != nil {
panic(err)
}
s.accSrv = accSvc
time.Sleep(time.Second)
_rpChs = make([]chan *databus.Message, c.Job.Proc)
_likeChs = make([]chan *databus.Message, c.Job.Proc)
for i := 0; i < c.Job.Proc; i++ {
_rpChs[i] = make(chan *databus.Message, _chLen)
_likeChs[i] = make(chan *databus.Message, _chLen)
s.waiter.Add(1)
go s.consumeproc(i)
s.waiter.Add(1)
go s.consumelikeproc(i)
}
s.waiter.Add(1)
go s.likeConsume()
s.waiter.Add(1)
go s.dataConsume()
go s.searchproc()
go s.mappingproc()
return
}
func (s *Service) addSearchUp(c context.Context, oldState int8, rp *model.Reply, rpt *model.Report) {
select {
case s.searchChan <- &searchFlush{OldState: oldState, Reply: rp, Report: rpt}:
default:
log.Error("addSearchUp chan full, type:%d oid:%d rpID:%d", rp.Type, rp.Oid, rp.RpID)
}
}
func (s *Service) searchproc() {
var (
m *searchFlush
merge = make(map[string]*searchFlush)
num = s.c.Job.SearchNum
ticker = time.NewTicker(time.Duration(s.c.Job.SearchFlush))
)
for {
select {
case m = <-s.searchChan:
merge[m.Key()] = m
if len(merge) < num {
continue
}
case <-ticker.C:
}
if len(merge) > 0 {
s.callSearchUp(context.Background(), merge)
merge = make(map[string]*searchFlush)
}
}
}
func (s *Service) likeConsume() {
defer func() {
s.waiter.Done()
for i := 0; i < s.c.Job.Proc; i++ {
close(_rpChs[i])
}
}()
msgs := s.likeConsumer.Messages()
for {
msg, ok := <-msgs
if !ok {
log.Warn("[service.dataConsume|reply] dataConsumer has been closed.")
return
}
if msg.Topic != s.c.Databus.Like.Topic {
continue
}
rpid, err := strconv.ParseInt(string(msg.Key), 10, 64)
if err != nil {
continue
}
_likeChs[rpid%int64(s.c.Job.Proc)] <- msg
}
}
func (s *Service) dataConsume() {
defer func() {
s.waiter.Done()
for i := 0; i < s.c.Job.Proc; i++ {
close(_rpChs[i])
}
}()
msgs := s.dataConsumer.Messages()
for {
msg, ok := <-msgs
if !ok {
log.Warn("[service.dataConsume|reply] dataConsumer has been closed.")
return
}
if msg.Topic != s.c.Databus.Consumer.Topic {
continue
}
oid, err := strconv.ParseInt(string(msg.Key), 10, 64)
if err != nil {
continue
}
_rpChs[oid%int64(s.c.Job.Proc)] <- msg
}
}
// StatMsg stat msg.
type StatMsg struct {
Type string `json:"type,omitempty"`
ID int64 `json:"id,omitempty"`
Count int `json:"count,omitempty"`
Oid int64 `json:"origin_id,omitempty"`
DislikeCount int `json:"dislike_count,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
Mid int64 `json:"mid,omitempty"`
}
func (s *Service) consumelikeproc(i int) {
defer s.waiter.Done()
for {
msg, ok := <-_likeChs[i]
if !ok {
log.Info("consumeproc exit")
return
}
cmsg := &StatMsg{}
if err := json.Unmarshal(msg.Value, cmsg); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
continue
}
if cmsg.Type != "reply" {
continue
}
s.setLike(context.Background(), cmsg)
msg.Commit()
log.Info("consumer topic:%s, partitionId:%d, offset:%d, Key:%s, Value:%s", msg.Topic, msg.Partition, msg.Offset, msg.Key, msg.Value)
}
}
func (s *Service) consumeproc(i int) {
defer s.waiter.Done()
for {
msg, ok := <-_rpChs[i]
if !ok {
log.Info("consumeproc exit")
return
}
cmsg := &consumerMsg{}
if err := json.Unmarshal(msg.Value, cmsg); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
continue
}
switch cmsg.Action {
case "add":
s.actionAdd(context.Background(), cmsg)
case "add_top":
s.addTopCache(context.Background(), cmsg)
case "rpt":
s.actionRpt(context.Background(), cmsg)
case "act":
//s.actionAct(context.Background(), cmsg)
s.recAct(context.Background(), cmsg)
case "re_idx":
s.actionRecoverIndex(context.Background(), cmsg)
case "idx_floor":
s.acionRecoverFloorIdx(context.Background(), cmsg)
case "re_rt_idx":
s.actionRecoverRootIndex(context.Background(), cmsg)
case "idx_dialog":
s.actionRecoverDialog(context.Background(), cmsg)
case "fix_dialog":
s.actionRecoverFixDialog(context.Background(), cmsg)
case "re_act":
// s.actionRecoverAction(context.Background(),cmsg)
case "up":
s.actionUp(context.Background(), cmsg)
case "admin":
s.actionAdmin(context.Background(), cmsg)
case "spam":
s.addRecReply(context.Background(), cmsg)
s.addDailyReply(context.Background(), cmsg)
case "folder":
s.folderHanlder(context.Background(), cmsg)
default:
log.Error("invalid action %s, cmsg is %v", cmsg.Action, cmsg)
}
msg.Commit()
log.Info("consumer topic:%s, partitionId:%d, offset:%d, Key:%s, Value:%s", msg.Topic, msg.Partition, msg.Offset, msg.Key, msg.Value)
}
}
// TypeToAlias map type to alias
func (s *Service) TypeToAlias(t int32) (alias string, exists bool) {
alias, exists = s.typeMapping[t]
return
}
// AliasToType map alias to type
func (s *Service) AliasToType(alias string) (t int32, exists bool) {
t, exists = s.aliasMapping[alias]
return
}
func (s *Service) mappingproc() {
for {
if business, err := s.ListBusiness(context.Background()); err != nil {
log.Error("s.ListBusiness error(%v)", err)
} else {
for _, b := range business {
s.typeMapping[b.Type] = b.Alias
s.aliasMapping[b.Alias] = b.Type
}
}
time.Sleep(time.Duration(time.Minute * 5))
}
}
// Close close service
func (s *Service) Close() error {
return s.dataConsumer.Close()
}
// Wait wait all chan close
func (s *Service) Wait() {
s.waiter.Wait()
}
// Ping check service health
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}

View File

@@ -0,0 +1,50 @@
package service
import (
"context"
"flag"
"os"
"path/filepath"
"testing"
"go-common/app/job/main/reply/conf"
_ "github.com/go-sql-driver/mysql"
. "github.com/smartystreets/goconvey/convey"
)
var (
ser *Service
)
func CleanCache() {
}
func TestMain(m *testing.M) {
dir, _ := filepath.Abs("../cmd/reply-job-test.toml")
flag.Set("conf", dir)
err := conf.Init()
if err != nil {
panic("conf init err:" + err.Error())
}
ser = New(conf.Conf)
m.Run()
os.Exit(0)
}
func WithService(f func(ser *Service)) func() {
return func() {
Reset(func() { CleanCache() })
f(ser)
}
}
func TestClose(t *testing.T) {
err := ser.Close()
So(err, ShouldBeNil)
}
func TestPing(t *testing.T) {
err := ser.Ping(context.Background())
So(err, ShouldBeNil)
}

View File

@@ -0,0 +1,139 @@
package service
import (
"context"
"encoding/json"
accmdl "go-common/app/service/main/account/api"
"go-common/library/ecode"
"go-common/library/log"
)
type spamMessage struct {
Mid int64 `json:"mid"`
IsUp bool `json:"is_up"`
Tp int8 `json:"tp"`
}
func (s *Service) getLevel(c context.Context, mid int64) (cur int, err error) {
arg := &accmdl.MidReq{Mid: mid}
res, err := s.accSrv.Card3(c, arg)
if err != nil {
return
}
cur = int(res.Card.Level)
return
}
func (s *Service) addRecReply(c context.Context, msg *consumerMsg) {
var (
d spamMessage
exp int
code = ecode.OK
)
if err := json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
count, err := s.spam.IncrReply(c, d.Mid, d.IsUp)
if err != nil {
log.Error("spam.IncrReply(%d) error(%v)", d.Mid, err)
return
}
if d.IsUp && count >= 20 {
exp = 5 * 60 // 5min
code = ecode.ReplyDeniedAsCaptcha
} else if count >= 5 {
exp = 5 * 60 // 5min
code = ecode.ReplyDeniedAsCaptcha
}
if code == ecode.OK {
return
}
if err = s.spam.SetReplyRecSpam(c, d.Mid, code.Code(), exp); err == nil {
log.Info("spam.SetReplyRecSpam(%d, %d, %d)", d.Mid, code, exp)
} else {
log.Error("spam.SetReplyRecSpam(%d, %d, %d), err (%v)", d.Mid, code, exp, err)
}
}
func (s *Service) addDailyReply(c context.Context, msg *consumerMsg) {
var d spamMessage
if err := json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
ttl, err := s.spam.TTLDailyReply(c, d.Mid)
if err != nil {
log.Error("spam.TTLDailyReply(%d) error(%v)", d.Mid, err)
return
}
count, err := s.spam.IncrDailyReply(c, d.Mid)
if err != nil {
log.Error("spam.IncrDailyReply(%d) error(%v)", d.Mid, err)
return
}
if ttl == -2 || ttl == -1 {
ttl = 24 * 60 * 60 // one day
if err = s.spam.ExpireDailyReply(c, d.Mid, ttl); err != nil {
log.Error("spam.ExpireDailyReply(%d) error(%v)", d.Mid, err)
}
}
var code ecode.Codes
// 23 BBQ 22 火鸟
if d.Tp == 23 || d.Tp == 22 {
if count <= 1000 {
return
}
code = ecode.ReplyDeniedAsCD
} else {
lv, err := s.getLevel(c, d.Mid)
if err != nil {
log.Error("s.getLevel(%d) error(%v)", d.Mid, err)
return
}
switch {
case lv <= 1 && count < 25:
return
case lv == 2 && count < 250:
return
case lv == 3 && count < 300:
return
case lv == 4 && count < 400:
return
case lv >= 5 && count < 800:
return
}
code = ecode.ReplyDeniedAsCaptcha
if count >= 1000 {
code = ecode.ReplyDeniedAsCD
}
}
if err = s.spam.SetReplyDailySpam(c, d.Mid, code.Code(), ttl); err == nil {
log.Info("spam.SetReplyDailySpam(%d, %d, %d)", d.Mid, code, ttl)
} else {
log.Error("spam.SetReplyDailySpam(%d, %d, %d) error(%v)", d.Mid, code, ttl, err)
}
}
func (s *Service) recAct(c context.Context, cmsg *consumerMsg) {
const _exp = 60
var d spamMessage
if err := json.Unmarshal([]byte(cmsg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
count, err := s.spam.IncrAct(c, d.Mid)
if err != nil {
log.Error("spam.IncUserRecAct(%d) error(%v)", d.Mid, err)
return
}
if count < 15 {
return
}
if err = s.spam.SetActionRecSpam(c, d.Mid, ecode.ReplyForbidAction.Code(), _exp); err == nil {
log.Info("spam.SetActRecSpam(%d, %d, %d)", d.Mid, ecode.ReplyForbidAction, _exp)
} else {
log.Error("spam.SetActRecSpam(%d, %d, %d) error(%v)", d.Mid, ecode.ReplyForbidAction, _exp, err)
}
}

View File

@@ -0,0 +1,69 @@
package service
import (
"context"
"encoding/json"
model "go-common/app/job/main/reply/model/reply"
"go-common/library/log"
xtime "go-common/library/time"
)
func (s *Service) actionUp(c context.Context, msg *consumerMsg) {
var d struct {
Op string `json:"op"`
Action uint32 `json:"action"`
Oid int64 `json:"oid"`
Tp int64 `json:"tp"`
RpID int64 `json:"rpid"`
MTime xtime.Time `json:"mtime"`
}
if err := json.Unmarshal([]byte(msg.Data), &d); err != nil {
log.Error("json.Unmarshal() error(%v)", err)
return
}
if d.Oid == 0 || d.RpID == 0 {
log.Error("The structure of action(%s) from rpCh was wrong", msg.Data)
return
}
rp, err := s.getReplyCache(c, d.Oid, d.RpID)
if err != nil {
log.Error("s.getReply failed , oid(%d), RpID(%d) err(%v)", d.Oid, d.RpID, err)
return
}
if rp == nil {
log.Error("reply is nil oid(%d) RpID(%d)", d.Oid, d.RpID)
return
}
var state int8
switch {
case d.Op == "show":
if rp.State != model.ReplyStateHidden {
log.Warn("reply state(%d) is not hidden", rp.State)
return
}
state = model.ReplyStateNormal
case d.Op == "hide":
if rp.State != model.ReplyStateNormal && rp.State != model.ReplyStateGarbage && rp.State != model.ReplyStateFiltered && rp.State != model.ReplyStateFolded {
log.Warn("reply state(%d) is not normal", rp.State)
return
}
state = model.ReplyStateHidden
case d.Op == "top_add":
if err := s.topAdd(c, rp, d.MTime, d.Action, model.SubAttrUpperTop); err != nil {
log.Error("s.topAdd(oid %d) err(%v)", d.Oid, err)
}
return
}
if rows, err := s.dao.Reply.UpState(c, d.Oid, d.RpID, state, d.MTime.Time()); err != nil || rows < 1 {
log.Error("dao.Reply.Update(%d, %d, %d), error(%v) and rows==0", d.Oid, d.RpID, state, err)
} else {
// if rp, err := s.dao.Mc.GetReply(nil, d.RpID); err == nil && rp != nil {
rp.State = state
if err = s.dao.Mc.AddReply(c, rp); err != nil {
log.Error("s.dao.Mc.AddReply(%d, %d, %d), error(%v) and rows==0", d.Oid, d.RpID, state, err)
}
// }
}
// callSearchUp(rp.Oid, rp.RpID, rp.Type, false)
}