go-common/app/admin/main/reply/service/reply.go

1152 lines
33 KiB
Go
Raw Normal View History

2019-04-22 10:49:16 +00:00
package service
import (
"context"
"encoding/json"
"fmt"
"strconv"
"sync"
"time"
"go-common/app/admin/main/reply/model"
accmdl "go-common/app/service/main/account/api"
"go-common/app/service/main/archive/api"
arcmdl "go-common/app/service/main/archive/model/archive"
rlmdl "go-common/app/service/main/relation/model"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/queue/databus/report"
"go-common/library/sync/errgroup"
xtime "go-common/library/time"
)
func (s *Service) reply(c context.Context, oid, rpID int64) (rp *model.Reply, err error) {
if rp, err = s.dao.Reply(c, oid, rpID); err != nil {
return
}
if rp == nil {
err = ecode.ReplyNotExist
return
}
if rp.Content, err = s.dao.ReplyContent(c, oid, rpID); err != nil {
return
}
if rp.Content == nil {
err = ecode.ReplyNotExist
}
return
}
func (s *Service) replies(c context.Context, oids, rpIDs []int64) (res map[int64]*model.Reply, err error) {
res, missIDs, err := s.dao.RepliesCache(c, rpIDs)
if err != nil {
return
}
if len(missIDs) > 0 {
var (
rps map[int64]*model.Reply
rcs map[int64]*model.ReplyContent
miss []*model.Reply
missOids []int64
)
for _, missID := range missIDs {
for i := range rpIDs {
if rpIDs[i] == missID {
missOids = append(missOids, oids[i])
break
}
}
}
if rps, err = s.dao.Replies(c, missOids, missIDs); err != nil {
return
}
if rcs, err = s.dao.ReplyContents(c, missOids, missIDs); err != nil {
return
}
for id, rp := range rps {
rp.Content = rcs[id]
res[id] = rp
miss = append(miss, rp)
}
s.cache.Do(c, func(ctx context.Context) {
s.dao.AddReplyCache(ctx, miss...)
})
}
return
}
// ReplySearch return reply result from search.
func (s *Service) ReplySearch(c context.Context, sp *model.SearchParams, page, pageSize int64) (res *model.SearchResult, err error) {
if res, err = s.dao.SearchReplyV3(c, sp, page, pageSize); err != nil {
log.Error("s.dao.SearchReplyV3(%+v,%d,%d) error(%v) ", sp, page, pageSize, err)
return
}
adMap := make(map[int64]*model.SearchAdminLog)
var ids []int64
for i := range res.Result {
ids = append(ids, res.Result[i].ID)
}
if adres, err := s.dao.SearchAdminLog(c, ids); err == nil {
for _, data := range adres {
adMap[data.ReplyID] = data
}
}
filters := make(map[int64]string, len(res.Result))
links := make(map[int64]string, len(res.Result))
titles := make(map[int64]string)
var mids []int64
for _, data := range res.Result {
if log, ok := adMap[data.ID]; ok && log != nil {
data.AdminID = log.AdminID
data.AdminName = log.AdminName
data.OpCtime = log.CTime
data.Opremark = log.Remark
data.Opresult = log.Result
}
mids = append(mids, data.Mid)
data.OidStr = strconv.FormatInt(data.Oid, 10)
// reply filtered
if len(data.Attr) > 0 {
for _, attr := range data.Attr {
if attr == 4 {
filters[data.ID] = data.Message
}
}
}
// show title for top reply
if sp.Attr == "1" && data.Title == "" {
var link string
data.Title, link, _ = s.TitleLink(c, data.Oid, int32(data.Type))
data.RedirectURL = fmt.Sprintf("%s#reply%d", link, data.ID)
} else {
links[data.Oid] = ""
if int32(data.Type) == model.SubTypeArchive {
titles[data.Oid] = ""
}
}
}
ip := metadata.String(c, metadata.RemoteIP)
stasMap, err := s.relationSvc.Stats(c, &rlmdl.ArgMids{Mids: mids, RealIP: ip})
if err == nil {
for i, data := range res.Result {
if stat, ok := stasMap[data.Mid]; ok {
res.Result[i].Stat = stat
}
}
} else {
log.Error("relationSvc.Stats error(%v)", err)
}
s.linkByOids(c, links, sp.Type)
s.titlesByOids(c, titles)
s.dao.FilterContents(c, filters)
for _, data := range res.Result {
if content := filters[data.ID]; content != "" {
data.Message = content
}
if data.RedirectURL == "" {
if link := links[data.Oid]; link != "" {
data.RedirectURL = fmt.Sprintf("%s#reply%d", link, data.ID)
}
}
if int32(data.Type) == model.SubTypeArchive && data.Title == "" {
if title := titles[data.Oid]; title != "" {
data.Title = title
}
}
}
return
}
func (s *Service) titlesByOids(c context.Context, titles map[int64]string) (err error) {
var aids []int64
for oid := range titles {
aids = append(aids, oid)
}
arg := &arcmdl.ArgAids2{
Aids: aids,
}
var m map[int64]*api.Arc
m, err = s.arcSrv.Archives3(c, arg)
for oid := range m {
titles[oid] = m[oid].Title
}
return
}
func (s *Service) linkByOids(c context.Context, oids map[int64]string, typ int32) (err error) {
if len(oids) == 0 {
return
}
if typ == model.SubTypeActivity {
err = s.dao.TopicsLink(c, oids, false)
} else {
for oid := range oids {
var link string
switch typ {
case model.SubTypeTopic:
link = fmt.Sprintf("https://www.bilibili.com/topic/%d.html", oid)
case model.SubTypeArchive:
link = fmt.Sprintf("https://www.bilibili.com/video/av%d", oid)
case model.SubTypeForbiden:
link = fmt.Sprintf("https://www.bilibili.com/blackroom/ban/%d", oid)
case model.SubTypeNotice:
link = fmt.Sprintf("https://www.bilibili.com/blackroom/notice/%d", oid)
case model.SubTypeActArc:
_, link, err = s.dao.ActivitySub(c, oid)
if err != nil {
return
}
case model.SubTypeArticle:
link = fmt.Sprintf("https://www.bilibili.com/read/cv%d", oid)
case model.SubTypeLiveVideo:
link = fmt.Sprintf("https://vc.bilibili.com/video/%d", oid)
case model.SubTypeLiveAct:
_, link, err = s.dao.LiveActivityTitle(c, oid)
if err != nil {
return
}
case model.SubTypeLivePicture:
link = fmt.Sprintf("https://h.bilibili.com/ywh/%d", oid)
case model.SubTypeCredit:
link = fmt.Sprintf("https://www.bilibili.com/judgement/case/%d", oid)
case model.SubTypeDynamic:
link = fmt.Sprintf("https://t.bilibili.com/%d", oid)
default:
return
}
oids[oid] = link
}
}
return
}
// AdminEditReply edit reply content by admin.
func (s *Service) AdminEditReply(c context.Context, adminID int64, adName string, oid, rpID int64, tp int32, msg, remark string) (err error) {
rp, err := s.reply(c, oid, rpID)
if err != nil {
return
}
if rp.IsDeleted() {
err = ecode.ReplyDeleted
return
}
now := time.Now()
if _, err = s.dao.UpReplyContent(c, oid, rpID, msg, now); err != nil {
log.Error("s.content.UpMessage(%d, %d, %s, %v), err is (%v)", oid, rpID, msg, now, err)
return
}
if err = s.dao.DelReplyCache(c, rpID); err != nil {
log.Error("dao.AddReplyCache(%+v,%s) rpid(%d) error(%v)", rp, msg, err)
}
s.addAdminLog(c, rp.Oid, rp.ID, adminID, rp.Type, model.AdminIsNew, model.AdminIsNotReport, model.AdminOperEdit, "已修改评论内容", remark, now)
s.cache.Do(c, func(ctx context.Context) {
s.pubSearchReply(ctx, map[int64]*model.Reply{rp.ID: rp}, rp.State)
})
report.Manager(&report.ManagerInfo{
UID: adminID,
Uname: adName,
Business: 41,
Type: int(tp),
Oid: rp.Oid,
Ctime: now,
Action: model.ReportActionReplyEdit,
Index: []interface{}{
rp.ID,
rp.State,
rp.State,
},
Content: map[string]interface{}{"remark": remark},
})
return
}
// AddTop add a top reply.
func (s *Service) AddTop(c context.Context, adid int64, adName string, oid, rpID int64, typ int32, act uint32) (err error) {
rp, err := s.reply(c, oid, rpID)
if err != nil {
return
}
if rp.IsFolded() {
return ecode.ReplyFolded
}
if rp.Root != 0 {
log.Error("add top reply illegal reply(oid:%v,type:%v,:rpID:%v) not root", oid, typ, rpID, err)
return
}
sub, err := s.subject(c, oid, typ)
if err != nil {
log.Error("s.subject(%d,%d),err:%v", oid, typ)
return
}
if act == model.AttrYes && sub.AttrVal(model.SubAttrTopAdmin) == model.AttrYes {
err = ecode.ReplyHaveTop
log.Error("Repeat to add top reply(%d,%d,%d,%d) ", rp.ID, rp.Oid, typ, sub.Attr)
return
}
sub.AttrSet(act, model.SubAttrTopAdmin)
err = sub.TopSet(rpID, 0, act)
if err != nil {
log.Error("sub.TopSet(%d,%d,%d) failed!err:=%v ", rp.ID, rp.Oid, 0, err)
return
}
rp.AttrSet(act, model.AttrTopAdmin)
now := time.Now()
tx, err := s.dao.BeginTran(c)
if err != nil {
return
}
if _, err = s.dao.TxUpReplyAttr(tx, oid, rpID, rp.Attr, now); err != nil {
tx.Rollback()
return
}
if _, err = s.dao.TxUpSubAttr(tx, oid, typ, sub.Attr, now); err != nil {
tx.Rollback()
return
}
if _, err = s.dao.TxUpSubMeta(tx, sub.Oid, sub.Type, sub.Meta, now); err != nil {
tx.Rollback()
log.Error("dao.TxUpMeta(oid:%d,tp:%d) err(%v)", sub.Oid, sub.Type, err)
return
}
if err = tx.Commit(); err != nil {
return
}
if act == model.AttrYes {
s.dao.DelIndexBySort(c, rp, model.SortByCount)
s.dao.DelIndexBySort(c, rp, model.SortByLike)
} else if act == model.AttrNo && rp.IsNormal() {
s.addReplyIndex(c, rp)
}
s.dao.AddTopCache(c, rp)
s.dao.AddReplyCache(c, rp)
s.dao.DelSubjectCache(c, rp.Oid, rp.Type)
report.Manager(&report.ManagerInfo{
UID: adid,
Uname: adName,
Business: 41,
Type: int(typ),
Oid: oid,
Ctime: now,
Action: model.ReportActionReplyTop,
Index: []interface{}{sub.Mid, act, rpID},
})
if act == model.AttrYes {
s.pubEvent(c, "top", 0, sub, rp, nil)
} else if act == model.AttrNo {
s.pubEvent(c, "untop", 0, sub, rp, nil)
}
//add admin log and search log
if act == model.AttrYes {
s.addAdminLog(c, rp.Oid, rp.ID, adid, rp.Type, model.AdminIsNew, model.AdminIsNotReport, model.AdminOperSubTop, "管理员置顶评论", "", time.Now())
s.cache.Do(c, func(ctx context.Context) {
s.pubSearchReply(ctx, map[int64]*model.Reply{rp.ID: rp}, rp.State)
})
} else {
s.addAdminLog(c, rp.Oid, rp.ID, adid, rp.Type, model.AdminIsNew, model.AdminIsNotReport, model.AdminOperSubTop, "管理员取消置顶评论", "", time.Now())
s.cache.Do(c, func(ctx context.Context) {
s.pubSearchReply(ctx, map[int64]*model.Reply{rp.ID: rp}, model.StateNormal)
})
}
return
}
// CallbackDeleteReply delete reply by admin.
func (s *Service) CallbackDeleteReply(ctx context.Context, adminID int64, oid, rpID int64, ftime int64, typ int32, moral int32, adminName, remark string, reason, freason int32) (err error) {
now := time.Now()
sub, rp, err := s.delReply(ctx, oid, rpID, model.StateDelAdmin, now)
if err != nil {
if ecode.ReplyDeleted.Equal(err) && rp.IsDeleted() {
err = nil
} else {
log.Error("delReply(%d,%d) error(%v)", oid, rpID, err)
return err
}
}
s.delCache(ctx, sub, rp)
s.cache.Do(ctx, func(ctx context.Context) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
var rpt *model.Report
if rpt, _ = s.dao.Report(ctx, oid, rpID); rpt != nil {
if rpt.State == model.ReportStateNew || rpt.State == model.ReportStateNew2 {
rpt.MTime = xtime.Time(now.Unix())
if rpt.State == model.ReportStateNew {
rpt.State = model.ReportStateDelete1
} else if rpt.State == model.ReportStateNew2 {
rpt.State = model.ReportStateDelete2
}
if _, err = s.dao.UpReportsState(ctx, []int64{rpt.Oid}, []int64{rpt.RpID}, rpt.State, now); err != nil {
log.Error("s.dao.UpdateReport(%+v) error(%v)", rpt, err)
}
state := model.StateDelAdmin
s.pubSearchReport(ctx, map[int64]*model.Report{rpt.RpID: rpt}, &state)
}
}
s.pubEvent(context.Background(), model.EventReportDel, rpt.Mid, sub, rp, rpt)
report.Manager(&report.ManagerInfo{
UID: adminID,
Uname: adminName,
Business: 41,
Type: int(typ),
Oid: rp.Oid,
Ctime: now,
Action: model.ReportActionReplyDel,
Index: []interface{}{
rp.ID,
rp.State,
model.StateDelAdmin,
},
Content: map[string]interface{}{
"moral": moral,
"notify": false,
"ftime": ftime,
"freason": freason,
"reason": reason,
"remark": remark,
},
})
rps := make(map[int64]*model.Reply)
rps[rp.ID] = rp
s.addAdminLogs(ctx, rps, adminID, typ, model.AdminIsNew, model.AdminIsReport, model.AdminOperDelete, fmt.Sprintf("已删除并封禁%s/扣除%d节操", forbidResult(ftime), moral), remark, now)
s.pubSearchReply(ctx, rps, model.StateDelAdmin)
})
return
}
// AdminDeleteReply delete reply by admin.
func (s *Service) AdminDeleteReply(c context.Context, adminID int64, oids, rpIDs []int64, ftime int64, typ int32, moral int32, notify bool, adminName, remark string, reason, freason int32) (err error) {
err = s.adminDeleteReply(c, adminID, oids, rpIDs, ftime, typ, moral, notify, adminName, remark, reason, freason)
return
}
func (s *Service) adminDeleteReply(c context.Context, adminID int64, oids, rpIDs []int64, ftime int64, typ int32, moral int32, notify bool, adminName, remark string, reason, freason int32) (err error) {
var (
lk sync.Mutex
rps = make(map[int64]*model.Reply)
now = time.Now()
)
wg := errgroup.Group{}
wg.GOMAXPROCS(4)
for idx := range oids {
i := idx
wg.Go(func() (err error) {
var sub *model.Subject
var rp *model.Reply
// 针对大忽悠事件 特殊用户删除评论不让删
var (
tp int32
ok bool
exsits bool
)
tp, ok = s.oids[oids[i]]
if ok && tp == typ {
_, exsits = s.ads[adminName]
if exsits {
return
}
}
sub, rp, err = s.delReply(c, oids[i], rpIDs[i], model.StateDelAdmin, now)
if err != nil {
if ecode.ReplyDeleted.Equal(err) && rp.IsDeleted() {
err = nil
} else {
log.Error("delReply(%d,%d) error(%v)", oids[i], rpIDs[i], err)
return err
}
}
if rp.IsFolded() {
s.marker.Do(c, func(ctx context.Context) {
s.handleFolded(ctx, rp)
})
}
s.delCache(c, sub, rp)
s.pubEvent(c, "reply_del", 0, sub, rp, nil)
rpt, _ := s.dao.Report(c, oids[i], rpIDs[i])
if rpt != nil {
if rpt.State == model.ReportStateNew || rpt.State == model.ReportStateNew2 {
rpt.MTime = xtime.Time(now.Unix())
if rpt.State == model.ReportStateNew {
rpt.State = model.ReportStateDelete1
} else if rpt.State == model.ReportStateNew2 {
rpt.State = model.ReportStateDelete2
}
if _, err = s.dao.UpReportsState(c, []int64{rpt.Oid}, []int64{rpt.RpID}, rpt.State, now); err != nil {
log.Error("s.dao.UpdateReport(%+v) error(%v)", rpt, err)
}
}
}
s.cache.Do(c, func(ctx context.Context) {
// 针对大忽悠事件的特殊推送
if _, ok := s.ads[adminName]; ok {
if e := s.NotifyTroll(ctx, rp.Mid); e != nil {
log.Warn("notify-troll error (%v)", e)
}
}
s.dao.DelReport(ctx, rp.Oid, rp.ID)
if rpt != nil {
rpt.ReplyCtime = rp.CTime
state := model.StateDelAdmin
s.pubSearchReport(ctx, map[int64]*model.Report{rpt.RpID: rpt}, &state)
}
s.moralAndNotify(ctx, rp, moral, notify, rp.Mid, adminID, adminName, remark, reason, freason, ftime, false)
})
lk.Lock()
rps[rp.ID] = rp
lk.Unlock()
report.Manager(&report.ManagerInfo{
UID: adminID,
Uname: adminName,
Business: 41,
Type: int(typ),
Oid: rp.Oid,
Ctime: now,
Action: model.ReportActionReplyDel,
Index: []interface{}{
rp.ID,
rp.State,
model.StateDelAdmin,
},
Content: map[string]interface{}{
"moral": moral,
"notify": notify,
"ftime": ftime,
"freason": freason,
"reason": reason,
"remark": remark,
},
})
return nil
})
}
if err = wg.Wait(); err != nil {
return
}
s.addAdminLogs(c, rps, adminID, typ, model.AdminIsNew, model.AdminIsNotReport, model.AdminOperDelete, fmt.Sprintf("已删除并封禁%s/扣除%d节操", forbidResult(ftime), moral), remark, now)
s.cache.Do(c, func(ctx context.Context) {
s.pubSearchReply(ctx, rps, model.StateDelAdmin)
})
return
}
// AdminRecoverReply recover reply by admin.
func (s *Service) AdminRecoverReply(c context.Context, adminID int64, adName string, oid, rpID int64, typ int32, remark string) (err error) {
rp, err := s.reply(c, oid, rpID)
if err != nil {
log.Error("s.reply(%d,%d) error(%v)", oid, rpID, err)
return
}
now := time.Now()
var sub *model.Subject
if sub, rp, err = s.recReply(c, rp.Oid, rp.ID, model.StateNormal, now); err != nil {
log.Error("s.recReply(%d,%d) error(%v)", rp.Oid, rp.ID, err)
return
}
s.addAdminLog(c, rp.Oid, rp.ID, adminID, rp.Type, model.AdminIsNew, model.AdminIsNotReport, model.AdminOperRecover, "已恢复评论", remark, now)
s.pubEvent(c, "reply_recover", 0, sub, rp, nil)
s.cache.Do(c, func(ctx context.Context) {
s.pubSearchReply(ctx, map[int64]*model.Reply{rp.ID: rp}, model.StateNormal)
})
report.Manager(&report.ManagerInfo{
UID: adminID,
Uname: adName,
Business: 41,
Type: int(typ),
Oid: rp.Oid,
Ctime: now,
Action: model.ReportActionReplyRecover,
Index: []interface{}{
rp.ID,
rp.State,
model.StateNormal,
},
Content: map[string]interface{}{"remark": remark},
})
return
}
// AdminPassReply recover reply by admin.
func (s *Service) AdminPassReply(c context.Context, adid int64, adName string, oids, rpIDs []int64, typ int32, remark string) (err error) {
s.adminPassReply(c, adid, adName, oids, rpIDs, typ, remark)
return
}
func (s *Service) adminPassReply(c context.Context, adid int64, adName string, oids, rpIDs []int64, typ int32, remark string) (err error) {
now := time.Now()
rps, err := s.replies(c, oids, rpIDs)
if err != nil {
return
}
wg, ctx := errgroup.WithContext(c)
for _, m := range rps {
rp := m
wg.Go(func() (err error) {
if rp.State == model.StatePending {
var sub *model.Subject
if sub, rp, err = s.recReply(ctx, rp.Oid, rp.ID, model.StateNormal, now); err != nil {
return
}
s.dao.DelAuditIndex(ctx, rp)
s.pubEvent(c, "reply_recover", 0, sub, rp, nil)
} else {
var (
tx *sql.Tx
rows int64
)
if tx, err = s.dao.BeginTran(ctx); err != nil {
return
}
if rows, err = s.dao.TxUpdateReplyState(tx, rp.Oid, rp.ID, model.StateNormal, now); err != nil || rows == 0 {
log.Error("dao.Reply.TxUpdateReplyState(%v,%d) error(%v)", rp, model.StateNormal, err)
tx.Rollback()
return
}
if rp.State == model.StateMonitor {
if _, err = s.dao.TxSubDecrMCount(tx, rp.Oid, rp.Type, now); err != nil {
log.Error("dao.Reply.TxSubDecrMCount(%v) error(%v)", rp, err)
tx.Rollback()
return
}
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit error(%v)", err)
return
}
if err = s.dao.DelReplyCache(ctx, rp.ID); err != nil {
log.Error("s.dao.DelReplyCache(%d,%d) error(%v)", rp.Oid, rp.ID, err)
}
}
report.Manager(&report.ManagerInfo{
UID: adid,
Uname: adName,
Business: 41,
Type: int(typ),
Oid: rp.Oid,
Ctime: now,
Action: model.ReportActionReplyPass,
Index: []interface{}{
rp.ID,
rp.State,
model.StateNormal,
},
Content: map[string]interface{}{"remark": remark},
})
return
})
}
if err = wg.Wait(); err != nil {
return
}
s.addAdminLogs(c, rps, adid, typ, model.AdminIsNew, model.AdminIsNotReport, model.AdminOperPass, "已通过评论", remark, now)
s.cache.Do(c, func(ctx context.Context) {
s.pubSearchReply(ctx, rps, model.StateNormal)
})
return
}
// addReplyIndex add reply index to redis.
func (s *Service) addReplyIndex(c context.Context, rp *model.Reply) (err error) {
var ok bool
if rp.IsRoot() {
if ok, err = s.dao.ExpireIndex(c, rp.Oid, rp.Type, model.SortByFloor); err == nil && ok {
if err = s.dao.AddFloorIndex(c, rp); err != nil {
log.Error("d.AddFloorIndex(%d,%d) error(%v)", rp.Oid, rp.Type, err)
}
}
if ok, err = s.dao.ExpireIndex(c, rp.Oid, rp.Type, model.SortByCount); err == nil && ok {
if err = s.dao.AddCountIndex(c, rp); err != nil {
log.Error("s.AddCountIndex(%d,%d) error(%v)", rp.Oid, rp.Type, err)
}
}
if ok, err = s.dao.ExpireIndex(c, rp.Oid, rp.Type, model.SortByLike); err == nil && ok {
rpt, _ := s.dao.Report(c, rp.Oid, rp.ID)
if err = s.dao.AddLikeIndex(c, rp, rpt); err != nil {
log.Error("d.AddLikeIndex(%d,%d) error(%v)", rp.Oid, rp.Type, err)
}
}
} else {
if ok, err = s.dao.ExpireNewChildIndex(c, rp.Root); err == nil && ok {
if err = s.dao.AddNewChildIndex(c, rp); err != nil {
log.Error("d.AddFloorRootIndex(%d) error(%v)", rp.Root, err)
}
}
}
return
}
func (s *Service) recReply(c context.Context, oid, rpID int64, state int32, now time.Time) (sub *model.Subject, rp *model.Reply, err error) {
if sub, rp, err = s.tranRecover(c, oid, rpID, state, now); err != nil {
return
}
if rp.Content, err = s.dao.ReplyContent(c, oid, rpID); err != nil {
return
}
if rp.Content == nil {
err = ecode.ReplyNotExist
return
}
if !rp.IsRoot() {
if err = s.dao.DelReplyCache(c, rp.Root); err != nil {
log.Error("s.dao.DelReplyCache(%d,%d) error(%v)", oid, rpID, err)
}
}
if err = s.dao.DelReplyCache(c, rpID); err != nil {
log.Error("s.dao.DelReplyCache(%d,%d) error(%v)", oid, rpID, err)
}
if err = s.addReplyIndex(c, rp); err != nil {
log.Error("s.dao.DelReplyIndex(%d,%d) error(%v)", oid, rpID, err)
}
if err = s.dao.AddSubjectCache(c, sub); err != nil {
log.Error("s.dao.DelSubjectCache(%+v) error(%v)", sub, err)
}
s.dao.SendStats(c, sub.Type, sub.Oid, sub.ACount)
return
}
func (s *Service) delReply(c context.Context, oid, rpID int64, state int32, now time.Time) (sub *model.Subject, rp *model.Reply, err error) {
if sub, rp, err = s.tranDel(c, oid, rpID, state, now); err != nil {
if ecode.ReplyDeleted.Equal(err) && rp.IsDeleted() {
if rp.Content, err = s.dao.ReplyContent(c, oid, rpID); err != nil {
return
} else if rp.Content == nil {
err = ecode.ReplyNotExist
return
}
err = ecode.ReplyDeleted
}
return
}
if rp.Content, err = s.dao.ReplyContent(c, oid, rpID); err != nil {
return
}
if rp.Content == nil {
err = ecode.ReplyNotExist
return
}
return
}
func (s *Service) delCache(c context.Context, sub *model.Subject, rp *model.Reply) (err error) {
if !rp.IsRoot() {
if err = s.dao.DelReplyCache(c, rp.Root); err != nil {
log.Error("s.dao.DelReplyCache(%d,%d) error(%v)", rp.Oid, rp.ID, err)
}
}
if err = s.dao.DelReplyCache(c, rp.ID); err != nil {
log.Error("s.dao.DelReplyCache(%d,%d) error(%v)", rp.Oid, rp.ID, err)
}
if err = s.dao.DelReplyIndex(c, rp); err != nil {
log.Error("s.dao.DelReplyIndex(%d,%d) error(%v)", rp.Oid, rp.ID, err)
}
if err = s.dao.AddSubjectCache(c, sub); err != nil {
log.Error("s.dao.DelSubjectCache(%+v) error(%v)", sub, err)
}
if rp.AttrVal(model.AttrTopAdmin) == model.AttrYes {
s.dao.DelTopCache(c, rp.Oid, model.SubAttrTopAdmin)
}
if rp.AttrVal(model.AttrTopUpper) == model.AttrYes {
s.dao.DelTopCache(c, rp.Oid, model.SubAttrTopUpper)
}
s.dao.SendStats(c, sub.Type, sub.Oid, sub.ACount)
return
}
func (s *Service) tranRecover(c context.Context, oid, rpID int64, state int32, now time.Time) (sub *model.Subject, rp *model.Reply, err error) {
var (
rootRp *model.Reply
count int32
)
tx, err := s.dao.BeginTran(c)
if err != nil {
return
}
if rp, err = s.dao.TxReplyForUpdate(tx, oid, rpID); err != nil {
tx.Rollback()
err = fmt.Errorf("s.dao.Reply(%d,%d) error(%v) ", oid, rpID, err)
return
}
if rp == nil {
tx.Rollback()
err = ecode.ReplyNotExist
return
} else if rp.IsNormal() {
tx.Rollback()
err = ecode.ReplyActioned
return
}
rows, err := s.dao.TxUpdateReplyState(tx, rp.Oid, rp.ID, state, now)
if err != nil || rows == 0 {
tx.Rollback()
err = fmt.Errorf("error(%v) or rows(%d)", err, rows)
return
}
rp.MTime = xtime.Time(now.Unix())
if rp.IsRoot() {
count = rp.RCount + 1
} else {
if rootRp, err = s.dao.TxReply(tx, rp.Oid, rp.Root); err != nil {
tx.Rollback()
return
}
count = 1
}
if rp.IsRoot() {
rows, err = s.dao.TxIncrSubRCount(tx, rp.Oid, rp.Type, now)
} else {
rows, err = s.dao.TxIncrReplyRCount(tx, rp.Oid, rp.Root, now)
}
if err != nil || rows == 0 {
tx.Rollback()
err = fmt.Errorf("tranRecover increase count error(%v) or rows(%d)", err, rows)
return
}
if rp.IsRoot() || (rootRp != nil && rootRp.IsNormal()) {
if rows, err = s.dao.TxIncrSubACount(tx, rp.Oid, rp.Type, count, now); err != nil || rows == 0 {
tx.Rollback()
err = fmt.Errorf("TxIncrSubACount error(%v) or rows(%d)", err, rows)
return
}
}
if rp.State == model.StatePending {
if rows, err = s.dao.TxSubDecrMCount(tx, rp.Oid, rp.Type, now); err != nil {
tx.Rollback()
err = fmt.Errorf("TxSubDecrMCount error(%v)", err)
return
}
}
if sub, err = s.dao.TxSubject(tx, rp.Oid, rp.Type); err != nil || sub == nil {
tx.Rollback()
err = fmt.Errorf(" s.dao.TxSubject(%d,%d) or rows(%d)", rp.Oid, rp.Type, rows)
return
}
err = tx.Commit()
return
}
func (s *Service) tranDel(c context.Context, oid, rpID int64, state int32, now time.Time) (sub *model.Subject, rp *model.Reply, err error) {
var (
count int32
rootReply *model.Reply
)
tx, err := s.dao.BeginTran(c)
if err != nil {
return
}
rp, err = s.dao.TxReplyForUpdate(tx, oid, rpID)
if err != nil {
tx.Rollback()
err = fmt.Errorf("s.dao.TxReplyForUpdate(%d,%d) error(%v) ", oid, rpID, err)
return
}
if rp == nil {
err = ecode.NothingFound
return
}
if rp.AttrVal(model.AttrTopAdmin) == 1 || rp.IsDeleted() {
if sub, err = s.dao.TxSubject(tx, rp.Oid, rp.Type); err != nil || sub == nil {
tx.Rollback()
err = fmt.Errorf("s.dao.TxSubject(%d,%d) error(%v)", rp.Oid, rp.Type, err)
return
}
tx.Rollback()
err = ecode.ReplyDeleted
return
}
rows, err := s.dao.TxUpdateReplyState(tx, oid, rpID, state, now)
if err != nil || rows == 0 {
tx.Rollback()
err = fmt.Errorf("s.dao.TxUpdateReplyState(%+v) rows:%d error(%v)", rp, rows, err)
return
}
rp.MTime = xtime.Time(now.Unix())
if rp.IsNormal() {
if rp.IsRoot() {
count = rp.RCount + 1
if rows, err = s.dao.TxSubDecrACount(tx, rp.Oid, rp.Type, count, now); err != nil || rows == 0 {
tx.Rollback()
err = fmt.Errorf("s.dao.TxSubDecrACount(%+v) rows:%d error(%v)", rp, rows, err)
return
}
rows, err = s.dao.TxDecrSubRCount(tx, rp.Oid, rp.Type, now)
if err != nil || rows == 0 {
tx.Rollback()
err = fmt.Errorf("TxDecrReplyRCount(%+v) rows:%d error(%v)", rp, rows, err)
return
}
} else {
if rootReply, err = s.dao.TxReplyForUpdate(tx, rp.Oid, rp.Root); err != nil {
tx.Rollback()
return
}
if rootReply != nil {
if rootReply.IsNormal() {
if rows, err = s.dao.TxSubDecrACount(tx, rp.Oid, rp.Type, 1, now); err != nil || rows == 0 {
tx.Rollback()
err = fmt.Errorf("s.dao.TxSubDecrACount(%+v) rows:%d error(%v)", rp, rows, err)
return
}
}
_, err = s.dao.TxDecrReplyRCount(tx, rp.Oid, rp.Root, now)
if err != nil {
tx.Rollback()
err = fmt.Errorf("TxDecrReplyRCount(%+v) error(%v)", rp, err)
return
}
}
}
}
if sub, err = s.dao.TxSubject(tx, rp.Oid, rp.Type); err != nil || sub == nil {
tx.Rollback()
err = fmt.Errorf("s.dao.TxSubject(%d,%d) rows:%d error(%v)", rp.Oid, rp.Type, rows, err)
return
}
if rp.State == model.StatePending || rp.State == model.StateMonitor {
if _, err = s.dao.TxSubDecrMCount(tx, rp.Oid, rp.Type, now); err != nil {
log.Error("dao.Reply.TxSubDecrMCount(%v) error(%v)", rp, err)
tx.Rollback()
return
}
}
if rp.AttrVal(model.AttrTopUpper) == model.AttrYes {
rp.AttrSet(model.AttrNo, model.AttrTopUpper)
sub.AttrSet(model.AttrNo, model.SubAttrTopUpper)
err = sub.TopSet(0, 1, 0)
if err != nil {
tx.Rollback()
log.Error("sub.TopSet(%d,%d,%d) failed!err:=%v ", rp.ID, rp.Oid, 0, err)
return
}
if _, err = s.dao.TxUpSubMeta(tx, sub.Oid, sub.Type, sub.Meta, now); err != nil {
tx.Rollback()
log.Error("dao.TxUpMeta(oid:%d,tp:%d) err(%v) rows(%d)", sub.Oid, sub.Type, err)
return
}
if rows, err = s.dao.TxUpSubAttr(tx, sub.Oid, sub.Type, sub.Attr, now); err != nil || rows == 0 {
tx.Rollback()
err = fmt.Errorf("s.dao.TxUpSubAttr(%+v) rows:%d error(%v)", sub, rows, err)
return
}
if rows, err = s.dao.TxUpReplyAttr(tx, rp.Oid, rp.ID, rp.Attr, now); err != nil || rows == 0 {
tx.Rollback()
err = fmt.Errorf("s.dao.TxUpReplyAttr(%+v) rows:%d error(%v)", rp, rows, err)
return
}
}
err = tx.Commit()
return
}
// ReplyTopLog ReplyTopLog
func (s *Service) ReplyTopLog(c context.Context, sp model.LogSearchParam) (result *model.ReplyTopLogResult, err error) {
result = &model.ReplyTopLogResult{
Logs: []*model.ReplyTopLog{},
}
sp.Action = "top"
reportData, err := s.dao.ReportLog(c, sp)
if err != nil {
return
}
result.Page = reportData.Page
result.Sort = reportData.Sort
result.Order = reportData.Order
var mids []int64
for _, data := range reportData.Result {
mid := data.Index0
action := data.Index1
rpid := data.Index2
title, link, _ := s.TitleLink(c, data.Oid, data.Type)
var extra map[string]string
if data.Content != "" {
err = json.Unmarshal([]byte(data.Content), &extra)
if err != nil {
log.Error("MointorLog unmarshal failed!err:=%v", err)
return
}
}
result.Logs = append(result.Logs, &model.ReplyTopLog{
Mid: mid,
AdminID: data.AdminID,
AdminName: data.AdminName,
Oid: data.Oid,
Type: data.Type,
Remark: extra["remark"],
CTime: data.Ctime,
RpID: rpid,
Action: action,
Title: title,
RedirectURL: link,
})
mids = append(mids, mid)
}
if len(mids) > 0 {
var res *accmdl.InfosReply
res, err = s.accSrv.Infos3(c, &accmdl.MidsReq{Mids: mids})
if err != nil {
log.Error(" s.accSrv.Infos3 (%v) error(%v)", mids, err)
err = nil
return
}
for _, log := range result.Logs {
if user, ok := res.Infos[log.Mid]; ok {
log.UserName = user.GetName()
}
}
}
return
}
// MarkAsSpam mark a reply(normal state) as spam.
func (s *Service) MarkAsSpam(c context.Context, oids, rpIDs []int64, adminID int64, adminName, remark string) (err error) {
rps, err := s.replies(c, oids, rpIDs)
if err != nil {
return
}
now := time.Now()
for rpID, rp := range rps {
if rp.State == model.StateNormal && rp.AttrVal(model.AttrGarbage) == model.AttrNo {
var (
rows int64
tx *sql.Tx
)
tx, err = s.dao.BeginTran(c)
if err != nil {
return
}
_, err = s.dao.TxUpdateReplyState(tx, rp.Oid, rpID, model.StateGarbage, now)
if err != nil {
tx.Rollback()
log.Error("s.dao.TxUpdateReplyState(%+v) error(%v)", rp, err)
return
}
rp.AttrSet(model.AttrYes, model.AttrGarbage)
_, err = s.dao.TxUpReplyAttr(tx, rp.Oid, rpID, rp.Attr, now)
if err != nil {
tx.Rollback()
log.Error("s.dao.TxUpReplyAttr(%+v) rows:%d error(%v)", rp, rows, err)
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit(%+v) error(%v)", rp, err)
return
}
if err = s.dao.DelReplyCache(c, rpID); err != nil {
log.Error("s.dao.DelReplyCache(%+v) error(%v)", rp, err)
}
report.Manager(&report.ManagerInfo{
UID: adminID,
Uname: adminName,
Business: 41,
Type: int(rp.Type),
Oid: rp.Oid,
Ctime: now,
Action: model.ReportActionReplyGarbage,
Index: []interface{}{
rp.ID,
rp.State,
model.StateGarbage,
},
})
s.cache.Do(c, func(ctx context.Context) {
s.addAdminLog(ctx, rp.Oid, rp.ID, adminID, rp.Type, model.AdminIsNew, model.AdminIsNotReport, model.AdminOperMarkSpam, "标记为垃圾", remark, now)
})
}
}
return
}
// ExportReply get exported replies by query
func (s *Service) ExportReply(c context.Context, oid, mid int64, tp int8, state string, startTime, endTime time.Time) (data [][]string, err error) {
if data, err = s.dao.ExportReplies(c, oid, mid, tp, state, startTime, endTime); err != nil {
log.Error("s.dao.ExportReplies(%d,%d,%d,%s,%v,%v) error(%v)", oid, mid, tp, state, startTime, endTime)
return
}
return
}
// ReplyList ReplyList
func (s *Service) ReplyList(c context.Context, oids, rpids []int64) (res map[int64]*model.ReplyEx, err error) {
res = make(map[int64]*model.ReplyEx, 0)
replies, err := s.replies(c, oids, rpids)
if err != nil {
return
}
subjects := make(map[int32]map[int64]*model.Subject, 0)
var roots []int64
var rootoids []int64
for _, data := range replies {
sub := subjects[data.Type]
if sub == nil {
sub = make(map[int64]*model.Subject, 0)
subjects[data.Type] = sub
}
sub[data.Oid] = nil
if data.Root != 0 {
rootoids = append(rootoids, data.Oid)
roots = append(roots, data.Root)
}
}
rootreplies, err := s.replies(c, rootoids, roots)
if err != nil {
return
}
for typ, data := range subjects {
var ids []int64
for oid := range data {
ids = append(ids, oid)
}
sub, err := s.subjects(c, ids, typ)
if err != nil {
return res, err
}
subjects[typ] = sub
}
for _, data := range replies {
var isUp bool
var rootFloor int32
sub := (subjects[data.Type])[data.Oid]
if sub != nil && sub.Mid == data.Mid {
isUp = true
}
if data.Root != 0 && rootreplies[data.Root] != nil {
rootFloor = rootreplies[data.Root].Floor
}
res[data.ID] = &model.ReplyEx{*data, isUp, rootFloor}
}
return
}
// TopChildReply ...
func (s *Service) TopChildReply(c context.Context, rootID, childID, oid int64) (err error) {
var (
root *model.Reply
child *model.Reply
ok bool
)
rps, err := s.dao.Replies(c, []int64{oid, oid}, []int64{rootID, childID})
if err != nil {
return ecode.ReplyNotExist
}
if root, ok = rps[rootID]; !ok {
return ecode.ReplyNotExist
}
if child, ok = rps[childID]; !ok {
return ecode.ReplyNotExist
}
if root.Root != 0 || child.Root != root.ID {
return ecode.ReplyNotExist
}
if ok, err = s.dao.ExpireNewChildIndex(c, rootID); !ok || err != nil {
return ecode.ReplyNotExist
}
if err = s.dao.TopChildReply(c, rootID, childID); err != nil {
return ecode.ReplyNotExist
}
return
}