You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1151 lines
33 KiB
1151 lines
33 KiB
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 |
|
}
|
|
|