Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"action_test.go",
"admin_test.go",
"business_test.go",
"config_test.go",
"event_test.go",
"moral_test.go",
"notice_test.go",
"reply_test.go",
"report_test.go",
"service_test.go",
"subject_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/main/reply/conf:go_default_library",
"//app/admin/main/reply/model:go_default_library",
"//library/log:go_default_library",
"//library/time: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 = [
"action.go",
"admin.go",
"business.go",
"config.go",
"emoji.go",
"emoji_package.go",
"event.go",
"fold.go",
"monitor.go",
"moral.go",
"notice.go",
"reply.go",
"report.go",
"service.go",
"subject.go",
],
importpath = "go-common/app/admin/main/reply/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/reply/conf:go_default_library",
"//app/admin/main/reply/dao:go_default_library",
"//app/admin/main/reply/model:go_default_library",
"//app/interface/openplatform/article/model:go_default_library",
"//app/interface/openplatform/article/rpc/client: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/relation/model:go_default_library",
"//app/service/main/relation/rpc/client:go_default_library",
"//app/service/main/thumbup/api: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/metadata:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/queue/databus/report:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/sync/errgroup.v2: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,98 @@
package service
import (
"context"
"fmt"
"go-common/app/admin/main/reply/model"
thumbup "go-common/app/service/main/thumbup/api"
"go-common/library/ecode"
"go-common/library/log"
)
// ActionCount return action exact count.
func (s *Service) ActionCount(c context.Context, rpID, oid, adminID int64, typ int32) (like, hate int32, err error) {
rp, err := s.dao.Reply(c, oid, rpID)
if err != nil {
return
}
if rp == nil {
err = ecode.ReplyNotExist
return
}
like = rp.Like
hate = rp.Hate
return
}
// UpActionLike update action like.
func (s *Service) UpActionLike(c context.Context, rpID, oid, adminID int64, typ, count int32, remark string) (err error) {
rp, err := s.dao.Reply(c, oid, rpID)
if err != nil {
return
}
if rp == nil {
err = ecode.ReplyNotExist
return
}
if _, err = s.thumbupClient.UpdateCount(c, &thumbup.UpdateCountReq{
Business: "reply",
OriginID: rp.Oid,
MessageID: rpID,
LikeChange: int64(count),
Operator: fmt.Sprintf("%d", adminID),
}); err != nil {
log.Error("s.thumbupClient.UpdateCount (%d,%d,%d) failed!err:=%v", oid, rpID, int64(count), err)
return
}
rp.Like += count
if rp.Like < 0 {
rp.Like = 0
}
if err = s.addReplyIndex(c, rp); err != nil {
log.Error("s.addReplyIndex(%d,%d,%d) error(%v)", rp.ID, rp.Oid, rp.Type, err)
}
if err = s.dao.DelReplyCache(c, rp.ID); err != nil {
log.Error("s.dao.DeleteReplyCache(%d) error(%v)", rp.ID, err)
}
s.cache.Do(c, func(ctx context.Context) {
s.pubSearchReply(ctx, map[int64]*model.Reply{rp.ID: rp}, rp.State)
})
return
}
// UpActionHate update action hate.
func (s *Service) UpActionHate(c context.Context, rpID, oid, adminID int64, typ, count int32, remark string) (err error) {
rp, err := s.dao.Reply(c, oid, rpID)
if err != nil {
return
}
if rp == nil {
err = ecode.ReplyNotExist
return
}
if _, err = s.thumbupClient.UpdateCount(c, &thumbup.UpdateCountReq{
Business: "reply",
OriginID: rp.Oid,
MessageID: rpID,
DislikeChange: int64(count),
Operator: fmt.Sprintf("%d", adminID),
}); err != nil {
log.Error("s.thumbupClient.UpdateCount (%d,%d,%d) failed!err:=%v", oid, rpID, int64(count), err)
return
}
rp.Hate += count
if rp.Hate < 0 {
rp.Hate = 0
}
if err = s.addReplyIndex(c, rp); err != nil {
log.Error("s.addReplyIndex(%d,%d,%d) error(%v)", rp.ID, rp.Oid, rp.Type, err)
}
if err = s.dao.DelReplyCache(c, rp.ID); err != nil {
log.Error("s.dao.DeleteReplyCache(%d) error(%v)", rp.ID, err)
}
s.cache.Do(c, func(ctx context.Context) {
s.pubSearchReply(ctx, map[int64]*model.Reply{rp.ID: rp}, rp.State)
})
return
}

View File

@@ -0,0 +1,21 @@
package service
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestAction(t *testing.T) {
c := context.Background()
Convey("action set", t, WithService(func(s *Service) {
err := s.UpActionLike(c, 894717392, 5464686, 23, 1, 32, "test")
So(err, ShouldBeNil)
like, hate, err := s.ActionCount(c, 894717392, 5464686, 23, 1)
So(err, ShouldBeNil)
So(hate, ShouldEqual, 0)
So(like, ShouldEqual, 32)
}))
}

View File

@@ -0,0 +1,72 @@
package service
import (
"go-common/app/admin/main/reply/model"
"go-common/library/log"
"context"
"time"
)
func (s *Service) addAdminLog(c context.Context, oid, rpID, adminID int64, typ, isNew, isReport, state int32, result, remark string, now time.Time) (err error) {
rpMap := map[int64]*model.Reply{rpID: &model.Reply{
Oid: oid,
ID: rpID,
}}
return s.addAdminLogs(c, rpMap, adminID, typ, isNew, isReport, state, result, remark, now)
}
func (s *Service) addAdminIDLogs(c context.Context, oids []int64, rpIDs []int64, adminID int64, typ, isNew, isReport, state int32, result, remark string, now time.Time) (err error) {
if _, err = s.dao.UpAdminNotNew(c, rpIDs, now); err != nil {
log.Error("s.dao.UpAdminNotNew(%v) error(%v)", rpIDs, err)
}
if _, err = s.dao.AddAdminLog(c, oids, rpIDs, adminID, typ, isNew, isReport, state, result, remark, now); err != nil {
log.Error("s.dao.AddAdminLog(admin:%d, oid:%v rpID:%v type:%d result:%s remark:%s isReport:%d state:%d) error(%v)", adminID, oids, rpIDs, typ, result, remark, isReport, state, err)
}
return
}
func (s *Service) addAdminLogs(c context.Context, rps map[int64]*model.Reply, adminID int64, typ, isNew, isReport, state int32, result, remark string, now time.Time) (err error) {
if len(rps) == 0 {
return
}
rpIDs := make([]int64, 0, len(rps))
oids := make([]int64, 0, len(rps))
for _, rp := range rps {
rpIDs = append(rpIDs, rp.ID)
oids = append(oids, rp.Oid)
}
if _, err = s.dao.UpAdminNotNew(c, rpIDs, now); err != nil {
log.Error("s.dao.UpAdminNotNew(%v) error(%v)", rpIDs, err)
}
if _, err = s.dao.AddAdminLog(c, oids, rpIDs, adminID, typ, isNew, isReport, state, result, remark, now); err != nil {
log.Error("s.dao.AddAdminLog(admin:%d, oid:%v rpID:%v type:%d result:%s remark:%s isReport:%d state:%d) error(%v)", adminID, oids, rpIDs, typ, result, remark, isReport, state, err)
}
return
}
// ReplyAdminLog ReplyAdminLog
type ReplyAdminLog struct {
model.AdminLog
AdminName string `json:"admin_name"`
}
// LogsByRpID get log by reply id.
func (s *Service) LogsByRpID(c context.Context, rpID int64) (res []ReplyAdminLog, err error) {
var resDao []*model.AdminLog
res = []ReplyAdminLog{}
if resDao, err = s.dao.AdminLogsByRpID(c, rpID); err != nil {
log.Error("s.dao.AdminLogsByRpID(%d) error(%v)", rpID, err)
return
}
admins := map[int64]string{}
for _, data := range resDao {
admins[data.AdminID] = ""
res = append(res, ReplyAdminLog{*data, ""})
}
s.dao.AdminName(c, admins)
for i := range res {
res[i].AdminName = admins[res[i].AdminID]
}
return
}

View File

@@ -0,0 +1,36 @@
package service
import (
"context"
"go-common/app/admin/main/reply/model"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func TestAdminLog(t *testing.T) {
var (
ok bool
oid = int64(1)
rpID = int64(1)
adminID = int64(1)
typ = int32(4)
now = time.Now()
c = context.Background()
)
Convey("action set", t, WithService(func(s *Service) {
err := s.addAdminLog(c, oid, rpID, adminID, typ, model.AdminIsNew, model.AdminIsReport, model.AdminOperDelete, "test", "remark", now)
So(err, ShouldBeNil)
list, err := s.LogsByRpID(c, rpID)
So(err, ShouldBeNil)
So(len(list), ShouldNotEqual, 0)
for _, log := range list {
if log.ReplyID == rpID {
ok = true
}
}
So(ok, ShouldEqual, true)
}))
}

View File

@@ -0,0 +1,32 @@
package service
import (
"context"
"go-common/app/admin/main/reply/model"
)
// ListBusiness return all business
func (s *Service) ListBusiness(c context.Context, state int32) (business []*model.Business, err error) {
return s.dao.ListBusiness(c, state)
}
// GetBusiness return a business by type
func (s *Service) GetBusiness(c context.Context, tp int32) (business *model.Business, err error) {
return s.dao.Business(c, tp)
}
// AddBusiness add a business
func (s *Service) AddBusiness(c context.Context, tp int32, name, appkey, remark, alias string) (id int64, err error) {
return s.dao.InBusiness(c, tp, name, appkey, remark, alias)
}
// UpBusiness update a business's name appkey and remark
func (s *Service) UpBusiness(c context.Context, name, appkey, remark, alias string, tp int32) (id int64, err error) {
return s.dao.UpBusiness(c, name, appkey, remark, alias, tp)
}
// UpBusinessState update a business's state
func (s *Service) UpBusinessState(c context.Context, state, tp int32) (id int64, err error) {
return s.dao.UpBusinessState(c, state, tp)
}

View File

@@ -0,0 +1,30 @@
package service
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestService_Business(t *testing.T) {
c := context.Background()
Convey("test service business", c, WithService(func(s *Service) {
Convey("test service add business", c, WithService(func(s *Service) {
_, err := s.AddBusiness(c, -3, "abc", "abc", "abc", "abc")
So(err, ShouldBeNil)
}))
Convey("test service list business", c, WithService(func(s *Service) {
_, err := s.ListBusiness(c, 0)
So(err, ShouldBeNil)
}))
Convey("test service update business", c, WithService(func(s *Service) {
_, err := s.UpBusiness(c, "test", "test", "test", "abc", -3)
So(err, ShouldBeNil)
}))
Convey("test service update business state", c, WithService(func(s *Service) {
_, err := s.UpBusinessState(c, 1, -3)
So(err, ShouldBeNil)
}))
}))
}

View File

@@ -0,0 +1,86 @@
package service
import (
"context"
"math"
"time"
"go-common/app/admin/main/reply/model"
"go-common/library/log"
)
// AddReplyConfig create a new administrator configuration for reply business
func (s *Service) AddReplyConfig(c context.Context, m *model.Config) (id int64, err error) {
sub, err := s.subject(c, m.Oid, m.Type)
if err != nil {
return
}
now := time.Now()
if _, err = s.dao.AddConfig(c, m.Type, m.Category, m.Oid, m.AdminID, m.Operator, m.Config, now); err != nil {
return
}
if m.ShowEntry == 1 && m.ShowAdmin == 1 {
sub.AttrSet(model.AttrNo, model.SubAttrConfig)
} else {
sub.AttrSet(model.AttrYes, model.SubAttrConfig)
}
if _, err = s.dao.UpSubjectAttr(c, m.Oid, m.Type, sub.Attr, now); err != nil {
log.Error("s.dao.UpSubjectAttr(%d,%d,%d,%d) error(%v)", m.Type, m.Oid, model.SubAttrConfig, m.ShowEntry, err)
return
}
if err = s.dao.DelSubjectCache(c, m.Oid, m.Type); err != nil {
log.Error("ReplyConfig del subject cache error(%v)", err)
}
if err = s.dao.DelConfigCache(c, m.Oid, m.Type, m.Category); err != nil {
log.Error("ReplyConfig del config cache error(%v)", err)
}
return
}
// LoadReplyConfig load a configuration record of reply business.
func (s *Service) LoadReplyConfig(c context.Context, typ, category int32, oid int64) (m *model.Config, err error) {
m, err = s.dao.LoadConfig(c, typ, category, oid)
return
}
//PaginateReplyConfig paginate configuration list of records indexing from start to end, and a total count of records
func (s *Service) PaginateReplyConfig(c context.Context, typ, category int32, oid int64, operator string, offset, count int) (configs []*model.Config, totalCount, pages int64, err error) {
configs, _ = s.dao.PaginateConfig(c, typ, category, oid, operator, offset, count)
totalCount, _ = s.dao.PaginateConfigCount(c, typ, category, oid, operator)
pages = int64(math.Ceil(float64(totalCount) / float64(count)))
return
}
//RenewReplyConfig reset reply configuration by default, with deleting the detail configurations from db
func (s *Service) RenewReplyConfig(c context.Context, id int64) (result bool, err error) {
now := time.Now()
config, err := s.dao.LoadConfigByID(c, id)
if err != nil {
log.Error("s.dao.LoadConfigByID(%d) error(%v)", id, err)
}
if config == nil {
return false, nil
}
sub, err := s.dao.Subject(c, config.Oid, config.Type)
if err != nil {
return
}
sub.AttrSet(model.AttrNo, model.SubAttrConfig)
_, err = s.dao.UpSubjectAttr(c, config.Oid, config.Type, sub.Attr, now)
if err != nil {
log.Error("s.dao.UpSubjectAttr(%d,%d,%d,%d) error(%v)", config.Type, config.Oid, model.SubAttrConfig, config.ShowEntry, err)
return
}
if _, err = s.dao.DeleteConfig(c, id); err != nil {
log.Error("s.dao.DeleteConfig(%d) error(%v)", id, err)
return
}
if err = s.dao.DelSubjectCache(c, config.Oid, config.Type); err != nil {
log.Error("ReplyConfig del subject cache error(%v)", err)
}
if err = s.dao.DelConfigCache(c, config.Oid, config.Type, config.Category); err != nil {
log.Error("ReplyConfig del config cache error(%v)", err)
}
result = true
return
}

View File

@@ -0,0 +1,37 @@
package service
import (
"context"
"testing"
"go-common/app/admin/main/reply/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestConfig(t *testing.T) {
var (
m = &model.Config{
Oid: 1,
Type: 4,
ShowEntry: 1,
ShowAdmin: 1,
}
c = context.Background()
)
Convey("test config ", t, WithService(func(s *Service) {
_, err := s.AddReplyConfig(c, m)
So(err, ShouldBeNil)
cfg, err := s.LoadReplyConfig(c, m.Type, m.Category, m.Oid)
So(err, ShouldBeNil)
So(cfg, ShouldNotBeNil)
list, total, pages, err := s.PaginateReplyConfig(c, m.Type, m.Category, m.Oid, "", 0, 10)
So(err, ShouldBeNil)
So(len(list), ShouldNotEqual, 0)
So(total, ShouldNotEqual, 0)
So(pages, ShouldNotEqual, 0)
ok, err := s.RenewReplyConfig(c, cfg.ID)
So(err, ShouldBeNil)
So(ok, ShouldEqual, true)
}))
}

View File

@@ -0,0 +1,90 @@
package service
import (
"context"
"strings"
"go-common/app/admin/main/reply/model"
"go-common/library/ecode"
"go-common/library/log"
)
// EmojiList EmojiList
func (s *Service) EmojiList(c context.Context, pid int64) (emojis []*model.Emoji, err error) {
if pid != 0 {
emojis, err = s.dao.EmojiListByPid(c, pid)
} else {
emojis, err = s.dao.EmojiList(c)
}
if err != nil {
log.Error("service.EmojiList error (%v)", err)
}
return
}
// CreateEmoji CreateEmoji
func (s *Service) CreateEmoji(c context.Context, pid int64, name string, url string, sort int32, state int32, remark string) (id int64, err error) {
if !strings.HasPrefix(name, "[") {
name = "[" + name
}
if !strings.HasSuffix(name, "]") {
name = name + "]"
}
id, err = s.dao.CreateEmoji(c, pid, name, url, sort, state, remark)
if err != nil {
log.Error("service.CreateEmoji error (%v)", err)
}
return
}
// UpEmojiSort UpEmojiSort
func (s *Service) UpEmojiSort(c context.Context, ids string) (err error) {
tx, err := s.dao.BeginTran(c)
if err != nil {
return
}
err = s.dao.UpEmojiSort(tx, ids)
if err != nil {
tx.Rollback()
log.Error("service.UpEmojiSort error (%v)", err)
return
}
return tx.Commit()
}
// UpEmojiState UpEmojiState
func (s *Service) UpEmojiState(c context.Context, state int32, id int64) (idx int64, err error) {
if state == 2 { // delete emoji
idx, err = s.dao.DelEmojiByID(c, id)
} else {
idx, err = s.dao.UpEmojiStateByID(c, state, id)
}
if err != nil {
log.Error("service.UpEmojiState error (%v)", err)
}
return
}
// UpEmoji UpEmoji
func (s *Service) UpEmoji(c context.Context, name string, remark string, url string, id int64) (idx int64, err error) {
idx, err = s.dao.UpEmoji(c, name, remark, url, id)
if err != nil {
log.Error("service.UpEmojiState error (%v)", err)
}
return
}
// EmojiByName EmojiByName
func (s *Service) EmojiByName(c context.Context, name string) (err error) {
emojis, e := s.dao.EmojiByName(c, name)
if e != nil {
log.Error("service.CreateEmoji EmojiByName err (%v)", e)
err = e
return
}
if len(emojis) > 0 {
err = ecode.ReplyEmojiExits
return
}
return
}

View File

@@ -0,0 +1,68 @@
package service
import (
"context"
"go-common/app/admin/main/reply/model"
"go-common/library/log"
)
// EmojiPackageList return all emojipackages and emojis
func (s *Service) EmojiPackageList(c context.Context) (resp []*model.EmojiPackage, err error) {
packs, err := s.dao.EmojiPackageList(c)
if err != nil {
log.Error("service.EmojiPackageList err (%v)", err)
return
}
for _, pack := range packs {
eList, err := s.dao.EmojiListByPid(c, pack.ID)
if err != nil {
return nil, err
}
pack.Emojis = eList
resp = append(resp, pack)
}
return
}
// CreateEmojiPackage CreateEmojiPackage
func (s *Service) CreateEmojiPackage(c context.Context, name string, url string, sort int32, remark string, state int32) (id int64, err error) {
id, err = s.dao.CreateEmojiPackage(c, name, url, sort, remark, state)
if err != nil {
log.Error("service.CreateEmojiPackage err (%v)", err)
}
return
}
// UpEmojiPackageSort UpEmojiPackageSort
func (s *Service) UpEmojiPackageSort(c context.Context, ids string) (err error) {
tx, err := s.dao.BeginTran(c)
if err != nil {
return
}
err = s.dao.UpEmojiPackageSort(tx, ids)
if err != nil {
tx.Rollback()
log.Error("service.UpEmojiPackageSort err (%v)", err)
return
}
return tx.Commit()
}
// UpEmojiPackage UpEmojiPackage
func (s *Service) UpEmojiPackage(c context.Context, name string, url string, remark string, state int32, id int64) (idx int64, err error) {
if state == 2 { // delete package
_, err = s.dao.DelEmojiPackage(c, id)
if err != nil {
log.Error("service.DelEmojiPackage err (%v)", err)
return 0, err
}
idx, err = s.dao.DelEmojiByPid(c, id)
} else {
idx, err = s.dao.UpEmojiPackage(c, name, url, remark, state, id)
}
if err != nil {
log.Error("service.UpEmojiPackage err (%v)", err)
}
return
}

View File

@@ -0,0 +1,29 @@
package service
import (
"context"
"go-common/app/admin/main/reply/model"
"go-common/library/log"
)
func (s *Service) pubEvent(c context.Context, action string, mid int64, sub *model.Subject, rp *model.Reply, rpt *model.Report) (err error) {
if err = s.dao.PubEvent(c, action, mid, sub, rp, rpt); err != nil {
log.Error("s.dao.PubEvent(%s,%d,%v,%v,%v) error(%v)", action, mid, sub, rp, rpt, err)
}
return
}
func (s *Service) pubSearchReply(c context.Context, rps map[int64]*model.Reply, newState int32) (err error) {
if err = s.dao.UpSearchReply(c, rps, newState); err != nil {
log.Error("s.dao.UpSearchReply(%v,%d) error(%v)", rps, newState, err)
}
return
}
func (s *Service) pubSearchReport(c context.Context, rpts map[int64]*model.Report, rpState *int32) (err error) {
if err = s.dao.UpSearchReport(c, rpts, rpState); err != nil {
log.Error("s.dao.UpSearchReport(%v,%d) error(%v)", rpts, rpState, err)
}
return
}

View File

@@ -0,0 +1,35 @@
package service
import (
"context"
"testing"
"go-common/app/admin/main/reply/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestEvent(t *testing.T) {
var (
sub = &model.Subject{}
rp = &model.Reply{}
rpt = &model.Report{}
c = context.Background()
)
Convey("test pub a event for reply", t, WithService(func(s *Service) {
err := s.pubEvent(c, "test", 0, sub, rp, rpt)
So(err, ShouldBeNil)
}))
Convey("test pub a event for search index", t, WithService(func(s *Service) {
rps := map[int64]*model.Reply{}
rps[rp.ID] = rp
err := s.pubSearchReply(c, rps, 0)
So(err, ShouldBeNil)
}))
Convey("test pub a event for search index", t, WithService(func(s *Service) {
rpts := map[int64]*model.Report{}
rpts[rpt.RpID] = rpt
err := s.pubSearchReport(c, rpts, nil)
So(err, ShouldBeNil)
}))
}

View File

@@ -0,0 +1,320 @@
package service
import (
"context"
"sync"
"time"
"go-common/app/admin/main/reply/model"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/sync/errgroup.v2"
)
func compose(oids, tps, rpIDs []int64) (rpMap map[int64][]int64, tpMap map[int64]int64) {
if len(oids) != len(rpIDs) {
return
}
rpMap = make(map[int64][]int64)
tpMap = make(map[int64]int64)
for i, oid := range oids {
if _, ok := rpMap[oid]; ok {
rpMap[oid] = append(rpMap[oid], rpIDs[i])
} else {
rpMap[oid] = []int64{rpIDs[i]}
}
tpMap[oid] = tps[i]
}
return
}
func extend(oid int64, length int) []int64 {
oids := make([]int64, 0, length)
for i := 0; i < length; i++ {
oids = append(oids, oid)
}
return oids
}
// FoldReplies ...
func (s *Service) FoldReplies(ctx context.Context, oids, tps, rpIDs []int64) (err error) {
g := errgroup.WithContext(ctx)
g.GOMAXPROCS(2)
rpMap, tpMap := compose(oids, tps, rpIDs)
for oid, IDs := range rpMap {
if tp, ok := tpMap[oid]; ok {
oid, tp, IDs := oid, tp, IDs
g.Go(func(ctx context.Context) error {
return s.foldReplies(ctx, oid, tp, IDs)
})
}
}
return g.Wait()
}
func (s *Service) foldReplies(ctx context.Context, oid, tp int64, rpIDs []int64) (err error) {
var (
sub *model.Subject
// 所有的评论,包含需要被折叠的子评论的根评论
rpMap = make(map[int64]*model.Reply)
// 需要被折叠的子评论的根评论IDs
roots []int64
rootMap map[int64]*model.Reply
// 应该被折叠的评论map
rps = make(map[int64]*model.Reply)
mu sync.Mutex
)
if sub, err = s.subject(ctx, oid, int32(tp)); err != nil {
return
}
if rpMap, err = s.dao.Replies(ctx, extend(oid, len(rpIDs)), rpIDs); err != nil {
return
}
for _, rp := range rpMap {
if !rp.IsRoot() {
roots = append(roots, rp.Root)
}
rps[rp.ID] = rp
}
if len(roots) > 0 {
if rootMap, err = s.dao.Replies(ctx, extend(oid, len(roots)), roots); err != nil {
return
}
for _, root := range rootMap {
rpMap[root.ID] = root
}
}
g := errgroup.WithContext(ctx)
g.GOMAXPROCS(4)
for rpID := range rps {
rpID := rpID
g.Go(func(ctx context.Context) error {
if err := s.tranFoldReply(ctx, sub.Oid, rpID); err != nil {
mu.Lock()
delete(rps, rpID)
mu.Unlock()
return err
}
return nil
})
}
err = g.Wait()
for _, rp := range rps {
// 这里不是删除是为了让reply-feed去掉热评, 折叠评论是可以有互动行为的, 所以这里异步,丢了也无所谓
rp := rp
s.cache.Do(ctx, func(ctx context.Context) {
s.pubEvent(ctx, "reply_del", 0, sub, rp, nil)
})
}
// 标记数据库有折叠评论, rpMap 是所有的被折叠评论以及他们的根评论
s.markHasFolded(ctx, rps, rpMap, sub)
s.handleCacheByFold(ctx, rps, rpMap, sub)
return err
}
// handleCacheByFold ...
func (s *Service) handleCacheByFold(ctx context.Context, rps map[int64]*model.Reply, rpMap map[int64]*model.Reply, sub *model.Subject) {
var (
roots []int64
childMap = make(map[int64][]int64)
)
for _, rp := range rps {
if rp.IsRoot() {
roots = append(roots, rp.ID)
} else {
childMap[rp.Root] = append(childMap[rp.Root], rp.ID)
}
}
s.cacheOperater.Do(ctx, func(ctx context.Context) {
// 删正常列表的redis缓存
s.dao.RemRdsByFold(ctx, roots, childMap, sub, rpMap)
// 加折叠列表redis缓存
s.dao.AddRdsByFold(ctx, roots, childMap, sub, rpMap)
})
}
// markAsFolded ...
func (s *Service) markHasFolded(ctx context.Context, foldedRp map[int64]*model.Reply, rpMap map[int64]*model.Reply, sub *model.Subject) {
var (
dirtyCacheRpIDs []int64
markedRpIDs []int64
)
for _, rp := range foldedRp {
rp := rp
if rp.IsRoot() {
// 如果subject还没被标记过
if !sub.HasFolded() {
sub.MarkHasFolded()
// 修改数据库标记
s.marker.Do(ctx, func(ctx context.Context) {
if err := s.tranMarkSubHasFolded(ctx, sub.Oid, sub.Type); err != nil {
return
}
})
// 删掉 subject mc
s.cacheOperater.Do(ctx, func(ctx context.Context) {
s.dao.DelSubjectCache(ctx, sub.Oid, sub.Type)
})
}
} else {
if _, ok := rpMap[rp.Root]; ok {
markedRpIDs = append(markedRpIDs, rp.Root)
}
}
dirtyCacheRpIDs = append(dirtyCacheRpIDs, rp.ID)
}
for _, rpID := range markedRpIDs {
// 修改数据库标记
rpID := rpID
s.marker.Do(ctx, func(ctx context.Context) {
if err := s.tranMarkReplyHasFolded(ctx, sub.Oid, rpID); err != nil {
return
}
})
}
dirtyCacheRpIDs = append(dirtyCacheRpIDs, markedRpIDs...)
for _, rpID := range dirtyCacheRpIDs {
// 删除被折叠子评论的根评论以及被折叠的子评论和根评论
rpID := rpID
s.cacheOperater.Do(ctx, func(ctx context.Context) {
s.dao.DelReplyCache(ctx, rpID)
})
}
}
func (s *Service) tranFoldReply(ctx context.Context, oid, rpID int64) (err error) {
var (
tx *sql.Tx
rp *model.Reply
)
if tx, err = s.dao.BeginTran(ctx); err != nil {
return
}
if rp, err = s.dao.TxReplyForUpdate(tx, oid, rpID); err != nil {
tx.Rollback()
return
}
if rp.DenyFolded() {
tx.Rollback()
return ecode.ReplyForbidFolded
}
if _, err = s.dao.TxUpdateReplyState(tx, oid, rpID, model.StateFolded, time.Now()); err != nil {
tx.Rollback()
return
}
return tx.Commit()
}
func (s *Service) tranMarkReplyHasFolded(ctx context.Context, oid, rpID int64) (err error) {
var (
tx *sql.Tx
rp *model.Reply
)
if tx, err = s.dao.BeginTran(ctx); err != nil {
return
}
if rp, err = s.dao.TxReplyForUpdate(tx, oid, rpID); err != nil {
tx.Rollback()
return
}
rp.MarkHasFolded()
if _, err = s.dao.TxUpReplyAttr(tx, oid, rpID, rp.Attr, time.Now()); err != nil {
tx.Rollback()
return
}
return tx.Commit()
}
func (s *Service) tranMarkSubHasFolded(ctx context.Context, oid int64, tp int32) (err error) {
var (
tx *sql.Tx
sub *model.Subject
)
if tx, err = s.dao.BeginTran(ctx); err != nil {
return
}
if sub, err = s.dao.TxSubjectForUpdate(tx, oid, tp); err != nil {
tx.Rollback()
return
}
sub.MarkHasFolded()
if _, err = s.dao.TxUpSubAttr(tx, oid, tp, sub.Attr, time.Now()); err != nil {
tx.Rollback()
return
}
return tx.Commit()
}
// handleFolded 处理折叠评论的逻辑,包括折叠评论被删除等, 状态改变之后标记改变的问题...
func (s *Service) handleFolded(ctx context.Context, rp *model.Reply) {
sub, root, err := s.handleHasFoldedMark(ctx, rp.Oid, rp.Type, rp.Root)
if err != nil {
return
}
if sub != nil {
s.dao.DelSubjectCache(ctx, sub.Oid, sub.Type)
}
if root != nil {
s.dao.DelReplyCache(ctx, root.ID)
}
s.remFoldedCache(ctx, rp)
}
func (s *Service) handleHasFoldedMark(ctx context.Context, oid int64, tp int32, root int64) (sub *model.Subject, reply *model.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.TxSubjectForUpdate(tx, oid, tp); err != nil {
tx.Rollback()
return
}
if count, err = s.dao.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.TxUpSubAttr(tx, oid, tp, sub.Attr, time.Now()); err != nil {
tx.Rollback()
return
}
} else {
if reply, err = s.dao.TxReplyForUpdate(tx, oid, root); err != nil {
tx.Rollback()
return
}
if !reply.HasFolded() {
tx.Rollback()
return
}
reply.UnmarkHasFolded()
if _, err = s.dao.TxUpReplyAttr(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 *model.Reply) {
if rp.IsRoot() {
s.dao.RemFolder(ctx, model.FolderKindSub, rp.Oid, rp.ID)
} else {
s.dao.RemFolder(ctx, model.FolderKindRoot, rp.Root, rp.ID)
}
}

View File

@@ -0,0 +1,173 @@
package service
import (
"context"
"encoding/json"
"fmt"
"strconv"
"time"
"go-common/app/admin/main/reply/model"
accmdl "go-common/app/service/main/account/api"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/queue/databus/report"
)
// MonitorStats return monitor stats.
func (s *Service) MonitorStats(c context.Context, mode, page, pageSize int64, adminIDs, sort, order, startTime, endTime string) (res *model.StatsMonitorResult, err error) {
return s.dao.MonitorStats(c, mode, page, pageSize, adminIDs, sort, order, startTime, endTime)
}
// MonitorSearch return monitor result from search.
func (s *Service) MonitorSearch(c context.Context, sp *model.SearchMonitorParams, page, pageSize int64) (res *model.SearchMonitorResult, err error) {
if res, err = s.dao.SearchMonitor(c, sp, page, pageSize); err != nil {
log.Error("s.dao.SearchMonitor(%v,%d,%d) error(%v)", sp, page, pageSize, err)
}
return
}
// UpMonitorState set monitor state into subject attr.
func (s *Service) UpMonitorState(c context.Context, adminID int64, adName string, oid int64, typ, state int32, remark string) (err error) {
sub, err := s.dao.Subject(c, oid, typ)
if err != nil {
return
}
if sub == nil {
err = ecode.NothingFound
return
}
var logState int32
switch state {
case model.MonitorClose:
if sub.AttrVal(model.SubAttrMonitor) == model.AttrYes {
logState = model.AdminOperSubMonitorClose
} else if sub.AttrVal(model.SubAttrAudit) == model.AttrYes {
logState = model.AdminOperSubAuditClose
} else {
err = ecode.ReplyIllegalSubState
return
}
sub.AttrSet(model.AttrNo, model.SubAttrMonitor)
sub.AttrSet(model.AttrNo, model.SubAttrAudit)
case model.MonitorOpen:
sub.AttrSet(model.AttrYes, model.SubAttrMonitor)
sub.AttrSet(model.AttrNo, model.SubAttrAudit)
logState = model.AdminOperSubMonitorOpen
case model.MonitorAudit:
sub.AttrSet(model.AttrNo, model.SubAttrMonitor)
sub.AttrSet(model.AttrYes, model.SubAttrAudit)
logState = model.AdminOperSubAuditOpen
default:
err = ecode.RequestErr
return
}
// update attr
now := time.Now()
if _, err = s.dao.UpSubjectAttr(c, oid, typ, sub.Attr, now); err != nil {
log.Error("s.dao.UpSubjectAttr(%d,%d,%d,%d) state:%d error(%v)", typ, oid, sub.Attr, state, err)
return
}
if err = s.dao.DelSubjectCache(c, oid, typ); err != nil {
log.Error("MonitorState del subject cache error(%v)", err)
}
// update search index
if err = s.dao.UpSearchMonitor(c, sub, remark); err != nil {
log.Error("s.dao.UpdateMonitor(%v) error(%v)", sub, err)
return
}
s.dao.AddAdminLog(c, []int64{oid}, []int64{0}, adminID, typ, model.AdminIsNew, model.AdminIsNotReport, logState, fmt.Sprintf("修改监控状态为: %d", state), remark, now)
report.Manager(&report.ManagerInfo{
UID: adminID,
Uname: adName,
Business: 41,
Type: int(typ),
Oid: oid,
Ctime: now,
Action: model.ReportActionReplyMonitor,
Content: map[string]interface{}{
"remark": remark,
},
Index: []interface{}{sub.Mid, logState, state},
})
return
}
// MointorLog MointorLog
func (s *Service) MointorLog(c context.Context, sp model.LogSearchParam) (result *model.MonitorLogResult, err error) {
var (
mids []int64
userInfo map[int64]*accmdl.Info
)
adNames := map[int64]string{}
result = &model.MonitorLogResult{
Logs: []*model.MonitorLog{},
}
sp.Action = "monitor"
reportData, err := s.dao.ReportLog(c, sp)
if err != nil {
return
}
result.Page = reportData.Page
result.Sort = reportData.Sort
result.Order = reportData.Order
for i, data := range reportData.Result {
mid := data.Index0
reportData.Result[i].OidStr = strconv.FormatInt(reportData.Result[i].Oid, 10)
logState := data.Index1
state := 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
}
}
if data.AdminName == "" {
adNames[data.AdminID] = ""
}
result.Logs = append(result.Logs, &model.MonitorLog{
Mid: mid,
AdminID: data.AdminID,
AdminName: data.AdminName,
Oid: data.Oid,
OidStr: data.OidStr,
Type: data.Type,
Remark: extra["remark"],
CTime: data.Ctime,
LogState: logState,
State: state,
Title: title,
RedirectURL: link,
})
mids = append(mids, mid)
}
if len(adNames) > 0 {
s.dao.AdminName(c, adNames)
}
if len(mids) > 0 {
var infosReply *accmdl.InfosReply
infosReply, 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
}
userInfo = infosReply.Infos
}
for _, log := range result.Logs {
if user, ok := userInfo[log.Mid]; ok {
log.UserName = user.GetName()
}
if log.AdminName == "" {
log.AdminName = adNames[log.AdminID]
}
}
return
}

View File

@@ -0,0 +1,397 @@
package service
import (
"context"
"fmt"
"go-common/app/admin/main/reply/model"
artmdl "go-common/app/interface/openplatform/article/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"
"go-common/library/log"
"time"
"net/url"
"strconv"
)
// 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)
}
// 专门给喷子,带狗发送的消息通知, 20181206 针对大忽悠事件
func (s *Service) NotifyTroll(c context.Context, mid int64) (err error) {
var ok bool
if ok, err = s.dao.ExsistsDelMid(c, mid); err != nil || ok {
return
}
title := "评论处理通知"
msg := fmt.Sprintf("您好,根据#{关于规范“主播吴织亚切大忽悠事件”相关言论、信息发布的公告}{\"%s\"},您的相关评论已被清理。对于这一事件的讨论请移步公告中告知的区域进行讨论。", s.conf.Reply.Link)
if err = s.dao.SendReplyDelMsg(c, mid, title, msg, time.Now()); err != nil {
return
}
return s.dao.SetDelMid(c, mid)
}
// TitleLink TitleLink
func (s *Service) TitleLink(c context.Context, oid int64, typ int32) (title, link string, err error) {
switch typ {
case model.SubTypeArchive:
arg := &arcmdl.ArgAid2{
Aid: oid,
}
var m *api.Arc
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.RedirectURL != "" {
// NOTE mobile native jump
var uri *url.URL
if uri, err = url.Parse(m.RedirectURL); err == nil {
q := uri.Query()
q.Set("aid", strconv.FormatInt(oid, 10))
uri.RawQuery = q.Encode()
link = uri.String()
}
} else {
link = fmt.Sprintf("http://www.bilibili.com/video/av%d/", oid)
}
title = m.Title
case model.SubTypeTopic:
if title, link, err = s.dao.TopicTitle(c, oid); err != nil {
log.Error("s.noticeDao.Topic(%d) error(%v)", oid, err)
return
}
case model.SubTypeActivity:
if title, link, err = s.dao.TopicTitle(c, oid); err != nil {
log.Error("s.noticeDao.Activity(%d) error(%v)", oid, err)
return
}
case model.SubTypeForbiden:
title, link, err = s.dao.BanTitle(c, oid)
if err != nil {
return
}
case model.SubTypeNotice:
title, link, err = s.dao.NoticeTitle(c, oid)
if err != nil {
return
}
case model.SubTypeActArc:
if title, link, err = s.dao.TopicTitle(c, oid); err != nil {
log.Error("s.noticeDao.ActivitySub(%d) error(%v)", oid, err)
return
}
case model.SubTypeArticle:
arg := &artmdl.ArgAids{
Aids: []int64{oid},
}
var m map[int64]*artmdl.Meta
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[oid]; ok {
title = meta.Title
link = fmt.Sprintf("http://www.bilibili.com/read/cv%d", oid)
}
case model.SubTypeLiveVideo:
if title, link, err = s.dao.LiveVideoTitle(c, oid); err != nil {
log.Error("s.noticeDao.LiveSmallVideo(%d) error(%v)", oid, err)
return
}
case model.SubTypeLiveAct:
if title, link, err = s.dao.LiveActivityTitle(c, oid); err != nil {
log.Error("s.noticeDao.LiveActivity(%d) error(%v)", oid, err)
return
}
case model.SubTypeLiveNotice:
// NOTE 忽略直播公告跳转链接
return
case model.SubTypeLivePicture:
if title, link, err = s.dao.LivePictureTitle(c, oid); err != nil {
log.Error("s.noticeDao.LivePiture(%d) error(%v)", oid, err)
return
}
case model.SubTypeCredit:
if title, link, err = s.dao.CreditTitle(c, oid); err != nil {
log.Error("s.noticeDao.Credit(%d) error(%v)", oid, err)
return
}
case model.SubTypeDynamic:
if title, link, err = s.dao.DynamicTitle(c, oid); err != nil {
log.Error("s.noticeDao.Dynamic(%d) error(%v)", oid, err)
return
}
default:
return
}
return
}
func (s *Service) moralAndNotify(c context.Context, rp *model.Reply, moral int32, notify bool, rptMid, adid int64, adname, remark string, reason, freason int32, ftime int64, isPunish bool) {
var 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.ID, err)
}
}
if ftime != 0 {
if err = s.dao.BlockAccount(c, rp.Mid, ftime, notify, freason, title, rp.Content.Message, link, adname, remark); err != nil {
log.Error("s.reportNotify(%d,%d,%d) error(%v)", rp.Oid, rp.Type, rp.ID, err)
}
}
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.ID, 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.dao.SendReplyDelMsg(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.ID, reason, mc)
} else {
log.Warn("no notify oid:%d type:%d rpid:%d", rp.Oid, rp.Type, rp.ID)
}
}
func (s *Service) messageInfo(c context.Context, rp *model.Reply) (title, link, jump, msg string) {
tmpMsg := []rune(rp.Content.Message)
if len(tmpMsg) > 80 {
msg = string(tmpMsg[:80])
} else {
msg = rp.Content.Message
}
var err error
switch rp.Type {
case model.SubTypeArchive:
arg := &arcmdl.ArgAid2{
Aid: rp.Oid,
}
var m *api.Arc
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.RedirectURL != "" {
// NOTE mobile native jump
var uri *url.URL
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
case model.SubTypeTopic:
if title, link, err = s.dao.TopicTitle(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Topic(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeActivity:
if title, link, err = s.dao.TopicTitle(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Activity(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeForbiden:
title, link, err = s.dao.BanTitle(c, rp.Oid)
if err != nil {
return
}
case model.SubTypeNotice:
title, link, err = s.dao.NoticeTitle(c, rp.Oid)
if err != nil {
return
}
case model.SubTypeActArc:
if title, link, err = s.dao.TopicTitle(c, rp.Oid); err != nil {
log.Error("s.noticeDao.ActivitySub(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeArticle:
arg := &artmdl.ArgAids{
Aids: []int64{rp.Oid},
}
var m map[int64]*artmdl.Meta
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.dao.LiveVideoTitle(c, rp.Oid); err != nil {
log.Error("s.noticeDao.LiveSmallVideo(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeLiveAct:
if title, link, err = s.dao.LiveActivityTitle(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.dao.LivePictureTitle(c, rp.Oid); err != nil {
log.Error("s.noticeDao.LivePiture(%d) error(%v)", rp.Oid, err)
return
}
case model.SubTypeCredit:
if title, link, err = s.dao.CreditTitle(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.dao.DynamicTitle(c, rp.Oid); err != nil {
log.Error("s.noticeDao.Dynamic(%d) error(%v)", rp.Oid, err)
return
}
default:
return
}
tmp := []rune(title)
if len(tmp) > 40 {
title = string(tmp[:40])
}
jump = fmt.Sprintf("%s#reply%d", link, rp.ID)
log.Info("messageInfo(%d,%d) title:%s link:%s jump:%s msg:%s", rp.Type, rp.Oid, title, link, jump, msg)
return
}
func (s *Service) reportNotify(c context.Context, rp *model.Reply, title, link, msg string, ftime int64, reason, freason int32, 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.ReportUsers(c, rp.Oid, rp.Type, rp.ID); err != nil {
log.Error("s.dao.ReportUsers(%d,%d,%d) error(%v)", rp.Oid, rp.Type, rp.ID, err)
return
}
for _, rptUser = range rptUsers {
// send the message
if err = s.dao.SendReportAcceptMsg(c, rptUser.Mid, mt, mc, rp.MTime.Time()); err != nil {
log.Error("s.MessageAcceptReport failed, (%d) error(%v)", rp.Mid, err)
}
}
if _, err = s.dao.SetUserReported(c, rp.Oid, rp.Type, rp.ID, rp.MTime.Time()); err != nil {
log.Error("s.dao.SetUserReported(%d, %d, %d) error(%v)", rp.Oid, rp.Type, rp.ID)
}
return
}

View File

@@ -0,0 +1 @@
package service

View File

@@ -0,0 +1,142 @@
package service
import (
"context"
"fmt"
"go-common/app/admin/main/reply/model"
"go-common/library/ecode"
"go-common/library/log"
)
// ListNotice ListNotice
func (s *Service) ListNotice(c context.Context, page, pageSize int64) (nts []*model.Notice, total int64, err error) {
offset := (page - 1) * pageSize
total, err = s.dao.CountNotice(c)
if err != nil {
return
}
nts, err = s.dao.ListNotice(c, offset, pageSize)
if err != nil {
return
}
return nts, total, err
}
// GetNotice GetNotice
func (s *Service) GetNotice(c context.Context, id uint32) (nt *model.Notice, err error) {
return s.dao.Notice(c, id)
}
// UpdateNotice UpdateNotice
func (s *Service) UpdateNotice(c context.Context, nt *model.Notice) (err error) {
var ntGet *model.Notice
ntGet, err = s.dao.Notice(c, nt.ID)
if err != nil || ntGet == nil {
err = ecode.NothingFound
return
}
if ntGet.Status == model.StatusOnline {
err = s.checkConflict(c, nt)
if err != nil {
return
}
}
_, err = s.dao.UpdateNotice(c, nt)
return
}
// CreateNotice CreateNotice
func (s *Service) CreateNotice(c context.Context, nt *model.Notice) (lastID int64, err error) {
lastID, err = s.dao.CreateNotice(c, nt)
if lastID <= 0 {
log.Error("create notice failed!last_id not found")
err = fmt.Errorf("create notice failed!last_id not found")
return
}
return
}
// DeleteNotice DeleteNotice
func (s *Service) DeleteNotice(c context.Context, id uint32) (err error) {
_, err = s.dao.DeleteNotice(c, id)
return err
}
func (s *Service) checkConflict(c context.Context, nt *model.Notice) error {
var nts []*model.Notice
var err error
nts, err = s.dao.RangeNotice(c, nt.Plat, nt.StartTime, nt.EndTime)
if err != nil {
return err
}
for _, data := range nts {
//如果ID相同说明是自己
if data.ID == nt.ID {
continue
}
//如果为web平台则必然冲突
if data.Plat == model.PlatWeb {
return ecode.ReplyNoticeConflict
}
//如果客户端类型不同则跳过检查
if data.ClientType != "" && nt.ClientType != "" && data.ClientType != nt.ClientType {
continue
}
//为每一种版本情况检查
if data.Condition == model.ConditionEQ {
if nt.Condition == model.ConditionEQ && nt.Build == data.Build {
return ecode.ReplyNoticeConflict
}
if nt.Condition == model.ConditionGT && nt.Build <= data.Build {
return ecode.ReplyNoticeConflict
}
if nt.Condition == model.ConditionLT && nt.Build >= data.Build {
return ecode.ReplyNoticeConflict
}
} else if data.Condition == model.ConditionGT {
if nt.Condition == model.ConditionEQ {
if nt.Build >= data.Build {
return ecode.ReplyNoticeConflict
}
} else if nt.Condition == model.ConditionLT {
if nt.Build >= data.Build {
return ecode.ReplyNoticeConflict
}
} else {
return ecode.ReplyNoticeConflict
}
} else if data.Condition == model.ConditionLT {
if nt.Condition == model.ConditionEQ {
if nt.Build <= data.Build {
return ecode.ReplyNoticeConflict
}
} else if nt.Condition == model.ConditionGT {
if nt.Build <= data.Build {
return ecode.ReplyNoticeConflict
}
} else {
return ecode.ReplyNoticeConflict
}
}
}
return nil
}
// UpdateNoticeStatus UpdateNoticeStatus
func (s *Service) UpdateNoticeStatus(c context.Context, status model.NoticeStatus, id uint32) (err error) {
//检测客户端在同一时间内是否存在另外一条已发布的公告,如果存在则不允许发布
if status == model.StatusOnline {
var nt *model.Notice
nt, err = s.dao.Notice(c, id)
if err != nil || nt == nil {
return
}
err = s.checkConflict(c, nt)
if err != nil {
return
}
}
_, err = s.dao.UpdateNoticeStatus(c, status, id)
return err
}

View File

@@ -0,0 +1,58 @@
package service
import (
"context"
"testing"
"time"
"go-common/app/admin/main/reply/model"
xtime "go-common/library/time"
. "github.com/smartystreets/goconvey/convey"
)
var nowTs = time.Now().Unix()
func Test_OnlineNoticeConflict(t *testing.T) {
c := context.Background()
nt := model.Notice{
Plat: model.PlatAndroid,
Condition: model.ConditionGT,
Build: 1113,
Title: "测试",
Status: model.StatusOffline,
Content: "测试内容",
Link: "http://www.bilibili.com",
StartTime: xtime.Time(nowTs),
EndTime: xtime.Time(nowTs + 10*3600),
ClientType: "",
}
nt2 := model.Notice{
Plat: model.PlatAndroid,
Condition: model.ConditionGT,
Build: 1000,
Title: "测试2",
Status: model.StatusOffline,
Content: "测试内容2",
Link: "http://www.bilibili.com",
StartTime: xtime.Time(nowTs - 5*3600),
EndTime: xtime.Time(nowTs + 5*3600),
ClientType: "android",
}
Convey("test notice data conflict ", t, WithService(func(s *Service) {
id1, err := s.CreateNotice(c, &nt)
So(err, ShouldBeNil)
id2, err := s.CreateNotice(c, &nt2)
So(err, ShouldBeNil)
defer func() {
s.DeleteNotice(c, uint32(id1))
s.DeleteNotice(c, uint32(id2))
}()
err = s.UpdateNoticeStatus(c, model.StatusOnline, uint32(id1))
So(err, ShouldBeNil)
err = s.UpdateNoticeStatus(c, model.StatusOnline, uint32(id2))
So(err, ShouldNotBeNil)
}))
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,118 @@
package service
import (
"context"
"encoding/json"
"go-common/app/admin/main/reply/conf"
"go-common/app/admin/main/reply/model"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestReply(t *testing.T) {
c := context.Background()
Convey("reply del and recover", t, WithService(func(s *Service) {
sub, err := s.subject(c, 5464686, 1)
So(err, ShouldBeNil)
rp, err := s.reply(c, 5464686, 894717392)
So(err, ShouldBeNil)
err = s.adminDeleteReply(c, 23, []int64{5464686, 5464686}, []int64{894733824, 894733816}, 0, 1, 0, true, "test", "test", 0, 0)
So(err, ShouldBeNil)
rp2, err := s.reply(c, 5464686, 894733824)
So(err, ShouldBeNil)
So(rp2.State, ShouldEqual, 3)
rp2, err = s.reply(c, 5464686, 894733816)
So(err, ShouldBeNil)
So(rp2.State, ShouldEqual, 3)
rp2, err = s.reply(c, 5464686, 894717392)
So(err, ShouldBeNil)
So((rp2.RCount - rp.RCount), ShouldEqual, -2)
sub2, err := s.subject(c, 5464686, 1)
So(err, ShouldBeNil)
So((sub2.ACount - sub.ACount), ShouldEqual, -2)
err = s.AdminRecoverReply(c, 23, "asd", 5464686, 894733824, 1, "test")
So(err, ShouldBeNil)
err = s.AdminRecoverReply(c, 23, "sad", 5464686, 894733816, 1, "test")
So(err, ShouldBeNil)
rp2, err = s.reply(c, 5464686, 894733824)
So(err, ShouldBeNil)
So(rp2.State, ShouldEqual, 0)
rp2, err = s.reply(c, 5464686, 894733816)
So(err, ShouldBeNil)
So(rp2.State, ShouldEqual, 0)
rp2, err = s.reply(c, 5464686, 894717392)
So(err, ShouldBeNil)
So((rp2.RCount - rp.RCount), ShouldEqual, 0)
sub2, err = s.subject(c, 5464686, 1)
So(err, ShouldBeNil)
So((sub2.ACount - sub.ACount), ShouldEqual, 0)
alog, err := s.dao.AdminLog(c, 894733824)
So(err, ShouldBeNil)
So(alog.State, ShouldEqual, 3)
alog, err = s.dao.AdminLog(c, 894733816)
So(err, ShouldBeNil)
So(alog.State, ShouldEqual, 3)
}))
Convey("reply top", t, WithService(func(s *Service) {
err := s.AddTop(c, 23, "asd", 5464686, 894717392, 1, 1)
So(err, ShouldBeNil)
rp, err := s.reply(c, 5464686, 894717392)
So(err, ShouldBeNil)
So(rp.IsTop(), ShouldBeTrue)
sub, err := s.subject(c, 5464686, 1)
So(err, ShouldBeNil)
So(sub.AttrVal(model.SubAttrTopAdmin), ShouldEqual, model.AttrYes)
err = s.AddTop(c, 23, "asd", 5464686, 894717392, 1, 0)
So(err, ShouldBeNil)
rp, err = s.reply(c, 5464686, 894717392)
So(err, ShouldBeNil)
So(rp.IsTop(), ShouldBeFalse)
sub, err = s.subject(c, 5464686, 1)
So(err, ShouldBeNil)
So(sub.AttrVal(model.SubAttrTopAdmin), ShouldEqual, model.AttrNo)
}))
Convey("reply pass", t, WithService(func(s *Service) {
s.dao.UpdateReplyState(c, 5464686, 894717384, model.StatePending)
err := s.adminPassReply(c, 23, "sds", []int64{5464686}, []int64{894717384}, 1, "test")
So(err, ShouldBeNil)
rp, err := s.reply(c, 5464686, 894717392)
So(err, ShouldBeNil)
So(rp.State, ShouldEqual, model.StateNormal)
}))
}
func TestAddReplyConfig(t *testing.T) {
var (
bs []byte
err error
id int64
typ = int32(1)
oid = int64(1)
adminID = int64(1)
category = int32(1)
operator = string("管理员001")
c = context.Background()
config = &model.Config{}
)
s := New(conf.Conf)
config.Oid = oid
config.Type = typ
config.Category = category
config.AdminID = adminID
config.Operator = operator
configValue := map[string]int64{
"showentry": 0,
"showadmin": 1,
}
if bs, err = json.Marshal(configValue); err == nil {
config.Config = string(bs)
}
if id, err = s.AddReplyConfig(c, config); err != nil {
t.Errorf("d.AddConfig error(%v)", err)
}
t.Logf("d.AddReplyConfig result(%d)", id)
}

View File

@@ -0,0 +1,505 @@
package service
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"go-common/app/admin/main/reply/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/queue/databus/report"
"go-common/library/sync/errgroup"
xtime "go-common/library/time"
)
func forbidResult(ftime int64) string {
if ftime == -1 {
return "永久"
}
return fmt.Sprintf("%d天", ftime)
}
func (s *Service) report(c context.Context, oid, rpID int64) (report *model.Report, err error) {
if report, err = s.dao.Report(c, oid, rpID); err != nil {
return
}
if report == nil {
err = ecode.ReplyReportNotExist
}
return
}
func (s *Service) reports(c context.Context, oids, rpIDs []int64) (res map[int64]*model.Report, err error) {
if res, err = s.dao.Reports(c, oids, rpIDs); err != nil {
return
}
return
}
// ReportSearch return report result from search.
func (s *Service) ReportSearch(c context.Context, sp *model.SearchReportParams, page, pageSize int64) (res *model.SearchReportResult, err error) {
if res, err = s.dao.SearchReport(c, sp, page, pageSize); err != nil {
log.Error("s.dao.SearchReport(%+v,%d,%d) error(%v)", sp, page, pageSize, err)
return
}
filterIds := map[int64]string{}
oids := map[int64]string{}
admins := map[int64]string{}
titles := map[int64]string{}
for _, r := range res.Result {
r.OidStr = strconv.FormatInt(r.Oid, 10)
if strings.Contains(r.Message, "*") {
filterIds[r.ID] = ""
} else if len(r.Attr) > 0 {
for _, attr := range r.Attr {
if attr == 4 {
filterIds[r.ID] = ""
}
}
}
admins[r.AdminID] = ""
if r.ReplyMid == r.ArcMid {
r.IsUp = 1
}
oids[r.Oid] = ""
if int32(r.Type) == model.SubTypeArchive {
titles[r.Oid] = ""
}
}
s.linkByOids(c, oids, sp.Type)
s.titlesByOids(c, titles)
s.dao.AdminName(c, admins)
s.dao.FilterContents(c, filterIds)
for i, data := range res.Result {
res.Result[i].AdminName = admins[res.Result[i].AdminID]
if content, ok := filterIds[data.ID]; ok && content != "" {
data.Message = content
}
if content, ok := oids[data.Oid]; ok {
data.RedirectURL = fmt.Sprintf("%s#reply%d", content, data.ID)
}
if int32(data.Type) == model.SubTypeArchive && data.Title == "" {
if title := titles[data.Oid]; title != "" {
data.Title = title
}
}
}
return
}
// ReportIgnore ignore a report.
func (s *Service) ReportIgnore(c context.Context, oids, rpIDs []int64, adminID int64, adName string, typ, audit int32, remark string, delReport bool) (err error) {
var (
state int32
op int32
result string
action string
)
if audit == model.AuditTypeFirst {
result = "一审忽略"
op = model.AdminOperRptIgnore1
state = model.ReportStateIgnore1
action = model.ReportActionReportIgnore1
} else if audit == model.AuditTypeSecond {
result = "二审忽略"
op = model.AdminOperRptIgnore2
state = model.ReportStateIgnore2
action = model.ReportActionReportIgnore2
} else {
err = ecode.RequestErr
return
}
subs, err := s.subjects(c, oids, typ)
if err != nil {
log.Error("ReportIgnore subjects(%v,%v,%d) error(%v)", oids, typ, state, err)
return
}
rps, err := s.replies(c, oids, rpIDs)
if err != nil {
log.Error("ReportIgnore replies(%v,%v,%d) error(%v)", oids, rpIDs, state, err)
return
}
rpts, err := s.reports(c, oids, rpIDs)
if err != nil {
log.Error("ReportIgnore reports(%v,%v,%d) error(%v)", oids, rpIDs, state, err)
return
}
now := time.Now()
rows, err := s.dao.UpReportsState(c, oids, rpIDs, state, now)
if err != nil {
log.Error("ReportIgnore UpReportsState(%v,%v,%d) rows:%d error(%v)", oids, rpIDs, state, rows, err)
return
} else if rows == 0 {
return
}
for _, rp := range rps {
rpt, ok := rpts[rp.ID]
if !ok {
continue
}
sub, ok := subs[rpt.Oid]
if !ok {
continue
}
s.pubEvent(c, model.EventReportDel, rpt.Mid, sub, rp, rpt)
}
for _, rpt := range rpts {
report.Manager(&report.ManagerInfo{
UID: adminID,
Uname: adName,
Business: 41,
Type: int(typ),
Oid: rpt.Oid,
Ctime: now,
Action: action,
Index: []interface{}{
rpt.RpID,
rpt.State,
state,
},
Content: map[string]interface{}{
"remark": remark,
},
})
rpt.State = state
rpt.MTime = xtime.Time(now.Unix())
if delReport {
s.cache.Do(c, func(ctx context.Context) {
s.dao.DelReport(ctx, rpt.Oid, rpt.RpID)
})
}
}
s.addAdminLogs(c, rps, adminID, typ, model.AdminIsNew, model.AdminIsReport, op, result, remark, now)
s.cache.Do(c, func(ctx context.Context) {
s.pubSearchReport(ctx, rpts, nil)
})
return
}
// ReportDel delete reply by report.
func (s *Service) ReportDel(c context.Context, oids, rpIDs []int64, adminID, ftime int64, typ, audit, moral, reason, freason int32, notify bool, adminName, remark, content string) (err error) {
return s.reportDel(c, oids, rpIDs, adminID, ftime, typ, audit, moral, reason, freason, notify, adminName, remark, content)
}
func (s *Service) reportDel(c context.Context, oids, rpIDs []int64, adminID, ftime int64, typ, audit, moral, reason, freason int32, notify bool, adminName, remark, content string) (err error) {
var (
op int32
rptState int32
action string
)
if audit == model.AuditTypeFirst {
op = model.AdminOperRptDel1
rptState = model.ReportStateDelete1
action = model.ReportActionReportDel1
} else if audit == model.AuditTypeSecond {
op = model.AdminOperRptDel2
rptState = model.ReportStateDelete2
action = model.ReportActionReportDel2
} else {
err = ecode.RequestErr
return
}
now := time.Now()
rpts, err := s.reports(c, oids, rpIDs)
if err != nil {
log.Error("reportDel reports(%v, %v) error(%v)", oids, rpIDs, err)
return
}
wg, ctx := errgroup.WithContext(c)
for _, rept := range rpts {
rpt := rept
wg.Go(func() (err error) {
isPunish := false
var sub *model.Subject
var rp *model.Reply
sub, rp, err = s.delReply(ctx, rpt.Oid, rpt.RpID, model.StateDelAdmin, now)
if err != nil {
if ecode.ReplyDeleted.Equal(err) && rp.IsDeleted() {
isPunish = true
err = nil
} else {
log.Error("reportDel tranDel(%d, %d) error(%v)", rpt.Oid, rpt.RpID, err)
return err
}
}
s.delCache(ctx, sub, rp)
rea := reason
if reason == -1 {
rea = rpt.Reason
}
report.Manager(&report.ManagerInfo{
UID: adminID,
Uname: adminName,
Business: 41,
Type: int(typ),
Oid: rpt.Oid,
Ctime: now,
Action: action,
Index: []interface{}{
rpt.RpID,
rpt.State,
rptState,
},
Content: map[string]interface{}{
"moral": moral,
"notify": notify,
"ftime": ftime,
"freason": freason,
"reason": rea,
"remark": remark,
},
})
rpt.State = rptState
rpt.MTime = xtime.Time(now.Unix())
rpt.ReplyCtime = rp.CTime
s.pubEvent(ctx, model.EventReportDel, rpt.Mid, sub, rp, rpt)
s.cache.Do(c, func(ctx context.Context) {
s.dao.DelReport(ctx, rp.Oid, rp.ID)
s.moralAndNotify(ctx, rp, moral, notify, rp.Mid, adminID, adminName, remark, rea, freason, ftime, isPunish)
})
return nil
})
}
if err = wg.Wait(); err != nil {
return
}
var rows int64
if reason == -1 {
rows, err = s.dao.UpReportsState(c, oids, rpIDs, rptState, now)
} else {
rows, err = s.dao.UpReportsStateWithReason(c, oids, rpIDs, rptState, reason, content, now)
}
if err != nil || rows == 0 {
return
}
s.cache.Do(c, func(ctx context.Context) {
state := model.StateDelAdmin
s.pubSearchReport(ctx, rpts, &state)
})
s.addAdminIDLogs(c, oids, rpIDs, adminID, typ, model.AdminIsNew, model.AdminIsReport, op, fmt.Sprintf("已通过举报并删除封禁%s/扣除%d节操", forbidResult(ftime), moral), remark, now)
return
}
// ReportRecover recover a report reply.
func (s *Service) ReportRecover(c context.Context, oids, rpIDs []int64, adminID int64, typ, audit int32, remark string) (err error) {
s.reportRecover(c, oids, rpIDs, adminID, typ, audit, remark)
return
}
func (s *Service) reportRecover(c context.Context, oids, rpIDs []int64, adminID int64, typ, audit int32, remark string) (err error) {
var (
rptState int32
op int32
result string
)
if audit == model.AuditTypeFirst {
result = "一审恢复评论"
op = model.AdminOperRptRecover1
rptState = model.ReportStateIgnore1
} else if audit == model.AuditTypeSecond {
result = "二审恢复评论"
op = model.AdminOperRptRecover2
rptState = model.ReportStateIgnore2
} else {
err = ecode.RequestErr
return
}
now := time.Now()
rpts, err := s.reports(c, oids, rpIDs)
if err != nil {
log.Error("ReportRecover reports(%v, %v) error(%v)", oids, rpIDs, err)
return
}
var rps = map[int64]*model.Reply{}
wg, ctx := errgroup.WithContext(c)
for _, report := range rpts {
rpt := report
wg.Go(func() (err error) {
sub, rp, err := s.recReply(ctx, rpt.Oid, rpt.RpID, model.StateNormal, now)
if err != nil {
log.Error("ReportRecover tranRecover(%d, %d) error(%v)", rpt.Oid, rpt.RpID, err)
return
}
rpt.MTime = xtime.Time(now.Unix())
rpt.State = rptState
s.pubEvent(ctx, model.EventReportRecover, rpt.Mid, sub, rp, rpt)
rps[rp.ID] = rp
return
})
}
if err = wg.Wait(); err != nil {
return
}
rows, err := s.dao.UpReportsState(c, oids, rpIDs, rptState, now)
if err != nil {
return
} else if rows == 0 {
return
}
s.addAdminIDLogs(c, oids, rpIDs, adminID, typ, model.AdminIsNew, model.AdminIsReport, op, result, remark, now)
s.cache.Do(c, func(ctx context.Context) {
s.pubSearchReply(ctx, rps, model.StateNormal)
})
return
}
// ReportTransfer transfer a report.
func (s *Service) ReportTransfer(c context.Context, oids, rpIDs []int64, adminID int64, adName string, typ, audit int32, remark string) (err error) {
var (
state int32
op int32
result string
action string
)
if audit == model.AuditTypeFirst {
result = "二审转一审"
op = model.AdminOperRptTransfer1
state = model.ReportStateNew
action = model.ReportActionReport2To1
} else if audit == model.AuditTypeSecond {
result = "一审转二审"
op = model.AdminOperRptTransfer2
state = model.ReportStateNew2
action = model.ReportActionReport1To2
} else {
err = ecode.RequestErr
return
}
rpts, err := s.reports(c, oids, rpIDs)
if err != nil {
return
}
now := time.Now()
var tranOids, tranRpIDs []int64
var tranRpt []*model.Report
for _, rpt := range rpts {
if rpt.AttrVal(model.ReportAttrTransferred) == model.AttrNo {
rpt.State = state
rpt.MTime = xtime.Time(now.Unix())
tranOids = append(tranOids, rpt.Oid)
tranRpIDs = append(tranRpIDs, rpt.RpID)
tranRpt = append(tranRpt, rpt)
}
}
rows, err := s.dao.UpReportsState(c, tranOids, tranRpIDs, state, now)
if err != nil {
log.Error("s.dao.UpReportState(%v,%v,%d) rows:%d error(%v)", oids, rpIDs, state, rows, err)
return
} else if rows == 0 {
return
}
for _, rpt := range tranRpt {
report.Manager(&report.ManagerInfo{
UID: adminID,
Uname: adName,
Business: 41,
Type: int(typ),
Oid: rpt.Oid,
Ctime: now,
Action: action,
Index: []interface{}{
rpt.RpID,
rpt.State,
state,
},
Content: map[string]interface{}{
"remark": remark,
},
})
}
if _, err = s.dao.UpReportsAttrBit(c, tranOids, tranRpIDs, model.ReportAttrTransferred, model.AttrYes, now); err != nil {
log.Error("s.dao.UpReportAttrBit(%v,%v) transfered errror(%v)", tranOids, tranRpIDs, err)
return
}
s.addAdminIDLogs(c, oids, rpIDs, adminID, typ, model.AdminIsNew, model.AdminIsReport, op, result, remark, now)
s.cache.Do(c, func(ctx context.Context) {
s.pubSearchReport(ctx, rpts, nil)
})
return
}
// ReportStateSet transfer a report.
func (s *Service) ReportStateSet(c context.Context, oids, rpIDs []int64, adminID int64, adname string, typ, state int32, remark string, delReport bool) (err error) {
if state != model.ReportStateTransferred {
return
}
var (
op int32
result string
action string
)
op = model.AdminOperRptTransferArbitration
action = model.ReportActionReportArbitration
if adminID == 0 {
result = "系统自动移交至风纪委"
} else {
result = "管理员移交至风纪委"
}
rpts, err := s.reports(c, oids, rpIDs)
if err != nil {
return
}
rps, err := s.replies(c, oids, rpIDs)
if err != nil {
log.Error("s.replies (%v,%v) error(%v)", oids, rpIDs, err)
return
}
links := make(map[int64]string, 0)
titles := make(map[int64]string, 0)
for _, rp := range rps {
title, link, _ := s.TitleLink(c, rp.Oid, rp.Type)
links[rp.ID] = link
titles[rp.ID] = title
}
err = s.dao.TransferArbitration(c, rps, rpts, adminID, adname, titles, links)
if err != nil {
log.Error("s.dao.TransferArbitration (%v,%v) error(%v)", rps, rpts, err)
return
}
mtime := time.Now()
for _, rpt := range rpts {
report.Manager(&report.ManagerInfo{
UID: adminID,
Uname: adname,
Business: 41,
Type: int(typ),
Oid: rpt.Oid,
Ctime: mtime,
Action: action,
Index: []interface{}{
rpt.RpID,
rpt.State,
state,
},
Content: map[string]interface{}{
"remark": remark,
},
})
rpt.State = state
rpt.MTime = xtime.Time(mtime.Unix())
if delReport {
s.cache.Do(c, func(ctx context.Context) {
s.dao.DelReport(ctx, rpt.Oid, rpt.RpID)
})
}
}
rows, err := s.dao.UpReportsState(c, oids, rpIDs, state, mtime)
if err != nil {
log.Error("s.dao.UpReportState(%v,%v,%d) rows:%d error(%v)", oids, rpIDs, state, rows, err)
return
} else if rows == 0 {
return
}
s.addAdminIDLogs(c, oids, rpIDs, adminID, typ, model.AdminIsNew, model.AdminIsReport, op, result, remark, mtime)
s.cache.Do(c, func(ctx context.Context) {
s.pubSearchReport(ctx, rpts, nil)
})
return
}

View File

@@ -0,0 +1,66 @@
package service
import (
"context"
"time"
"go-common/app/admin/main/reply/model"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestReport(t *testing.T) {
c := context.Background()
Convey("report del and recover", t, WithService(func(s *Service) {
sub, err := s.subject(c, 5464686, 1)
So(err, ShouldBeNil)
err = s.reportDel(c, []int64{5464686}, []int64{894726080}, 23, 0, 1, 2, 0, 0, 0, false, "testadmin", "test", "content")
So(err, ShouldBeNil)
rpt, err := s.report(c, 5464686, 894726080)
So(err, ShouldBeNil)
So(rpt.State, ShouldEqual, model.ReportStateDelete2)
rp, err := s.reply(c, 5464686, 894726080)
So(err, ShouldBeNil)
So(rp.State, ShouldEqual, model.StateDelAdmin)
sub2, err := s.subject(c, 5464686, 1)
So(err, ShouldBeNil)
So(sub2.RCount-sub.RCount, ShouldEqual, -1)
So(sub2.ACount-sub.ACount, ShouldEqual, -1)
alog, err := s.dao.AdminLog(c, 894726080)
So(err, ShouldBeNil)
So(alog.State, ShouldEqual, model.AdminOperRptDel2)
err = s.reportRecover(c, []int64{5464686}, []int64{894726080}, 23, 1, 2, "test")
So(err, ShouldBeNil)
rpt, err = s.report(c, 5464686, 894726080)
So(err, ShouldBeNil)
So(rpt.State, ShouldEqual, model.ReportStateIgnore2)
rp, err = s.reply(c, 5464686, 894726080)
So(err, ShouldBeNil)
So(rp.State, ShouldEqual, model.StateNormal)
sub2, err = s.subject(c, 5464686, 1)
So(err, ShouldBeNil)
So(sub2.RCount-sub.RCount, ShouldEqual, 0)
So(sub2.ACount-sub.ACount, ShouldEqual, 0)
alog, err = s.dao.AdminLog(c, 894726080)
So(err, ShouldBeNil)
So(alog.State, ShouldEqual, model.AdminOperRptRecover2)
}))
Convey("reply transfer", t, WithService(func(s *Service) {
err := s.ReportTransfer(c, []int64{5464686}, []int64{894726080}, 23, "asd", 1, 2, "test")
So(err, ShouldBeNil)
rpt, err := s.report(c, 5464686, 894726080)
So(err, ShouldBeNil)
So(rpt.State, ShouldEqual, model.ReportStateNew2)
So(rpt.AttrVal(model.ReportAttrTransferred), ShouldEqual, model.AttrYes)
s.dao.UpReportsAttrBit(c, []int64{5464686}, []int64{894726080}, model.ReportAttrTransferred, model.AttrNo, time.Now())
}))
Convey("reply ignore", t, WithService(func(s *Service) {
err := s.ReportIgnore(c, []int64{5464686}, []int64{894726080}, 23, "asd", 1, 2, "test", false)
rpt, err := s.report(c, 5464686, 894726080)
So(err, ShouldBeNil)
So(rpt.State, ShouldEqual, model.ReportStateIgnore2)
}))
}

View File

@@ -0,0 +1,82 @@
package service
import (
"context"
"fmt"
"go-common/app/admin/main/reply/conf"
"go-common/app/admin/main/reply/dao"
artrpc "go-common/app/interface/openplatform/article/rpc/client"
accrpc "go-common/app/service/main/account/api"
arcrpc "go-common/app/service/main/archive/api/gorpc"
rlrpc "go-common/app/service/main/relation/rpc/client"
thumbup "go-common/app/service/main/thumbup/api"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/rpc/warden"
"go-common/library/sync/pipeline/fanout"
)
// Service is a service.
type Service struct {
conf *conf.Config
dao *dao.Dao
httpClient *bm.Client
accSrv accrpc.AccountClient
arcSrv *arcrpc.Service2
articleSrv *artrpc.Service
cache *fanout.Fanout
thumbupClient thumbup.ThumbupClient
relationSvc *rlrpc.Service
// 特殊admin针对大忽悠事件消息通知
ads map[string]struct{}
// 特殊稿件,针对大忽悠时间 不让删评论
oids map[int64]int32
// mark folded or unmark folded worker
marker *fanout.Fanout
// del cache or add cache worker
cacheOperater *fanout.Fanout
}
// New new a service and return.
func New(c *conf.Config) (s *Service) {
s = &Service{
conf: c,
dao: dao.New(c),
httpClient: bm.NewClient(c.HTTPClient),
arcSrv: arcrpc.New2(c.RPCClient2.Archive),
articleSrv: artrpc.New(c.RPCClient2.Article),
cache: fanout.New("cache", fanout.Worker(1), fanout.Buffer(1024*10)),
relationSvc: rlrpc.New(c.RPCClient2.Relation),
marker: fanout.New("marker", fanout.Worker(1), fanout.Buffer(1024*10)),
cacheOperater: fanout.New("cacheOp", fanout.Worker(1), fanout.Buffer(1024*10)),
}
accSrv, err := accrpc.NewClient(c.AccountClient)
if err != nil {
panic(err)
}
s.accSrv = accSrv
cc, err := warden.NewConn(fmt.Sprintf("discovery://default/%s", thumbup.AppID))
if err != nil {
panic(err)
}
s.thumbupClient = thumbup.NewThumbupClient(cc)
ads := make(map[string]struct{})
for _, ID := range c.Reply.AdminName {
ads[ID] = struct{}{}
}
s.ads = ads
oids := make(map[int64]int32)
for i, oid := range c.Reply.Oids {
oids[oid] = c.Reply.Tps[i]
}
s.oids = oids
return
}
// Ping check service is ok.
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}

View File

@@ -0,0 +1,37 @@
package service
import (
"flag"
"os"
"testing"
"time"
"go-common/app/admin/main/reply/conf"
"go-common/library/log"
_ "github.com/go-sql-driver/mysql"
)
var svr *Service
func TestMain(m *testing.M) {
flag.Parse()
conf.ConfPath = "../cmd/reply-admin-test.toml"
if err := conf.Init(); err != nil {
panic(err)
}
log.Init(conf.Conf.Log)
defer log.Close()
svr = New(conf.Conf)
time.Sleep(time.Millisecond * 300)
m.Run()
//flush db and log
time.Sleep(time.Millisecond * 300)
os.Exit(0)
}
func WithService(f func(s *Service)) func() {
return func() {
f(svr)
}
}

View File

@@ -0,0 +1,217 @@
package service
import (
"context"
"encoding/json"
"strconv"
"time"
"go-common/library/queue/databus/report"
"go-common/app/admin/main/reply/model"
"go-common/library/ecode"
"go-common/library/log"
)
func (s *Service) subject(c context.Context, oid int64, typ int32) (sub *model.Subject, err error) {
if sub, err = s.dao.Subject(c, oid, typ); err != nil {
return
}
if sub == nil {
err = ecode.NothingFound
}
return
}
func (s *Service) subjects(c context.Context, oids []int64, typ int32) (res map[int64]*model.Subject, err error) {
return s.dao.Subjects(c, oids, typ)
}
// Subject get subject info.
func (s *Service) Subject(c context.Context, oid int64, typ int32) (sub *model.Subject, err error) {
if sub, err = s.dao.Subject(c, oid, typ); err != nil {
return
}
// NOTE nothing found return to normal
return
}
// ModifySubState modify subject state
func (s *Service) ModifySubState(c context.Context, adid int64, adName string, oids []int64, typ int32, state int32, remark string) (fails map[int64]string, err error) {
var (
action string
sub *model.Subject
now = time.Now()
)
switch state {
case model.SubStateNormal:
action = model.SujectAllow
case model.SubStateForbid:
action = model.SujectForbid
default:
err = ecode.ReplyIllegalSubState
return
}
fails = make(map[int64]string)
for _, oid := range oids {
sub, err = s.subject(c, oid, typ)
if err != nil {
log.Error("rpSvr.subject(oid,%d,type,%d)error(%v)", oid, typ, err)
fails[oid] = ecode.Cause(err).Message()
err = nil
continue
}
// subject frozen
if sub.AttrVal(model.SubAttrFrozen) == model.AttrYes {
fails[oid] = ecode.ReplySubjectFrozen.Message()
continue
}
if _, err = s.dao.UpSubjectState(c, oid, typ, state, now); err != nil {
log.Error("s.UpSubjectState(%d,%d) error(%v)", oid, typ, err)
return
}
if err = s.dao.DelSubjectCache(c, oid, typ); err != nil {
log.Error("s.dao.DeleteSubjectCache(%d,%d) state:%d error(%v)", oid, typ, err)
}
report.Manager(&report.ManagerInfo{
UID: adid,
Uname: adName,
Business: 41,
Type: int(typ),
Oid: oid,
Ctime: now,
Action: action,
Index: []interface{}{sub.State, state},
Content: map[string]interface{}{"remark": remark},
})
}
return
}
// FreezeSub freeze or unfreeze subject
func (s *Service) FreezeSub(c context.Context, adid int64, adName string, oids []int64, typ int32, freeze int32, remark string) (fails map[int64]string, err error) {
var (
state int32
action string
sub *model.Subject
now = time.Now()
)
fails = make(map[int64]string)
for _, oid := range oids {
if sub, err = s.subject(c, oid, typ); err != nil {
log.Error("rpSvr.subject(oid,%d,type,%d)error(%v)", oid, typ, err)
fails[oid] = ecode.Cause(err).Message()
err = nil
continue
}
// already freeze or already unfreeze
if (sub.AttrVal(model.SubAttrFrozen) == model.AttrYes && freeze == 2) || (sub.AttrVal(model.SubAttrFrozen) == model.AttrNo && freeze != 2) {
continue
}
switch freeze {
case 0:
// unfreeze and allow
sub.AttrSet(model.AttrNo, model.SubAttrFrozen)
state = model.SubStateNormal
action = model.SujectUnfrozenAllow
case 1:
// unfreeze and forbid
sub.AttrSet(model.AttrNo, model.SubAttrFrozen)
state = model.SubStateForbid
action = model.SujectUnfrozenForbid
case 2:
// freeze and forbid
sub.AttrSet(model.AttrYes, model.SubAttrFrozen)
state = model.SubStateForbid
action = model.SujectFrozen
default:
err = ecode.ReplyIllegalSubState
return
}
if _, err = s.dao.UpStateAndAttr(c, oid, typ, state, sub.Attr, now); err != nil {
log.Error("s.UpSubjectAttr(%d,%d,%d,%d) error(%v)", oid, typ, sub.Attr, err)
return
}
if err = s.dao.DelSubjectCache(c, oid, typ); err != nil {
log.Error("s.dao.DeleteSubjectCache(%d,%d) state:%d error(%v)", oid, typ, err)
}
report.Manager(&report.ManagerInfo{
UID: adid,
Uname: adName,
Business: 41,
Type: int(typ),
Oid: oid,
Ctime: now,
Action: action,
Index: []interface{}{sub.State, state},
Content: map[string]interface{}{"remark": remark},
})
}
return
}
// SubjectLog returns operation logs by query parameters
func (s *Service) SubjectLog(c context.Context, sp model.LogSearchParam) (res *model.SubjectLogRes, err error) {
res = &model.SubjectLogRes{
Logs: []*model.SubjectLog{},
}
sp.Action = "subject_allow,subject_forbid,subject_frozen,subject_unfrozen_allow,subject_unfrozen_forbid"
if sp.Pn <= 0 || sp.Ps <= 0 {
return nil, ecode.RequestErr
}
reportData, err := s.dao.ReportLog(c, sp)
if err != nil || reportData == nil {
return
}
res.Page = reportData.Page
res.Sort = reportData.Sort
res.Order = reportData.Order
exists := make(map[int64]bool)
var oids []int64
for _, data := range reportData.Result {
if !exists[data.Oid] {
exists[data.Oid] = true
oids = append(oids, data.Oid)
}
var extra map[string]string
err = json.Unmarshal([]byte(data.Content), &extra)
if err != nil {
log.Error("Subject Operation Log unmarshal failed(%v)", err)
return
}
res.Logs = append(res.Logs, &model.SubjectLog{
AdminID: data.AdminID,
AdminName: data.AdminName,
Oid: strconv.FormatInt(data.Oid, 10),
Type: data.Type,
Remark: extra["remark"],
CTime: data.Ctime,
Action: data.Action,
})
}
if sp.Type != 0 && len(oids) > 0 {
var subjects map[int64]*model.Subject
subjects, err = s.subjects(c, oids, sp.Type)
if err == nil {
for _, data := range res.Logs {
var oid int64
oid, err = strconv.ParseInt(data.Oid, 10, 64)
if err != nil {
log.Error("strconv.ParseInt failed(%v)", err)
return
}
if sub, ok := subjects[oid]; ok && sub != nil {
if sub.AttrVal(model.SubAttrFrozen) == model.AttrYes {
//frozen
data.State = 2
} else if sub.State == model.SubStateForbid {
data.State = model.SubStateForbid
} else {
data.State = model.SubStateNormal
}
}
}
}
}
return
}

View File

@@ -0,0 +1,21 @@
package service
import (
"context"
"testing"
"go-common/app/admin/main/reply/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestUpSubjectAttr(t *testing.T) {
c := context.Background()
Convey("test update subject attr", t, WithService(func(s *Service) {
_, err := s.FreezeSub(c, 23, "asd", []int64{1}, 1, 1, "test")
So(err, ShouldBeNil)
sub, err := s.Subject(c, 1, 1)
So(err, ShouldBeNil)
So(sub.AttrVal(model.SubAttrFrozen), ShouldEqual, 1)
}))
}