Create & Init Project...

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

View File

@@ -0,0 +1,133 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"advance_test.go",
"advert_test.go",
"archive_test.go",
"dm_history_test.go",
"dm_manage_test.go",
"dm_seg_test.go",
"dm_test.go",
"dmpost_test.go",
"filter_test.go",
"mask_test.go",
"member_test.go",
"service_test.go",
"subject_test.go",
"subtitle_test.go",
"thumbup_test.go",
],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/interface/main/dm2/conf:go_default_library",
"//app/interface/main/dm2/model:go_default_library",
"//app/service/main/account/api:go_default_library",
"//app/service/main/member/model/block:go_default_library",
"//library/ecode:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"account.go",
"advance.go",
"advert.go",
"archive.go",
"bnj.go",
"dm.go",
"dm_history.go",
"dm_manage.go",
"dm_seg.go",
"dm_seg_v2.go",
"dmpost.go",
"filter.go",
"mask.go",
"member.go",
"service.go",
"subject.go",
"subtitle.go",
"subtitle_audit.go",
"subtitle_check.go",
"subtitle_get.go",
"subtitle_lan.go",
"subtitle_report.go",
"subtitle_save.go",
"subtitle_search.go",
"subtitle_subject.go",
"subtitle_video.go",
"thumbup.go",
"view.go",
"wave_form.go",
],
importpath = "go-common/app/interface/main/dm2/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/dm2/conf:go_default_library",
"//app/interface/main/dm2/dao:go_default_library",
"//app/interface/main/dm2/lib/xregex:go_default_library",
"//app/interface/main/dm2/model:go_default_library",
"//app/interface/main/dm2/model/oplog:go_default_library",
"//app/service/main/account/api:go_default_library",
"//app/service/main/archive/api:go_default_library",
"//app/service/main/archive/api/gorpc:go_default_library",
"//app/service/main/archive/model/archive:go_default_library",
"//app/service/main/assist/model/assist:go_default_library",
"//app/service/main/assist/rpc/client:go_default_library",
"//app/service/main/coin/api/gorpc:go_default_library",
"//app/service/main/coin/model:go_default_library",
"//app/service/main/figure/model:go_default_library",
"//app/service/main/figure/rpc/client:go_default_library",
"//app/service/main/filter/api/grpc/v1:go_default_library",
"//app/service/main/location/model:go_default_library",
"//app/service/main/location/rpc/client:go_default_library",
"//app/service/main/member/api/gorpc:go_default_library",
"//app/service/main/member/model/block:go_default_library",
"//app/service/main/relation/model:go_default_library",
"//app/service/main/relation/rpc/client:go_default_library",
"//app/service/main/seq-server/model:go_default_library",
"//app/service/main/seq-server/rpc/client:go_default_library",
"//app/service/main/spy/model:go_default_library",
"//app/service/main/spy/rpc/client:go_default_library",
"//app/service/main/thumbup/api:go_default_library",
"//app/service/main/ugcpay/api/grpc/v1:go_default_library",
"//app/service/openplatform/pgc-season/api/grpc/season/v1:go_default_library",
"//library/database/elastic:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/log/infoc:go_default_library",
"//library/net/metadata:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/sync/pipeline/fanout:go_default_library",
"//library/time:go_default_library",
"//library/xstr:go_default_library",
"//vendor/github.com/zhenjl/cityhash:go_default_library",
"//vendor/golang.org/x/sync/singleflight: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,58 @@
package service
import (
"context"
"math"
"sync"
account "go-common/app/service/main/account/api"
"go-common/library/log"
"go-common/library/sync/errgroup"
)
func (s *Service) accountInfos(c context.Context, mids []int64) (res map[int64]*account.Info, err error) {
var (
g errgroup.Group
mu sync.Mutex
pagesize = 50
ids = make([]int64, 0, len(mids))
midMap = make(map[int64]struct{})
)
res = make(map[int64]*account.Info)
if len(mids) == 0 {
return
}
for _, mid := range mids {
if _, ok := midMap[mid]; !ok {
midMap[mid] = struct{}{}
ids = append(ids, mid)
}
}
total := len(ids)
pageNum := int(math.Ceil(float64(total) / float64(pagesize)))
for i := 0; i < pageNum; i++ {
start := i * pagesize
end := (i + 1) * pagesize
if end > total {
end = total
}
g.Go(func() (err error) {
var (
arg = &account.MidsReq{Mids: ids[start:end]}
reply *account.InfosReply
)
if reply, err = s.accountRPC.Infos3(c, arg); err != nil {
log.Error("accRPC.Infos3(%+v) error(%v)", arg, err)
return
}
for mid, info := range reply.GetInfos() {
mu.Lock()
res[mid] = info
mu.Unlock()
}
return
})
}
err = g.Wait()
return
}

View File

@@ -0,0 +1,368 @@
package service
import (
"context"
"fmt"
"go-common/app/interface/main/dm2/model"
account "go-common/app/service/main/account/api"
arcMdl "go-common/app/service/main/archive/model/archive"
coinmdl "go-common/app/service/main/coin/model"
relation "go-common/app/service/main/relation/model"
"go-common/library/ecode"
"go-common/library/log"
)
// BuyAdvance 购买高级弹幕
func (s *Service) BuyAdvance(c context.Context, mid, cid int64, mode string) (err error) {
var (
refund = int64(1)
coin = model.AdvSPCoin
reason = model.AdvSPCoinReason
advPermit int8
)
if !s.dao.AddAdvanceLock(c, cid, mid) {
return
}
defer s.dao.DelAdvanceLock(c, cid, mid)
reply, err := s.accountRPC.Info3(c, &account.MidReq{Mid: mid})
if err != nil {
log.Error("s.accRPC.Info3(%d) error(%v)", mid, err)
return
}
if s.isSuperUser(reply.GetInfo().GetRank()) {
return
}
if mode != model.AdvSpeMode { // pool=2 弹幕
coin = model.AdvCoin
reason = model.AdvCoinReason
}
typ, err := s.dao.AdvanceType(c, cid, mid, mode)
if err != nil {
log.Error("dao.AdvanceType(%d,%d,%s) error(%v)", cid, mid, mode, err)
return
}
sub, err := s.subject(c, model.SubTypeVideo, cid)
if err != nil {
return
}
if typ != "" { // 已有购买记录
if typ == model.AdvTypeRequest && mid != sub.Mid {
err = ecode.DMAdvConfirm
} else {
err = ecode.DMAdvBought
}
return
}
if mid != sub.Mid {
advPermit, err = s.dao.UpperConfig(c, sub.Mid)
if err != nil {
return
}
if err = s.checkAdvancePermit(c, advPermit, sub.Mid, mid); err != nil {
return
}
}
typ = model.AdvTypeRequest
if sub.Mid == mid {
typ = model.AdvTypeBuy
}
coins, err := s.coinRPC.UserCoins(c, &coinmdl.ArgCoinInfo{Mid: mid})
if err != nil {
log.Error("coinRPC.UserCoins(%v) error(%v)", mid, err)
return
}
if coins < float64(coin) {
err = ecode.LackOfCoins
return
}
if _, err = s.dao.BuyAdvance(c, mid, cid, sub.Mid, refund, typ, mode); err != nil {
return
}
if _, err = s.coinRPC.ModifyCoin(c, &coinmdl.ArgModifyCoin{Mid: mid, Count: -float64(coin), Reason: reason, CheckZero: 1}); err != nil {
log.Error("coinRPC.ModifyCoin(%v,%v,%v) error(%v)", mid, coin, reason, err)
return
}
if err = s.dao.DelAdvCache(c, mid, cid, mode); err != nil {
return
}
s.cache.Do(c, func(ctx context.Context) {
arc, err := s.arcRPC.Archive3(ctx, &arcMdl.ArgAid2{Aid: sub.Pid})
if err != nil {
log.Error("s.arcRPC.Archive3(aid:%d) error(%v)", sub.Pid, err)
return
}
title := "您收到了一条高级弹幕请求"
content := fmt.Sprintf(`您的稿件《%s》收到了一条高级弹幕请求#{立即处理}{"https://member.bilibili.com/v/#/danmu/report/advance"}`, arc.Title)
s.dao.SendNotify(ctx, title, content, "4", []int64{sub.Mid})
})
return
}
func (s *Service) checkAdvancePermit(c context.Context, advPermit int8, upid, mid int64) (err error) {
switch advPermit {
case model.AdvPermitAll:
return
case model.AdvPermitFollower, model.AdvPermitAttention:
var (
arg = &relation.ArgMid{Mid: upid}
res = make([]*relation.Following, 0)
follower *relation.Following
)
if res, err = s.relRPC.Followers(c, arg); err != nil {
log.Error("relRPC.Followers(%+v) error(%v)", arg, err)
return
}
for _, v := range res {
if v.Mid == mid {
follower = v
break
}
}
if follower == nil {
err = ecode.DMAdvNotAllow
return
}
if advPermit == model.AdvPermitAttention && follower.Attribute != 6 { // Attribute=6为相互关注
err = ecode.DMAdvNotAllow
return
}
case model.AdvPermitForbid:
err = ecode.DMAdvNotAllow
return
}
return
}
// AdvanceState 高级弹幕状态
func (s *Service) AdvanceState(c context.Context, mid, cid int64, mode string) (state *model.AdvState, err error) {
state = &model.AdvState{
Accept: true,
Coins: model.AdvSPCoin,
Confirm: model.AdvStatConfirmDefault,
}
if mode == model.AdvMode { // pool=2 弹幕
state.Coins = model.AdvCoin
}
reply, err := s.accountRPC.Info3(c, &account.MidReq{Mid: mid})
if err != nil {
log.Error("s.accRPC.Info3(%d) error(%v)", mid, err)
return
}
if s.isSuperUser(reply.GetInfo().GetRank()) {
state.HasBuy = true
state.Confirm = model.AdvStatConfirmAgree
return
}
typ, err := s.dao.AdvanceType(c, cid, mid, mode)
if err != nil {
return
}
switch typ {
case model.AdvTypeAccept, model.AdvTypeBuy: // 已通过
state.Confirm = model.AdvStatConfirmAgree
state.HasBuy = true
case model.AdvTypeRequest: // 正在确认中
state.Confirm = model.AdvStatConfirmRequest
state.HasBuy = true
case model.AdvTypeDeny: // up 主拒绝
state.Confirm = model.AdvStatConfirmDeny
state.HasBuy = true
}
return
}
// Advances 高级弹幕申请列表
func (s *Service) Advances(c context.Context, mid int64) (res []*model.Advance, err error) {
var (
cidMap = make(map[int64]bool)
midMap = make(map[int64]bool)
mids = make([]int64, 0)
cidAidMap = make(map[int64]int64)
aids = make([]int64, 0)
)
list, err := s.dao.Advances(c, mid)
if err != nil {
log.Error("dao.Advances(%d) error(%v)", mid, err)
return
}
if len(list) == 0 {
return
}
res = make([]*model.Advance, 0, len(list))
for _, l := range list {
if _, ok := midMap[l.Mid]; !ok {
midMap[l.Mid] = true
mids = append(mids, l.Mid)
}
cidMap[l.Cid] = true
}
for cid := range cidMap { // get cids->aids
var sub *model.Subject
if sub, err = s.subject(c, model.SubTypeVideo, cid); err != nil {
log.Error("s.subject(%d) error(%v)", cid, err)
return
}
if sub == nil {
err = ecode.NothingFound
continue
}
if _, ok := cidAidMap[sub.Oid]; !ok {
cidAidMap[sub.Oid] = sub.Pid
aids = append(aids, sub.Pid)
}
}
arcs, err := s.archiveInfos(c, aids) // get archiveinfos
if err != nil {
return
}
reply, err := s.accountRPC.Infos3(c, &account.MidsReq{
Mids: mids,
})
if err != nil {
log.Error("s.accRPC.Infos3(%v) error(%v)", mids, err)
return
}
for _, v := range list {
if aid, ok := cidAidMap[v.Cid]; ok {
v.Aid = aid
} else {
continue
}
if archive, ok := arcs[v.Aid]; ok {
v.Title = archive.Title
v.Cover = archive.Pic
}
if user, ok := reply.GetInfos()[v.Mid]; ok {
v.Uname = user.Name
}
res = append(res, v)
}
return
}
// PassAdvance 通过高级弹幕申请
func (s *Service) PassAdvance(c context.Context, mid, id int64) (err error) {
adv, err := s.dao.Advance(c, mid, id)
if err != nil {
log.Error("dao.Advance(%d,%d) error(%v)", mid, id, err)
return
}
if adv == nil || adv.Type == model.AdvTypeDeny {
err = ecode.DMAdvNoFound
return
}
if adv.Type == model.AdvTypeAccept {
return
}
if _, err = s.dao.UpdateAdvType(c, id, model.AdvTypeAccept); err != nil {
log.Error("dao.UpdateAdvType(%d,%d,%s) error(%v)", mid, id, model.AdvTypeAccept, err)
return
}
if err = s.dao.DelAdvCache(c, adv.Mid, adv.Cid, adv.Mode); err != nil {
log.Error("dao.DelAdvCache(%+v) error(%v)", adv, err)
}
return
}
// DenyAdvance 拒绝高级弹幕申请
func (s *Service) DenyAdvance(c context.Context, mid, id int64) (err error) {
var (
coin float64
reason string
af int64
)
adv, err := s.dao.Advance(c, mid, id)
if err != nil {
log.Error("dao.Advance(%d,%d) error(%v)", mid, id, err)
return
}
if adv == nil {
return
}
if len(adv.Type) == 0 || adv.Type == model.AdvTypeDeny {
err = ecode.DMAdvNoFound
return
}
if af, err = s.dao.UpdateAdvType(c, id, model.AdvTypeDeny); err != nil {
log.Error("dao.UpdateAdvType(%d) error(%v)", id, err)
return
}
if err = s.dao.DelAdvCache(c, adv.Mid, adv.Cid, adv.Mode); err != nil {
log.Error("dao.DelAdvCache(%+v) error(%v)", adv, err)
return
}
if af < 1 {
err = ecode.DMAdvNoFound
return
}
if adv.Refund == 0 {
return
}
coin = model.AdvSPCoin
reason = model.AdvSPCoinCancelReason
if adv.Mode == model.AdvMode {
coin = model.AdvCoin
reason = model.AdvCoinCancelReason
}
if _, err = s.coinRPC.ModifyCoin(c, &coinmdl.ArgModifyCoin{Mid: adv.Mid, Count: coin, Reason: reason}); err != nil {
log.Error("s.accRPC.AddCoin2(%v,%v,%v) error(%v)", adv.Mid, coin, reason, err)
}
return
}
// CancelAdvance 取消高级弹幕申请
func (s *Service) CancelAdvance(c context.Context, mid, id int64) (err error) {
var (
adv *model.Advance
coin float64
reason string
af int64
)
if adv, err = s.dao.Advance(c, mid, id); err != nil {
log.Error("s.dao.Advance(%d,%d) error(%v)", mid, id, err)
return
}
if adv == nil {
err = ecode.DMAdvNoFound
return
}
if af, err = s.dao.DelAdvance(c, id); err != nil {
log.Error("s.dao.DelAdvance(%d) error(%v)", id, err)
return
}
if err = s.dao.DelAdvCache(c, adv.Mid, adv.Cid, adv.Mode); err != nil {
log.Error("s.dao.DelAdvCache(%+v) error(%v)", adv, err)
return
}
if af < 1 {
err = ecode.DMAdvNoFound
return
}
if adv.Refund == 0 || adv.Type == model.AdvTypeDeny {
return
}
coin = model.AdvSPCoin
reason = model.AdvSPCoinCancelReason
if adv.Mode == model.AdvMode {
coin = model.AdvCoin
reason = model.AdvCoinCancelReason
}
if _, err = s.coinRPC.ModifyCoin(c, &coinmdl.ArgModifyCoin{Mid: adv.Mid, Count: coin, Reason: reason}); err != nil {
log.Error("s.accRPC.AddCoin2(%v,%v,%v) error(%v)", adv.Mid, coin, reason, err)
}
return
}
// UpdateAdvancePermit update advance permit.
func (s *Service) UpdateAdvancePermit(c context.Context, mid int64, advPermit int8) (err error) {
_, err = s.dao.AddUpperConfig(c, mid, advPermit)
return
}
// AdvancePermit get advance permission.
func (s *Service) AdvancePermit(c context.Context, mid int64) (advPermit int8, err error) {
advPermit, err = s.dao.UpperConfig(c, mid)
return
}

View File

@@ -0,0 +1,44 @@
package service
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestAdvanceState(t *testing.T) {
Convey("test adv State", t, func() {
_, err := svr.AdvanceState(context.TODO(), 27515330, 10107292, "sp")
So(err, ShouldBeNil)
})
}
func TestAdvances(t *testing.T) {
Convey("test adv", t, func() {
res, err := svr.Advances(context.TODO(), 27515260)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
}
func TestPassAdvance(t *testing.T) {
Convey("test pass adv", t, func() {
err := svr.PassAdvance(context.TODO(), 7158471, 2)
So(err, ShouldBeNil)
})
}
func TestDenyAdvance(t *testing.T) {
Convey("test deny adv", t, func() {
err := svr.DenyAdvance(context.TODO(), 27515615, 107)
So(err, ShouldBeNil)
})
}
func TestCancelAdvance(t *testing.T) {
Convey("test cancel adv", t, func() {
err := svr.CancelAdvance(context.TODO(), 27515615, 122)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,17 @@
package service
import (
"context"
"go-common/app/interface/main/dm2/model"
)
// DMAdvert dm advert.
func (s *Service) DMAdvert(c context.Context, arg *model.ADReq) (res *model.ADResp, err error) {
ad, err := s.dao.DMAdvert(c, arg.Aid, arg.Oid, arg.Mid, arg.Build, arg.Buvid, arg.MobiApp, arg.ADExtra)
if err != nil || ad == nil {
return
}
res = ad.Convert(arg.ClientIP)
return
}

View File

@@ -0,0 +1,21 @@
package service
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestDMAdvert(t *testing.T) {
Convey("test dm advert", t, func() {
// arg := &model.ADReq{
// Aid: 10100572,
// Oid: 10115216,
// Mid: 1234,
// Buvid: "1234",
// ClientIP: "127.0.0.1",
// MobiApp: "iphone",
// }
// svr.DMAdvert(context.TODO(), arg)
})
}

View File

@@ -0,0 +1,82 @@
package service
import (
"context"
"math"
"sync"
"go-common/app/interface/main/dm2/model"
"go-common/app/service/main/archive/api"
arcMdl "go-common/app/service/main/archive/model/archive"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/sync/errgroup"
)
func (s *Service) archiveInfos(c context.Context, aids []int64) (archiveInfos map[int64]*api.Arc, err error) {
var (
pagesize = 100
wg errgroup.Group
mu sync.Mutex
)
archiveInfos = make(map[int64]*api.Arc)
if len(aids) <= 0 {
return
}
page := int(math.Ceil(float64(len(aids)) / float64(pagesize)))
for i := 0; i < page; i++ {
start := i * pagesize
end := (i + 1) * pagesize
if end > len(aids) {
end = len(aids)
}
wg.Go(func() (err error) {
arg := &arcMdl.ArgAids2{Aids: aids[start:end]}
infos, err := s.arcRPC.Archives3(c, arg)
if err != nil {
log.Error("s.arcRPC.Archives3(%v) error(%v)", arg, err)
return
}
for _, info := range infos {
mu.Lock()
archiveInfos[info.Aid] = info
mu.Unlock()
}
return
})
}
err = wg.Wait()
return
}
// videoDuration return video duration cid.
func (s *Service) videoDuration(c context.Context, aid, cid int64) (duration int64, err error) {
var cache = true
if duration, err = s.dao.DurationCache(c, cid); err != nil {
log.Error("dao.Duration(cid:%d) error(%v)", cid, err)
err = nil
cache = false
} else if duration != model.NotFound {
return
}
arg := &arcMdl.ArgVideo2{Aid: aid, Cid: cid, RealIP: metadata.String(c, metadata.RemoteIP)}
page, err := s.arcRPC.Video3(c, arg)
if err != nil {
if ecode.Cause(err).Code() == ecode.NothingFound.Code() {
duration = 0
err = nil
log.Warn("acvSvc.Video3(%v) error(duration not exist)", arg)
} else {
log.Error("acvSvc.Video3(%v) error(%v)", arg, err)
}
} else {
duration = page.Duration * 1000
}
if cache {
s.cache.Do(c, func(ctx context.Context) {
s.dao.SetDurationCache(ctx, cid, duration)
})
}
return
}

View File

@@ -0,0 +1,21 @@
package service
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestVideoDuration(t *testing.T) {
var (
aid int64 = 10097265
oid int64 = 1508
c = context.TODO()
)
Convey("", t, func() {
_, err := svr.videoDuration(c, aid, oid)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,69 @@
package service
import (
"encoding/csv"
"net/http"
"strconv"
"time"
"go-common/library/log"
)
const (
_bnjShieldCsvURL = "http://i0.hdslb.com/bfs/dm/bnj_shield.csv"
)
func (s *Service) shieldProc() {
s.shieldFromCsv()
ticker := time.NewTicker(time.Minute * 5)
defer ticker.Stop()
for range ticker.C {
s.shieldFromCsv()
}
}
func (s *Service) shieldFromCsv() {
resp, err := http.Get(_bnjShieldCsvURL)
if err != nil {
log.Error("shieldFromCsv(url:%v) error(%v)", _bnjShieldCsvURL, err)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Error("shieldFromCsv(url:%v) status(%v)", _bnjShieldCsvURL, resp.StatusCode)
return
}
r := csv.NewReader(resp.Body)
// ignore first record
r.Read()
aids := make([]int64, 0, 100)
mids := make([]int64, 0, 100)
for {
records, err := r.Read()
if err != nil {
break
}
if len(records) != 2 {
continue
}
// ignore error
aid, _ := strconv.ParseInt(records[0], 10, 64)
mid, _ := strconv.ParseInt(records[1], 10, 64)
if aid > 0 {
aids = append(aids, aid)
}
if mid > 0 {
mids = append(mids, mid)
}
}
aidMap := make(map[int64]struct{})
midMap := make(map[int64]struct{})
for _, aid := range aids {
aidMap[aid] = struct{}{}
}
for _, mid := range mids {
midMap[mid] = struct{}{}
}
s.aidSheild = aidMap
s.midsSheild = midMap
}

View File

@@ -0,0 +1,522 @@
package service
import (
"bytes"
"context"
"encoding/json"
"fmt"
"html"
"sort"
"go-common/app/interface/main/dm2/model"
arcMdl "go-common/app/service/main/archive/model/archive"
"go-common/library/ecode"
"go-common/library/log"
)
func (s *Service) dmsCache(c context.Context, tp int32, oid, maxlimit int64, needTrim bool) (dms []*model.DM, err error) {
ok, err := s.dao.ExpireDMCache(c, tp, oid)
if err != nil || !ok {
return
}
values, err := s.dao.DMCache(c, tp, oid)
if err != nil || len(values) == 0 {
return
}
var (
start, trimCnt int
normal, protect, special []*model.DM
)
for _, value := range values {
dm := &model.DM{}
if err = dm.Unmarshal(value); err != nil {
log.Error("proto.Unmarshal(%s) error(%v)", value, err)
return
}
if dm.Pool == model.PoolNormal {
if dm.AttrVal(model.AttrProtect) == model.AttrYes {
protect = append(protect, dm)
} else {
normal = append(normal, dm)
}
} else {
special = append(special, dm)
}
}
// 保护弹幕
if start = len(protect) - int(maxlimit); start > 0 { // 只保留maxlimit条保护弹幕
trimCnt += start
protect = protect[start:]
}
dms = append(dms, protect...)
// 普通弹幕
if start = len(normal) + len(protect) - int(maxlimit); start > 0 { // 保护弹幕+普通弹幕=maxlimit
trimCnt += start
normal = normal[start:]
}
dms = append(dms, normal...)
// 追加字幕弹幕和特殊弹幕
dms = append(dms, special...)
if trimCnt > 0 && needTrim {
err = s.dao.TrimDMCache(c, tp, oid, int64(trimCnt))
}
return
}
// 返回所有每个弹幕池对应的弹幕列表
func (s *Service) dms(c context.Context, tp int32, oid, maxlimit int64, childpool int32) (dms []*model.DM, err error) {
var (
key string
count int
keyprotect = "kp"
dmMap = make(map[string][]*model.DM)
contentSpeMap = make(map[int64]*model.ContentSpecial)
)
idxMap, dmids, spedmids, err := s.dao.Indexs(c, tp, oid)
if err != nil {
return
}
if len(dmids) == 0 {
return
}
contents, err := s.dao.Contents(c, oid, dmids)
if err != nil {
return
}
if len(spedmids) > 0 {
if contentSpeMap, err = s.dao.ContentsSpecial(c, spedmids); err != nil {
return
}
}
for _, content := range contents {
if dm, ok := idxMap[content.ID]; ok {
key = fmt.Sprint(dm.Pool)
dm.Content = content
if dm.Pool == model.PoolNormal {
if dm.AttrVal(model.AttrProtect) == model.AttrYes {
key = keyprotect
}
}
if dm.Pool == model.PoolSpecial {
contentSpe, ok := contentSpeMap[dm.ID]
if ok {
dm.ContentSpe = contentSpe
}
}
dmMap[key] = append(dmMap[key], dm)
}
}
// dm sort
for _, dmsTmp := range dmMap {
sort.Sort(model.DMSlice(dmsTmp))
}
// pool = 0 保护弹幕和普通弹幕总和为maxlimit
if protect, ok := dmMap[keyprotect]; ok {
if start := len(protect) - int(maxlimit); start > 0 { // 只保留maxlimit条保护弹幕
protect = protect[start:]
}
dms = append(dms, protect...)
count = len(protect)
}
key = fmt.Sprint(model.PoolNormal)
if normal, ok := dmMap[key]; ok {
start := len(normal) + count - int(maxlimit)
if start > 0 {
normal = normal[start:]
}
dms = append(dms, normal...)
}
if childpool > 0 {
// pool = 1 字幕弹幕
if _, ok := dmMap[fmt.Sprint(model.PoolSubtitle)]; ok {
dms = append(dms, dmMap[fmt.Sprint(model.PoolSubtitle)]...)
}
// pool =2 特殊弹幕
key = fmt.Sprint(model.PoolSpecial)
if _, ok := dmMap[key]; ok {
dms = append(dms, dmMap[key]...)
}
}
return
}
func (s *Service) genXML(c context.Context, sub *model.Subject) (xml []byte, err error) {
buf := new(bytes.Buffer)
buf.WriteString(`<?xml version="1.0" encoding="UTF-8"?><i>`)
buf.WriteString(`<chatserver>chat.bilibili.com</chatserver><chatid>`)
buf.WriteString(fmt.Sprint(sub.Oid))
buf.WriteString(`</chatid><mission>`)
buf.WriteString(fmt.Sprint(sub.AttrVal(model.AttrSubMission)))
buf.WriteString(`</mission><maxlimit>`)
buf.WriteString(fmt.Sprint(sub.Maxlimit))
buf.WriteString(`</maxlimit>`)
buf.WriteString(fmt.Sprintf(`<state>%d</state>`, sub.State))
realname := s.isRealname(c, sub.Pid, sub.Oid)
if realname {
buf.WriteString(`<real_name>1</real_name>`)
} else {
buf.WriteString(`<real_name>0</real_name>`)
}
if sub.State == model.SubStateClosed {
buf.WriteString(`</i>`)
xml = buf.Bytes()
return
}
dms, err := s.dmsCache(c, sub.Type, sub.Oid, sub.Maxlimit, true)
if err != nil {
return
}
if len(dms) > 0 {
buf.WriteString(`<source>k-v</source>`)
} else {
buf.WriteString(`<source>e-r</source>`)
if dms, err = s.dms(c, sub.Type, sub.Oid, sub.Maxlimit, sub.Childpool); err != nil {
return
}
flush := &model.Flush{Type: sub.Type, Oid: sub.Oid, Force: false}
data, err := json.Marshal(flush)
if err != nil {
log.Error("json.Marshal(%v) error(%v)", flush, err)
return nil, err
}
s.dao.SendAction(context.TODO(), fmt.Sprint(sub.Oid), &model.Action{Action: model.ActionFlush, Data: data})
}
for _, dm := range dms {
buf.WriteString(dm.ToXML(realname))
}
buf.WriteString("</i>")
return buf.Bytes(), nil
}
// DMXML return dm xml.
func (s *Service) DMXML(c context.Context, tp int32, oid int64) (data []byte, err error) {
// get from local cache
var ok bool
if data, ok = s.localCache[keyXML(tp, oid)]; ok {
return
}
data, err = s.singleGenXML(c, tp, oid)
return
}
func (s *Service) singleGenXML(c context.Context, tp int32, oid int64) (data []byte, err error) {
v, err, _ := s.singleGroup.Do(keyXML(tp, oid), func() (res interface{}, err error) {
// 从memcache获取
if data, err = s.dao.XMLCache(c, oid); err != nil {
return
}
if len(data) > 0 {
res = data
return
}
sub, err := s.subject(c, tp, oid)
if err != nil {
return
}
if data, err = s.genXML(c, sub); err != nil {
return
}
if len(data) == 0 {
err = ecode.NothingFound
return
}
if data, err = s.gzflate(data); err != nil {
log.Error("s.gzflate(oid:%d) error(%v)", oid, err)
return
}
s.cache.Do(c, func(ctx context.Context) {
s.dao.AddXMLCache(ctx, oid, data)
})
res = data
return
})
if err != nil {
return
}
data = v.([]byte)
return
}
// AjaxDM 返回首页弹幕列表
func (s *Service) AjaxDM(c context.Context, aid int64) (msgs []string, err error) {
msgs = make([]string, 0)
res, err := s.arcRPC.Archive3(c, &arcMdl.ArgAid2{Aid: aid})
if err != nil {
log.Error("arcRPC.Archive3(%d) error(%v)", aid, err)
return
}
oid := res.FirstCid
if msgs, err = s.dao.AjaxDMCache(c, oid); err != nil {
return
}
if len(msgs) > 0 {
return
}
dms, err := s.dmsCache(c, model.SubTypeVideo, oid, 20, false)
if err != nil {
log.Error("s.dmsCache(%d %d) error(%v)", aid, oid, err)
return
}
for _, dm := range dms {
if dm.Pool == model.PoolNormal && dm.Content.Mode != model.ModeSpecial {
msgs = append(msgs, html.EscapeString(dm.Content.Msg))
}
}
s.cache.Do(c, func(ctx context.Context) {
s.dao.AddAjaxDMCache(ctx, oid, msgs)
})
return
}
// JudgeDms get fengjiwei dm list
func (s *Service) JudgeDms(c context.Context, tp int8, oid int64, dmid int64) (judgeDMList *model.JudgeDMList, err error) {
var (
start, end, dmidIn int
length = 100
dms = make([]*model.JDM, 0)
dmids []int64
contentSpec = make(map[int64]*model.ContentSpecial)
)
judgeDMList, err = s.dao.DMJudgeCache(c, tp, oid, dmid)
if err != nil {
log.Error("DMJudge:s.dmDao.DMJudgeCache(tp:%d,oid:%d,dmid:%d) error(%v)", tp, oid, dmid, err)
return
}
if judgeDMList != nil {
return
}
judgeDMList = new(model.JudgeDMList)
judgeDMList.List = make([]*model.JDM, 0)
judgeDMList.Index = make([]int64, 0)
idx, err := s.dao.IndexByid(c, tp, oid, dmid)
if err != nil {
log.Error("DMJudge:s.dmDao.Index2(type:%d,oid:%d, dmid:%d) error (%v)", tp, oid, dmid, err)
return
}
if idx == nil {
s.cache.Do(c, func(ctx context.Context) {
s.dao.SetDMJudgeCache(ctx, tp, oid, dmid, judgeDMList)
})
return
}
part, spart, err := s.dao.JudgeIndex(c, tp, oid, idx.Ctime-86400, idx.Ctime+86400, idx.Progress-5000, idx.Progress+5000)
if err != nil {
log.Error("DMJudge:s.dmDao.JudgeIndex (type:%d,oid:%d) error (%v)", tp, oid, err)
return
}
sort.Sort(model.JudgeSlice(part))
for k, d := range part {
if d.ID == dmid {
dmidIn = k + 1
break
}
}
if dmidIn == 0 {
log.Error("DMJudge: cid:%d dmid:%d dm too much", oid, dmid)
return
}
start = dmidIn - length/2
end = dmidIn + length/2
if start < 0 {
start = 0
}
if end > len(part) {
end = len(part)
}
part = part[start:end]
for k, i := range part {
if i.Mid == idx.Mid {
judgeDMList.Index = append(judgeDMList.Index, int64(k))
}
dmids = append(dmids, i.ID)
}
ctsMap, err := s.dao.Contents(c, oid, dmids)
if err != nil {
log.Error("DMJudge:s.dmDao.Contents(type:%d,oid:%d dmids:%v) error (%v)", tp, oid, dmids, err)
return
}
if len(spart) > 0 {
if contentSpec, err = s.dao.ContentsSpecial(c, spart); err != nil {
log.Error("DMJudge:s.dmDao.ContentsSpecial(type:%d,oid:%d) error (%v)", tp, oid, err)
return
}
}
for _, i := range part {
ct, ok := ctsMap[i.ID]
if !ok {
continue
}
dm := &model.JDM{
ID: i.ID,
Progress: timeStr(int64(i.Progress)),
Msg: ct.Msg,
Mid: i.Mid,
CTime: i.Ctime,
}
if i.Pool == model.PoolSpecial {
if _, ok = contentSpec[dm.ID]; ok {
dm.Msg = contentSpec[dm.ID].Msg
}
}
dms = append(dms, dm)
}
judgeDMList.List = dms
s.cache.Do(c, func(ctx context.Context) {
s.dao.SetDMJudgeCache(ctx, tp, oid, dmid, judgeDMList)
})
return
}
// timeStr microTime to string.
func timeStr(t int64) string {
sec := t / 1000 % 60
min := t / 1000 / 60 % 60
hour := t / 1000 / 60 / 60
return fmt.Sprintf("%02d:%02d:%02d", hour, min, sec)
}
func (s *Service) isRealname(c context.Context, aid, oid int64) (realname bool) {
for _, v := range s.conf.Realname.Whitelist {
if oid == v {
realname = true
return
}
}
if !s.conf.Realname.SwitchOn {
return
}
arg := &arcMdl.ArgAid2{Aid: aid}
archive, err := s.arcRPC.Archive3(c, arg)
if err != nil {
log.Error("arcRPC.Archive3(%v) error(%v)", arg, err)
return
}
if v, ok := s.realname[int64(archive.TypeID)]; ok && oid >= v {
realname = true
} else {
realname = false
}
return
}
// DMDistribution get dm distribution from dm list.
func (s *Service) DMDistribution(c context.Context, typ int32, oid int64, interval int32) (res map[int64]int64, err error) {
res = make(map[int64]int64)
sub, err := s.subject(c, typ, oid)
if err != nil {
return
}
dms, err := s.dmsCache(c, sub.Type, sub.Oid, sub.Maxlimit, true)
if err != nil {
return
}
if len(dms) == 0 {
if dms, err = s.dms(c, sub.Type, sub.Oid, sub.Maxlimit, sub.Childpool); err != nil {
return
}
flush := &model.Flush{Type: sub.Type, Oid: sub.Oid, Force: false}
data, err := json.Marshal(flush)
if err != nil {
log.Error("json.Marshal(%v) error(%v)", flush, err)
return nil, err
}
s.dao.SendAction(context.TODO(), fmt.Sprint(sub.Oid), &model.Action{Action: model.ActionFlush, Data: data})
}
for _, dm := range dms {
x := int64(dm.Progress/1000.0/interval) + 1
if _, ok := res[x]; !ok {
res[x] = 1
} else {
res[x]++
}
}
return
}
func (s *Service) loadLocalcache(oids []int64) {
var (
err error
duration int64
sub *model.Subject
tp = model.SubTypeVideo
c = context.Background()
tmp = make(map[string][]byte)
bs []byte
)
for _, oid := range oids {
if sub, err = s.dao.Subject(c, tp, oid); err != nil || sub == nil {
continue
}
if bs, err = json.Marshal(sub); err != nil {
continue
}
tmp[keySubject(sub.Type, sub.Oid)] = bs
// local cache video duration
if duration, err = s.videoDuration(c, sub.Pid, oid); err != nil {
continue
}
tmp[keyDuration(tp, oid)] = []byte(fmt.Sprint(duration))
// local cache segment dm xml
seg := model.SegmentInfo(0, duration)
for i := int64(1); i <= seg.Cnt; i++ { // flush every segment cache
seg.Num = i
var xml []byte
if xml, err = s.singleGenSegXML(c, sub.Pid, sub, seg); err != nil {
continue
}
tmp[keySeg(tp, oid, seg.Cnt, seg.Num)] = xml
}
// local cache dm xml
data, err := s.singleGenXML(c, tp, oid)
if err != nil {
continue
}
tmp[keyXML(tp, oid)] = []byte(data)
}
if len(tmp) > 0 {
s.localCache = tmp
}
}
// dmList get dm list from database.
func (s *Service) dmList(c context.Context, tp int32, oid int64, dmids []int64) (dms []*model.DM, err error) {
if len(dmids) == 0 {
return
}
dms = make([]*model.DM, 0, len(dmids))
contentSpe := make(map[int64]*model.ContentSpecial)
idxMap, special, err := s.dao.IndexsByid(c, tp, oid, dmids)
if err != nil || len(idxMap) == 0 {
return
}
ctsMap, err := s.dao.Contents(c, oid, dmids)
if err != nil {
return
}
if len(special) > 0 {
if contentSpe, err = s.dao.ContentsSpecial(c, special); err != nil {
return
}
}
for _, dmid := range dmids {
if dm, ok := idxMap[dmid]; ok {
var content *model.Content
if content, ok = ctsMap[dmid]; ok {
dm.Content = content
} else {
log.Error("dm content not exist,tp:%d,oid:%d,dmid:%d", tp, oid, dmid)
continue
}
if dm.Pool == model.PoolSpecial {
if _, ok = contentSpe[dmid]; ok {
dm.ContentSpe = contentSpe[dmid]
} else {
log.Error("dm special content not exist,tp:%d,oid:%d,dmid:%d", tp, oid, dmid)
continue
}
}
dms = append(dms, dm)
}
}
return
}

View File

@@ -0,0 +1,185 @@
package service
import (
"bytes"
"context"
"fmt"
"math"
"go-common/app/interface/main/dm2/model"
"go-common/library/log"
)
const (
_hisPagesize = 5000
)
// SearchDMHisIndex get history date index.
func (s *Service) SearchDMHisIndex(c context.Context, tp int32, oid int64, month string) (dates []string, err error) {
if dates, err = s.dao.HistoryIdxCache(c, tp, oid, month); err == nil && len(dates) > 0 {
return
}
if dates, err = s.dao.SearchDMHisIndex(c, tp, oid, month); err != nil {
log.Error("dao.SearchDMHisIndex(%d,%d,%s) error(%v)", tp, oid, month, err)
return
}
if len(dates) > 0 {
s.cache.Do(c, func(ctx context.Context) {
s.dao.AddHisIdxCache(ctx, tp, oid, month, dates)
})
}
return
}
// SearchDMHistory get history dm list from search.
func (s *Service) SearchDMHistory(c context.Context, tp int32, oid, ctimeTo int64) (xml []byte, err error) {
var (
sub *model.Subject
dmids []int64
contentSpeMap = make(map[int64]*model.ContentSpecial)
)
if xml, err = s.dao.HistoryCache(c, tp, oid, ctimeTo); err == nil && len(xml) > 0 {
return
}
if sub, err = s.subject(c, tp, oid); err != nil {
return
}
buf := new(bytes.Buffer)
defer func() {
if err == nil {
buf.WriteString(`</i>`)
xml, err = s.gzflate(buf.Bytes())
s.cache.Do(c, func(ctx context.Context) {
s.dao.AddHistoryCache(ctx, tp, oid, ctimeTo, xml)
})
}
}()
buf.WriteString(`<?xml version="1.0" encoding="UTF-8"?><i>`)
buf.WriteString(`<chatserver>chat.bilibili.com</chatserver><chatid>`)
buf.WriteString(fmt.Sprint(sub.Oid))
buf.WriteString(`</chatid><mission>`)
buf.WriteString(fmt.Sprint(sub.AttrVal(model.AttrSubMission)))
buf.WriteString(`</mission><maxlimit>`)
buf.WriteString(fmt.Sprint(sub.Maxlimit))
buf.WriteString(`</maxlimit>`)
buf.WriteString(fmt.Sprintf(`<state>%d</state>`, sub.State))
realname := s.isRealname(c, sub.Pid, sub.Oid)
if realname {
buf.WriteString(`<real_name>1</real_name>`)
} else {
buf.WriteString(`<real_name>0</real_name>`)
}
if sub.State == model.SubStateClosed {
return
}
totalPage := int(math.Ceil(float64(sub.Maxlimit) / float64(_hisPagesize)))
dmids, err = s.dao.SearchDMHistory(c, tp, oid, ctimeTo, totalPage, _hisPagesize)
if err != nil {
return
}
if len(dmids) == 0 {
return
}
if int64(len(dmids)) > sub.Maxlimit {
dmids = dmids[:sub.Maxlimit]
}
idxMap, special, err := s.dao.IndexsByid(c, tp, oid, dmids)
if err != nil {
return
}
ctsMap, err := s.dao.Contents(c, oid, dmids)
if err != nil {
return
}
if len(special) > 0 {
if contentSpeMap, err = s.dao.ContentsSpecial(c, special); err != nil {
return
}
}
for _, dmid := range dmids {
if idx, ok := idxMap[dmid]; ok {
dm := &model.DM{
ID: idx.ID,
Type: idx.Type,
Oid: idx.Oid,
Mid: idx.Mid,
Progress: idx.Progress,
Pool: idx.Pool,
Attr: idx.Attr,
State: idx.State,
Ctime: idx.Ctime,
Mtime: idx.Mtime,
}
content, ok := ctsMap[dmid]
if !ok {
continue
}
dm.Content = content
if idx.Pool == model.PoolSpecial {
if ctsSpec, ok := contentSpeMap[dmid]; ok {
dm.ContentSpe = ctsSpec
}
}
buf.WriteString(dm.ToXML(realname))
}
}
return
}
// SearchDMHistoryV2 get history dm list from search.
func (s *Service) SearchDMHistoryV2(c context.Context, tp int32, oid, ctimeTo int64) (res *model.DMSeg, err error) {
sub, err := s.subject(c, tp, oid)
if err != nil {
return
}
res = &model.DMSeg{Elems: make([]*model.Elem, 0, sub.Maxlimit)}
if sub.State == model.SubStateClosed {
return
}
totalPage := int(math.Ceil(float64(sub.Maxlimit) / float64(_hisPagesize)))
dmids, err := s.dao.SearchDMHistory(c, tp, oid, ctimeTo, totalPage, _hisPagesize)
if err != nil {
return
}
if len(dmids) == 0 {
fmt.Println("dmids from search is empty")
return
}
if int64(len(dmids)) > sub.Maxlimit {
dmids = dmids[:sub.Maxlimit]
}
// TODO special dm
idxMap, _, err := s.dao.IndexsByid(c, tp, oid, dmids)
if err != nil {
return
}
ctsMap, err := s.dao.Contents(c, oid, dmids)
if err != nil {
return
}
for _, dmid := range dmids {
if idx, ok := idxMap[dmid]; ok {
dm := &model.DM{
ID: idx.ID,
Type: idx.Type,
Oid: idx.Oid,
Mid: idx.Mid,
Progress: idx.Progress,
Pool: idx.Pool,
Attr: idx.Attr,
State: idx.State,
Ctime: idx.Ctime,
Mtime: idx.Mtime,
}
content, ok := ctsMap[dmid]
if !ok {
continue
}
dm.Content = content
if e := dm.ToElem(); e != nil {
res.Elems = append(res.Elems, e)
}
}
}
return
}

View File

@@ -0,0 +1,29 @@
package service
import (
"context"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func TestSearchDMHisIndex(t *testing.T) {
Convey("test dm history date index", t, func() {
res, err := svr.SearchDMHisIndex(context.TODO(), 1, 10109227, "2018-04")
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
t.Log(res)
})
}
func TestSearchDMHistory(t *testing.T) {
Convey("", t, func() {
date, _ := time.Parse("2006-01-02", "2018-04-24")
// convert 2006-01-02-->2016-01-02 23:59:59
tm := time.Date(date.Year(), date.Month(), date.Day(), 23, 59, 59, 0, time.Local)
xml, err := svr.SearchDMHistory(context.TODO(), 1, 10109227, tm.Unix())
So(err, ShouldBeNil)
t.Log(xml)
})
}

View File

@@ -0,0 +1,318 @@
package service
import (
"context"
"fmt"
"strconv"
"time"
"go-common/app/interface/main/dm2/model"
"go-common/app/interface/main/dm2/model/oplog"
account "go-common/app/service/main/account/api"
assmdl "go-common/app/service/main/assist/model/assist"
"go-common/library/ecode"
"go-common/library/log"
)
const (
_assistUpperLimit = 100
)
// isAssist check if the user is assist of upper or not.
func (s *Service) isAssist(c context.Context, mid, uid int64) (err error) {
arg := assmdl.ArgAssist{
Mid: mid,
AssistMid: uid,
Type: assmdl.TypeDm,
RealIP: "",
}
res, err := s.assRPC.Assist(c, &arg)
if err != nil {
log.Error("s.assRPC.Assist(%v) error(%v)", arg, err)
return ecode.AccessDenied
}
if res.Assist == 1 && res.Allow == 1 {
return nil
}
if res.Assist == 1 && res.Count > _assistUpperLimit {
return ecode.DMAssistOpToMuch
}
return ecode.AccessDenied
}
// isUpper check if the user is upper.
func (s *Service) isUpper(mid, uid int64) bool {
return mid == uid
}
// EditDMState change dm state
// 0正常、1删除、10用户删除、11举报脚本删除
func (s *Service) EditDMState(c context.Context, tp int32, mid, oid int64, state int32, dmids []int64, source oplog.Source, operatorType oplog.OperatorType) (err error) {
var (
affect, action int64
)
if source <= 0 {
source = oplog.SourceUp
}
if operatorType <= 0 {
operatorType = oplog.OperatorUp
}
sub, err := s.subject(c, tp, oid)
if err != nil {
return
}
switch state {
case model.StateNormal, model.StateDelete:
var isAssist bool
if !s.isUpper(sub.Mid, mid) {
if err = s.isAssist(c, sub.Mid, mid); err != nil {
return
}
}
affect, err = s.dao.UpdateDMStat(c, tp, oid, state, dmids)
if err != nil {
log.Error("s.dao.UpdateDMStat(oid:%d state:%d dmids:%v) error(%v)", oid, state, dmids, err)
return
}
if affect > 0 {
if state == model.StateDelete && sub.IsMonitoring() {
s.oidLock.Lock()
s.moniOidMap[sub.Oid] = struct{}{}
s.oidLock.Unlock()
}
s.OpLog(c, oid, mid, time.Now().Unix(), int(tp), dmids, "status", "", strconv.FormatInt(int64(state), 10), "更新弹幕状态", source, operatorType)
if state == model.StateDelete {
action = assmdl.ActDelete
affect = -affect
}
if sub.Count+affect < 0 {
affect = -sub.Count
}
if _, err = s.dao.IncrSubjectCount(c, tp, oid, affect); err != nil {
return
}
if isAssist {
for _, dmid := range dmids {
s.addAssistLog(sub.Mid, mid, oid, action, dmid)
}
}
}
case model.StateUserDelete:
affect, err = s.dao.UpdateUserDMStat(c, tp, oid, mid, state, dmids)
if err != nil {
log.Error("s.dao.UpdateUserDMStat(mid:%d oid:%d state:%d dmids:%v) error(%v)", mid, oid, state, dmids, err)
return
}
if affect > 0 {
if sub.IsMonitoring() {
s.oidLock.Lock()
s.moniOidMap[sub.Oid] = struct{}{}
s.oidLock.Unlock()
}
s.OpLog(c, oid, mid, time.Now().Unix(), int(tp), dmids, "status", "", fmt.Sprint(state), "更新弹幕状态", source, operatorType)
affect = -affect
if sub.Count+affect < 0 {
affect = -sub.Count
}
if _, err = s.dao.IncrSubjectCount(c, tp, oid, affect); err != nil {
return
}
}
case model.StateScriptDelete:
affect, err = s.dao.UpdateDMStat(c, tp, oid, state, dmids)
if err != nil {
log.Error("s.dao.UpdateDMStat(oid:%d state:%d dmids:%v) error(%v)", oid, state, dmids, err)
return
}
if affect > 0 {
if sub.IsMonitoring() {
s.oidLock.Lock()
s.moniOidMap[sub.Oid] = struct{}{}
s.oidLock.Unlock()
}
s.OpLog(c, oid, mid, time.Now().Unix(), int(tp), dmids, "status", "", fmt.Sprint(state), "更新弹幕状态", source, operatorType)
affect = -affect
if sub.Count+affect < 0 {
affect = -sub.Count
}
if _, err = s.dao.IncrSubjectCount(c, tp, oid, affect); err != nil {
return
}
}
default:
err = ecode.RequestErr
}
return
}
// EditDMPool edit dm pool.
func (s *Service) EditDMPool(c context.Context, tp int32, mid, oid int64, pool int32, ids []int64, source oplog.Source, operatorType oplog.OperatorType) (err error) {
var (
isAssist bool
affect int64
)
if pool != model.PoolNormal && pool != model.PoolSubtitle {
err = ecode.RequestErr
return
}
// pool 2 dm can't move to other pool
dmids := make([]int64, 0, len(ids))
indexs, _, err := s.dao.IndexsByid(c, tp, oid, ids)
for dmid, index := range indexs {
if index.Pool != model.PoolSpecial {
dmids = append(dmids, dmid)
}
}
if len(dmids) <= 0 {
return
}
if source <= 0 {
source = oplog.SourceUp
}
if operatorType <= 0 {
operatorType = oplog.OperatorUp
}
sub, err := s.subject(c, tp, oid)
if err != nil {
return
}
// maximum batch move count to subtitle pool is 300 when the rank of
// user is equal or less than 15000
if pool == model.PoolSubtitle {
var (
reply *account.ProfileReply
)
if reply, err = s.accountRPC.Profile3(c, &account.MidReq{Mid: mid}); err != nil {
log.Error("accRPC.Profile3(%v) error(%v)", mid, err)
return
}
if reply.Profile.Rank <= 15000 && int(sub.MoveCnt)+len(dmids) > 300 {
err = ecode.DMPoolLimit
return
}
}
if !s.isUpper(sub.Mid, mid) {
if err = s.isAssist(c, sub.Mid, mid); err != nil {
return
}
}
if sub.Childpool < pool {
if _, err = s.dao.UpSubjectPool(c, tp, oid, pool); err != nil {
return
}
}
if affect, err = s.dao.UpdateDMPool(c, tp, oid, pool, dmids); err != nil {
log.Error("s.dao.UpdateDMPool(oid:%d pool:%d dmids:%v) error(%v)", oid, pool, dmids, err)
return
}
if affect > 0 {
if pool == model.PoolNormal {
s.dao.IncrSubMoveCount(c, sub.Type, sub.Oid, -affect) // NOTE update move_count,ignore error
} else {
s.dao.IncrSubMoveCount(c, sub.Type, sub.Oid, affect) // NOTE update move_count,ignore error
}
s.OpLog(c, oid, mid, time.Now().Unix(), int(tp), dmids, "pool", "", strconv.FormatInt(int64(pool), 10), "弹幕池变更", source, operatorType)
}
if isAssist {
for _, dmid := range dmids {
s.addAssistLog(sub.Mid, mid, oid, assmdl.ActProtect, dmid)
}
}
return
}
// EditDMAttr update dm attribute.
func (s *Service) EditDMAttr(c context.Context, tp int32, mid, oid int64, bit uint, value int32, dmids []int64, source oplog.Source, operatorType oplog.OperatorType) (affectIds []int64, err error) {
var isAssist bool
affectIds = make([]int64, 0, len(dmids))
if value != model.AttrNo && value != model.AttrYes {
err = ecode.RequestErr
return
}
if source <= 0 {
source = oplog.SourceUp
}
if operatorType <= 0 {
operatorType = oplog.OperatorUp
}
sub, err := s.subject(c, tp, oid)
if err != nil {
return
}
if !s.isUpper(sub.Mid, mid) {
if err = s.isAssist(c, sub.Mid, mid); err != nil {
return
}
}
idxMap, _, err := s.dao.IndexsByid(c, tp, oid, dmids)
if err != nil {
return
}
for dmid, idx := range idxMap {
if !model.IsDMEditAble(idx.State) {
continue
}
idx.AttrSet(value, bit)
if _, err = s.dao.UpdateDMAttr(c, tp, oid, dmid, idx.Attr); err != nil {
continue
}
s.OpLog(c, oid, mid, time.Now().Unix(), int(tp), []int64{dmid}, "attribute", "", fmt.Sprintf("bit:%d,value:%d", bit, value), "弹幕保护状态变更", source, operatorType)
if isAssist {
s.addAssistLog(sub.Mid, mid, oid, assmdl.ActProtect, dmid)
}
affectIds = append(affectIds, dmid)
}
return
}
func (s *Service) addAssistLog(mid, assistMid, oid, action, dmid int64) {
ct, err := s.dao.Content(context.TODO(), oid, dmid)
if err != nil || ct == nil {
return
}
detail := ct.Msg
if len([]rune(ct.Msg)) > 50 {
detail = string([]rune(ct.Msg)[:50])
}
arg := &assmdl.ArgAssistLogAdd{
Mid: mid,
AssistMid: assistMid,
Type: assmdl.TypeDm,
Action: action,
SubjectID: oid,
ObjectID: fmt.Sprint(dmid),
Detail: detail,
}
select {
case s.assistLogChan <- arg:
default:
log.Error("assistLogChan is full")
}
}
func (s *Service) assistLogproc() {
for arg := range s.assistLogChan {
if err := s.assRPC.AssistLogAdd(context.TODO(), arg); err != nil {
log.Error("assRPC.AssistLogAdd(%v) error(%v)", arg, err)
} else {
log.Info("assRPC.AssistLogAdd(%v) success", arg)
}
}
}
// updateMonitorCnt update mcount of subject.
func (s *Service) updateMonitorCnt(c context.Context, sub *model.Subject) (err error) {
var state, mcount int64
if sub.AttrVal(model.AttrSubMonitorBefore) == model.AttrYes {
state = int64(model.StateMonitorBefore)
} else if sub.AttrVal(model.AttrSubMonitorAfter) == model.AttrYes {
state = int64(model.StateMonitorAfter)
} else {
return
}
if mcount, err = s.dao.DMCount(c, sub.Type, sub.Oid, []int64{state}); err != nil {
return
}
_, err = s.dao.UpSubjectMCount(c, sub.Type, sub.Oid, mcount)
return
}

View File

@@ -0,0 +1,33 @@
package service
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestEditDMState(t *testing.T) {
var (
c = context.TODO()
mid, oid int64 = 0, 5
state int32 = 1
dmids = []int64{719149905, 719149906, 719149907}
)
Convey("test edit dm state", t, func() {
err := svr.EditDMState(c, 1, mid, oid, state, dmids, 0, 0)
So(err, ShouldBeNil)
})
}
func TestEditDMPool(t *testing.T) {
var (
c = context.TODO()
mid, oid int64 = 0, 5
dmids = []int64{719149905, 719149906, 719149907}
)
Convey("test edit dm pool", t, func() {
err := svr.EditDMPool(c, 1, mid, oid, 1, dmids, 0, 0)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,281 @@
package service
import (
"bytes"
"compress/flate"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"strconv"
"go-common/app/interface/main/dm2/model"
"go-common/library/ecode"
"go-common/library/log"
)
func (s *Service) gzip(input []byte) ([]byte, error) {
buf := new(bytes.Buffer)
zw := gzip.NewWriter(buf)
if _, err := zw.Write(input); err != nil {
return nil, err
}
if err := zw.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// gzflate flate 压缩
func (s *Service) gzflate(input []byte) ([]byte, error) {
buf := new(bytes.Buffer)
w, err := flate.NewWriter(buf, 4)
if err != nil {
return nil, err
}
if _, err = w.Write(input); err != nil {
return nil, err
}
if err = w.Close(); err != nil {
return nil, err
}
return buf.Bytes(), err
}
// Gzdeflate deflate 解码
func (s *Service) Gzdeflate(in []byte) (out []byte, err error) {
if len(in) == 0 {
return
}
out, err = ioutil.ReadAll(flate.NewReader(bytes.NewReader(in)))
return
}
func (s *Service) dmsSeg(c context.Context, tp int32, oid int64, missed []int64) (dms []*model.DM, err error) {
idxMap, special, err := s.dao.IndexsByid(c, tp, oid, missed)
if err != nil || len(idxMap) == 0 {
return
}
ctsMap, err := s.dao.Contents(c, oid, missed)
if err != nil {
return
}
ctsSpeMap := make(map[int64]*model.ContentSpecial)
if len(special) > 0 {
if ctsSpeMap, err = s.dao.ContentsSpecial(c, special); err != nil {
return
}
}
for _, content := range ctsMap {
if idx, ok := idxMap[content.ID]; ok {
dm := &model.DM{
ID: idx.ID,
Type: idx.Type,
Oid: idx.Oid,
Mid: idx.Mid,
Progress: idx.Progress,
Pool: idx.Pool,
Attr: idx.Attr,
State: idx.State,
Ctime: idx.Ctime,
Mtime: idx.Mtime,
Content: content,
}
if idx.Pool == model.PoolSpecial {
if _, ok = ctsSpeMap[dm.ID]; ok {
dm.ContentSpe = ctsSpeMap[dm.ID]
}
}
dms = append(dms, dm)
}
}
return
}
func (s *Service) dmSegXML(c context.Context, aid int64, sub *model.Subject, seg *model.Segment) (res []byte, err error) {
var (
cache = true
buf = new(bytes.Buffer)
realname = s.isRealname(c, sub.Pid, sub.Oid)
dms []*model.DM
dmids, normalIds, spIds []int64
)
if realname {
buf.WriteString(seg.ToXMLHeader(sub.Oid, sub.State, 1))
} else {
buf.WriteString(seg.ToXMLHeader(sub.Oid, sub.State, 0))
}
defer func() {
if err == nil {
buf.WriteString(`</i>`)
res, err = s.gzip(buf.Bytes())
}
}()
if normalIds, err = s.dmNormalIds(c, sub.Type, sub.Oid, seg.Cnt, seg.Num, seg.Start, seg.End, 2*sub.Maxlimit); err != nil {
return
}
dmids = append(dmids, normalIds...)
if sub.Childpool > 0 {
if spIds, err = s.dmSegSubtitlesIds(c, sub.Type, sub.Oid, seg.Start, seg.End, 2*sub.Maxlimit); err != nil {
return
}
dmids = append(dmids, spIds...)
}
if len(dmids) <= 0 {
return
}
content, missed, err := s.dao.IdxContentCache(c, sub.Type, sub.Oid, dmids)
if err != nil {
missed = dmids
cache = false
} else {
buf.Write(content)
}
if len(missed) > 0 {
if dms, err = s.dmsSeg(c, sub.Type, sub.Oid, missed); err != nil {
return
}
for _, dm := range dms {
buf.WriteString(dm.ToXMLSeg(realname))
}
}
if cache && len(dms) > 0 {
s.cache.Do(c, func(ctx context.Context) {
s.dao.AddIdxContentCache(ctx, sub.Type, sub.Oid, dms, realname)
})
}
s.cache.Do(c, func(ctx context.Context) {
var (
bs []byte
err error
)
dmSeg := &model.ActionFlushDMSeg{
Type: sub.Type,
Oid: sub.Oid,
Force: false,
Page: &model.Page{
Num: seg.Num,
Total: seg.Cnt,
Size: model.DefaultPageSize,
},
}
if bs, err = json.Marshal(dmSeg); err != nil {
return
}
s.dao.SendAction(ctx, fmt.Sprint(sub.Oid), &model.Action{
Action: model.ActFlushDMSeg,
Data: bs,
})
})
return
}
// DMSeg return dm content.
func (s *Service) DMSeg(c context.Context, tp, plat int32, mid, aid, oid, ps int64) (res []byte, err error) {
var (
sub = &model.Subject{}
flag = model.DefaultFlag
)
seg, err := s.segmentInfo(c, tp, aid, oid, ps)
if err != nil {
return
}
if sub, err = s.subject(c, tp, oid); err != nil {
return
}
if sub.State == model.SubStateClosed {
xml := []byte(seg.ToXMLHeader(sub.Oid, sub.State, 0) + `</i>`)
if xml, err = s.gzip(xml); err != nil {
return
}
res = model.Encode(flag, xml)
return
}
// get from local cache first
xml, ok := s.localCache[keySeg(tp, oid, seg.Cnt, seg.Num)]
if ok {
res = model.Encode(flag, xml)
return
}
// NOTE 将视频弹幕上限调整为 2*maxlimit条
data, err := s.dao.RecFlag(c, mid, aid, oid, 2*sub.Maxlimit, seg.Start, seg.End, plat)
if err == nil {
flag = data
}
// get from remote cache or database
if xml, err = s.singleGenSegXML(c, aid, sub, seg); err != nil {
return
}
res = model.Encode(flag, xml)
return
}
func (s *Service) singleGenSegXML(c context.Context, aid int64, sub *model.Subject, seg *model.Segment) (xml []byte, err error) {
key := keySeg(sub.Type, sub.Oid, seg.Cnt, seg.Num)
v, err, _ := s.singleGroup.Do(key, func() (res interface{}, err error) {
data, err := s.dao.XMLSegCache(c, sub.Type, sub.Oid, seg.Cnt, seg.Num)
if err != nil {
return
}
if len(data) > 0 {
res = data
return
}
if data, err = s.dmSegXML(c, aid, sub, seg); err != nil {
return
}
s.cache.Do(c, func(ctx context.Context) {
s.dao.SetXMLSegCache(ctx, sub.Type, sub.Oid, seg.Cnt, seg.Num, data)
})
return data, err
})
if err != nil {
return
}
xml = v.([]byte)
return
}
// segmentInfo get segment info of oid.
func (s *Service) segmentInfo(c context.Context, tp int32, aid, oid, ps int64) (seg *model.Segment, err error) {
var duration int64
data, ok := s.localCache[keyDuration(tp, oid)]
if ok {
duration, err = strconv.ParseInt(string(data), 10, 64)
} else {
duration, err = s.videoDuration(c, aid, oid)
}
if err != nil {
return
}
if duration != 0 && ps >= duration {
log.Warn("oid:%d ps:%d larger than duration:%d", oid, ps, duration)
err = ecode.NotModified
return
}
seg = model.SegmentInfo(ps, duration)
return
}
func (s *Service) dmNormalIds(c context.Context, tp int32, oid int64, cnt, n, ps, pe, limit int64) (dmids []int64, err error) {
dmids, err = s.dao.DMIDCache(c, tp, oid, cnt, n, limit)
if err != nil || len(dmids) == 0 {
if dmids, err = s.dao.DMIDs(c, tp, oid, ps, pe, limit, model.PoolNormal); err != nil {
return
}
}
return
}
//dmSegSubtitlesIds dm Subtitles
func (s *Service) dmSegSubtitlesIds(c context.Context, tp int32, oid int64, ps, pe, limit int64) (dmids []int64, err error) {
dmids, err = s.dao.DMIDSubtitlesCache(c, tp, oid, ps, pe, limit)
if err != nil || len(dmids) == 0 {
if dmids, err = s.dao.DMIDs(c, tp, oid, ps, pe, limit, model.PoolSubtitle); err != nil || len(dmids) == 0 {
return
}
}
return
}

View File

@@ -0,0 +1,56 @@
package service
import (
"context"
"testing"
"go-common/app/interface/main/dm2/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestDmNormalIds(t *testing.T) {
var (
tp int32 = 1
oid int64 = 19
cnt int64 = 1
n int64 = 2
ps int64
pe int64 = 1000
limit int64 = 100
)
Convey("test edit dm state", t, func() {
dmids, err := svr.dmNormalIds(context.Background(), tp, oid, cnt, n, ps, pe, limit)
So(err, ShouldBeNil)
So(dmids, ShouldNotBeNil)
})
}
func TestSegmentInfo(t *testing.T) {
var (
tp int32 = 1
aid int64 = 10097265
oid int64 = 1508
ps int64 = 10
c = context.TODO()
seg *model.Segment
err error
)
if seg, err = svr.segmentInfo(c, tp, aid, oid, ps); err != nil {
t.Fatalf("s.segmentInfo(%d, %d) error(%v)", aid, oid, err)
}
t.Logf("aid:%d, oid:%d, segment:%+v", aid, oid, seg)
}
func TestService_JudgeDMList(t *testing.T) {
var (
cid, dmid int64 = 9967369, 719232849
)
dm, err := svr.JudgeDms(context.TODO(), 1, cid, dmid)
if err != nil {
t.Fatal(err)
}
for _, m := range dm.List {
t.Logf("%v", m)
}
}

View File

@@ -0,0 +1,183 @@
package service
import (
"context"
"fmt"
"math"
"strconv"
"sync"
"go-common/app/interface/main/dm2/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/sync/errgroup"
)
// DM dm list.
func (s *Service) DM(c context.Context, tp int32, aid, oid int64) (res *model.DMSeg, err error) {
var (
total, size int64 = 1, model.DefaultVideoEnd
mu sync.Mutex
)
sub, err := s.subject(c, tp, oid)
if err != nil {
return
}
res = &model.DMSeg{Elems: make([]*model.Elem, 0, 2*sub.Maxlimit)}
duration, err := s.videoDuration(c, aid, oid)
if err != nil {
return
}
if duration != 0 {
total = int64(math.Ceil(float64(duration) / float64(model.DefaultPageSize)))
size = model.DefaultPageSize
}
g, ctx := errgroup.WithContext(c)
for i := int64(1); i <= total; i++ {
num := i
g.Go(func() (err error) {
var dmseg *model.DMSeg
if dmseg, err = s.dao.DMSegCache(ctx, tp, oid, total, num); err != nil {
return
}
if dmseg == nil {
ps := (num - 1) * size
pe := num * size
fmt.Println(ps, pe, total, num)
if dmseg, err = s.dmSegV2(ctx, sub, total, num, ps, pe); err != nil {
return
}
}
if dmseg != nil {
mu.Lock()
res.Elems = append(res.Elems, dmseg.Elems...)
mu.Unlock()
}
return
})
}
err = g.Wait()
return
}
// DMSegV2 dm segment new.
func (s *Service) DMSegV2(c context.Context, tp int32, mid, aid, oid, pn int64, plat int32) (res *model.DMSegResp, err error) {
page, err := s.pageinfo(c, tp, aid, oid, pn)
if err != nil {
return
}
ps := (page.Num - 1) * page.Size
pe := page.Num * page.Size
sub, err := s.subject(c, tp, oid)
if err != nil {
return
}
res = &model.DMSegResp{
Flag: model.DefaultFlag,
}
if sub.State == model.SubStateClosed {
return
}
flag, err := s.dao.RecFlag(c, mid, aid, oid, 2*sub.Maxlimit, ps, pe, plat)
if err == nil {
res.Flag = flag
}
dmseg, err := s.dao.DMSegCache(c, tp, oid, page.Total, page.Num)
if err != nil {
return
}
if dmseg != nil {
res.Dms = dmseg.Elems
return
}
if dmseg, err = s.dmSegV2(c, sub, page.Total, page.Num, ps, pe); err != nil {
return
}
res.Dms = dmseg.Elems
s.cache.Do(c, func(ctx context.Context) {
s.dao.SetDMSegCache(ctx, tp, oid, page.Total, page.Num, dmseg) // add mc cache
})
return
}
func (s *Service) dmSegV2(c context.Context, sub *model.Subject, total, num, ps, pe int64) (res *model.DMSeg, err error) {
var (
cache = true
limit = 2 * sub.Maxlimit
dmids = make([]int64, 0, limit)
)
res = &model.DMSeg{Elems: make([]*model.Elem, 0, limit)}
normalIds, err := s.dmNormalIds(c, sub.Type, sub.Oid, total, num, ps, pe, limit)
if err != nil {
return
}
dmids = append(dmids, normalIds...)
if sub.Childpool > 0 {
var subtitleIds []int64
if subtitleIds, err = s.dmSegSubtitlesIds(c, sub.Type, sub.Oid, ps, pe, limit); err != nil {
return
}
dmids = append(dmids, subtitleIds...)
}
if len(dmids) <= 0 {
return
}
elemsCache, missed, err := s.dao.IdxContentCacheV2(c, sub.Type, sub.Oid, dmids)
if err != nil {
missed = dmids
cache = false
} else {
res.Elems = append(res.Elems, elemsCache...)
}
if len(missed) == 0 {
return
}
dms, err := s.dmsSeg(c, sub.Type, sub.Oid, missed)
if err != nil {
return
}
for _, dm := range dms {
if e := dm.ToElem(); e != nil {
res.Elems = append(res.Elems, e)
}
}
if cache && len(dms) > 0 {
s.cache.Do(c, func(ctx context.Context) {
s.dao.AddIdxContentCache(ctx, sub.Type, sub.Oid, dms, false) // add memcache,realname=false
})
}
return
}
// pageinfo get page info of oid.
func (s *Service) pageinfo(c context.Context, tp int32, aid, oid, pn int64) (p *model.Page, err error) {
var duration int64
data, ok := s.localCache[keyDuration(tp, oid)]
if ok {
duration, err = strconv.ParseInt(string(data), 10, 64)
} else {
duration, err = s.videoDuration(c, aid, oid)
}
if err != nil {
return
}
if duration == 0 {
p = &model.Page{
Num: pn,
Size: model.DefaultVideoEnd,
Total: 1,
}
} else {
p = &model.Page{
Num: pn,
Size: model.DefaultPageSize,
Total: int64(math.Ceil(float64(duration) / float64(model.DefaultPageSize))),
}
}
if pn > p.Total {
log.Warn("oid:%d pn:%d larger than total page:%d", oid, pn, p.Total)
err = ecode.NotModified
return
}
return
}

View File

@@ -0,0 +1,55 @@
package service
import (
"context"
"fmt"
"testing"
"go-common/app/interface/main/dm2/model"
. "github.com/smartystreets/goconvey/convey"
)
func BenchmarkDMXML(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, err := svr.DMXML(context.TODO(), 1, 1221)
if err != nil {
fmt.Println(err)
}
}
})
}
func TestDMDistribution(t *testing.T) {
res, err := svr.DMDistribution(context.TODO(), 1, 1221, 5)
if err != nil {
t.Error(err)
}
t.Log(res)
}
func TestAsyncAddDM(t *testing.T) {
dm := model.DM{
ID: 123,
Content: &model.Content{
ID: 123,
},
}
if err := svr.asyncAddDM(&dm, 1, 1); err != nil {
t.Error(err)
}
}
func TestDMList(t *testing.T) {
var (
oid int64 = 5
dmids = []int64{719149905, 719149906, 719149907}
)
Convey("test dm list", t, func() {
res, err := svr.dmList(context.TODO(), 1, oid, dmids)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
}

View File

@@ -0,0 +1,768 @@
package service
import (
"context"
"encoding/json"
"fmt"
"regexp"
"strconv"
"strings"
"time"
"go-common/app/interface/main/dm2/model"
"go-common/app/interface/main/dm2/model/oplog"
account "go-common/app/service/main/account/api"
"go-common/app/service/main/archive/api"
arcMdl "go-common/app/service/main/archive/model/archive"
figureMdl "go-common/app/service/main/figure/model"
filterMdl "go-common/app/service/main/filter/api/grpc/v1"
locmdl "go-common/app/service/main/location/model"
spyMdl "go-common/app/service/main/spy/model"
ugcPayMdl "go-common/app/service/main/ugcpay/api/grpc/v1"
seasonMbl "go-common/app/service/openplatform/pgc-season/api/grpc/season/v1"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
)
var (
msgRegex = regexp.MustCompile(`^(\s|\xE3\x80\x80)*$`) // 全文仅空格
_dateFormat = "20060102"
)
//Post dm post
func (s *Service) Post(c context.Context, dm *model.DM, aid, rnd int64) (err error) {
// 验证主题是否存在
sub, err := s.subject(c, dm.Type, dm.Oid)
if err != nil {
return
}
if sub.State == model.SubStateClosed {
err = ecode.DMForbidPost
return
}
if sub.Maxlimit == 0 {
return
}
// 验证海外用户
if err = s.checkOverseasUser(c); err != nil {
return
}
// 验证账号信息
myinfo, err := s.checkAccountInfo(c, dm.Mid)
if err != nil {
return
}
// 验证弹幕内容
if err = s.checkMsg(c, dm, myinfo); err != nil {
return
}
// 验证弹幕progress
duration, err := s.videoDuration(c, aid, dm.Oid)
if err != nil {
return
}
if duration > 0 && int64(dm.Progress) > duration {
return ecode.DMProgressTooBig
}
// 验证稿件信息
arc, err := s.checkArchiveInfo(c, aid, dm.Oid, myinfo.GetRank(), dm.Content.Mode)
if err != nil {
return
}
if sub.Mid != dm.Mid && arc.Rights.UGCPay == arcMdl.AttrYes {
if err = s.archiveUgcPay(c, dm.Mid, aid); err != nil {
return
}
}
// 验证弹幕发送速率
if err = s.checkPubRate(c, dm, sub.Mid, rnd, myinfo.GetRank()); err != nil {
return
}
// 判定用户发送速率
if err = s.checkRateLimit(c, myinfo.GetRank(), dm.Mid); err != nil {
return
}
// 验证弹幕颜色
if err = s.checkMsgColor(sub.Mid, myinfo, dm); err != nil {
return
}
// 判定弹幕字体大小
if err = s.checkFontsize(dm, sub.Mid, myinfo.GetRank()); err != nil {
return
}
// 验证mode 1、4、5、6
if err = s.checkNormalMode(sub.Mid, myinfo, dm); err != nil {
return
}
// 验证mode 7发送权限
if err = s.checkAdvanceMode(c, dm, sub.Mid, myinfo); err != nil {
return
}
// 验证特殊弹幕池(pool=1 pool=2)发送权限
if err = s.checkSpecialPool(c, dm, sub.Mid, myinfo); err != nil {
return
}
// 生成弹幕id
if err = s.genDMID(c, dm); err != nil {
return
}
// 弹幕屏蔽词过滤
if err = s.checkFilterService(c, dm, arc); err != nil {
return
}
// 检查up的全局屏蔽词
if err = s.checkUpFilter(c, dm, sub.Mid, myinfo.GetRank()); err != nil {
return
}
// 弹幕的先审后发和先发后审
if err = s.checkMonitor(c, sub, dm); err != nil {
return
}
// 垃圾弹幕过滤
if err = s.checkUnusualAction(c, dm, myinfo); err != nil {
return
}
// bnj专用 黑名单过滤
s.checkShield(c, aid, dm)
// 发消息给job异步落库
if err = s.asyncAddDM(dm, arc.Aid, arc.TypeID); err != nil {
return
}
remark := fmt.Sprintf("新增弹幕,ip:%s,port:%s", metadata.String(c, metadata.RemoteIP), metadata.String(c, metadata.RemotePort))
s.OpLog(c, dm.Oid, dm.Mid, time.Now().Add(time.Second).Unix(), 1, []int64{dm.ID}, "status", "", strconv.FormatInt(int64(dm.State), 10), remark, oplog.SourcePlayer, oplog.OperatorMember)
// 弹幕行为日志
s.dao.ReportDmLog(c, dm)
// 弹幕广播
select {
case s.broadcastChan <- &broadcast{Aid: arc.Aid, Rnd: rnd, DM: dm}:
default:
log.Error("broadcast channel is full")
}
return
}
// checkUnusualAction check unusual post dm
func (s *Service) checkUnusualAction(c context.Context, dm *model.DM, accInfo *account.Profile) (err error) {
var (
ip = metadata.String(c, metadata.RemoteIP)
userScore *spyMdl.UserScore
figureWithRank *figureMdl.FigureWithRank
dmDailyLimit *model.DailyLimiter
)
if !s.garbageDanmu {
return
}
if userScore, err = s.spyRPC.UserScore(c, &spyMdl.ArgUserScore{
Mid: accInfo.GetMid(),
IP: ip,
}); err != nil {
// dragrade spy service
err = nil
return
}
if figureWithRank, err = s.figureRPC.UserFigure(c, &figureMdl.ArgUserFigure{
Mid: accInfo.GetMid(),
}); err != nil {
if ecode.Cause(err).Code() != ecode.FigureNotFound.Code() {
log.Error("checkUnusualAction.UserFigure(mid:%v) error(%v)", accInfo.GetMid(), err)
return
}
err = nil
return
}
if accInfo.GetMid() > 50000000 && accInfo.GetLevel() <= 2 && figureWithRank.Percentage > 40 && userScore.Score < 90 {
if dmDailyLimit, err = s.dao.GetDmDailyLimitCache(c, accInfo.GetMid()); err != nil {
return
}
now := time.Now().Format(_dateFormat)
if dmDailyLimit == nil || now > dmDailyLimit.Date {
dmDailyLimit = &model.DailyLimiter{
Date: time.Now().Format(_dateFormat),
Count: 0,
}
}
dmDailyLimit.Count++
if dmDailyLimit.Count > 5 {
dm.State = model.StateDelete
s.OpLog(c, dm.Oid, dm.Mid, time.Now().Unix(), 1, []int64{dm.ID}, "status", "", strconv.FormatInt(int64(dm.State), 10), "垃圾弹幕发送", oplog.SourcePlayer, oplog.OperatorMember)
s.dao.ReportDmGarbageLog(c, dm)
}
if err = s.dao.SetDmDailyLimitCache(c, accInfo.GetMid(), dmDailyLimit); err != nil {
return
}
return
}
return
}
func (s *Service) checkFontsize(dm *model.DM, upid int64, rank int32) (err error) {
if !s.isSuperUser(rank) && dm.Content.Mode != model.ModeSpecial {
if dm.Content.FontSize != 18 && dm.Content.FontSize != 25 {
dm.Content.FontSize = 25
}
}
return
}
func (s *Service) checkMsgColor(upid int64, profile *account.Profile, dm *model.DM) (err error) {
if s.isSuperUser(profile.GetRank()) || dm.Mid == upid {
return
}
if profile.GetLevel() <= 1 && dm.Content.Color != 0xffffff {
err = ecode.DMMsgNoColorPerm
}
return
}
// 验证mode 1、4、5、6
func (s *Service) checkNormalMode(upid int64, profile *account.Profile, dm *model.DM) (err error) {
if s.isSuperUser(profile.GetRank()) {
return
}
switch dm.Content.Mode {
case 1:
if profile.GetLevel() < 1 && upid != dm.Mid {
err = ecode.DMMsgNoPubPerm
}
case 4:
if profile.GetLevel() < 3 && upid != dm.Mid {
err = ecode.DMMsgNoPubBottomPerm
}
case 5:
if profile.GetLevel() < 3 && upid != dm.Mid {
err = ecode.DMMsgNoPubTopPerm
}
case 6:
err = ecode.DMMsgNoPubAdvancePerm
}
return
}
// 验证 mode 7
func (s *Service) checkAdvanceMode(c context.Context, dm *model.DM, upid int64, profile *account.Profile) (err error) {
if dm.Content.Mode != 7 || dm.Mid == upid || s.isSuperUser(profile.GetRank()) {
return
}
if profile.GetLevel() <= 1 {
err = ecode.DMMsgNoPubStylePerm
return
}
var adv *model.AdvanceCmt
if adv, err = s.advanceComment(c, dm.Oid, dm.Mid, "sp"); err != nil {
return
}
if adv.Type != "buy" && adv.Type != "accept" {
err = ecode.DMMsgNoPubStylePerm
}
return
}
// 验证 pool 1、2
func (s *Service) checkSpecialPool(c context.Context, dm *model.DM, upid int64, profile *account.Profile) (err error) {
if (dm.Pool != 1 && dm.Pool != 2) || dm.Mid == upid || s.isSuperUser(profile.Rank) {
return
}
if dm.Pool == model.PoolSubtitle {
dm.Pool = model.PoolNormal
log.Warn("force change(%+v) pool to normal", dm)
return
}
if profile.Level <= 1 {
err = ecode.DMMsgNoPubStylePerm
return
}
var adv *model.AdvanceCmt
if dm.Pool == model.PoolSpecial {
if adv, err = s.advanceComment(c, dm.Oid, dm.Mid, "advance"); err != nil {
return
}
if adv.Type != "buy" && adv.Type != "accept" {
err = ecode.DMMsgNoPubStylePerm
return
}
if profile.Rank <= 10000 {
err = ecode.DMMsgNoPubStylePerm
}
}
return
}
func (s *Service) checkFilterService(c context.Context, dm *model.DM, arc *api.Arc) (err error) {
if dm.Content.Mode == 7 || dm.Content.Mode == 8 || dm.Content.Mode == 9 {
return
}
var (
sid int32
pid int64
rid = arc.TypeID
keys []string
filterReply *filterMdl.FilterReply
seasonReply *seasonMbl.CardsInfoReply
)
if v, ok := s.arcTypes[int16(rid)]; ok {
pid = int64(v)
}
if arc.AttrVal(arcMdl.AttrBitIsBangumi) == arcMdl.AttrYes {
if seasonReply, err = s.seasonRPC.CardsByAids(c, &seasonMbl.SeasonAidReq{
Aids: []int32{int32(arc.Aid)},
}); err != nil || seasonReply == nil {
log.Error("s.seasonRPC.CardsByAids(%d) error(%v)", arc.Aid, err) // NOTE ignore error and continue
} else if seasonInfo, ok := seasonReply.Cards[int32(arc.Aid)]; !ok {
log.Error("seasonReply.Cards(%d) don't exist", arc.Aid)
} else {
sid = seasonInfo.SeasonId
}
}
if sid > 0 {
keys = append(keys, fmt.Sprintf("season:%d", sid))
}
keys = append(keys, fmt.Sprintf("typeid:%d", rid))
keys = append(keys, fmt.Sprintf("typeid:%d", pid))
keys = append(keys, fmt.Sprintf("cid:%d", dm.Oid))
keys = append(keys, fmt.Sprintf("aid:%d", arc.Aid))
if filterReply, err = s.filterRPC.Filter(c, &filterMdl.FilterReq{
Area: "danmu",
Message: dm.Content.Msg,
TypeId: int64(rid),
Id: dm.ID,
Oid: dm.Oid,
Mid: dm.Mid,
Keys: keys,
}); err != nil {
log.Error("checkFilterService(dm:%+v),err(%v)", dm, err)
return
}
if filterReply.Level > 0 || filterReply.Limit == model.SpamBlack || filterReply.Limit == model.SpamOverflow {
dm.State = model.StateFilter
log.Info("filter service delete(dmid:%d,data:+%v)", dm.ID, filterReply)
remark := filterReply.Result
if filterReply.Limit == model.SpamBlack {
remark = "命中反垃圾黑名单"
}
if filterReply.Limit == model.SpamOverflow {
remark = "超过反垃圾限制次数"
}
s.OpLog(c, dm.Oid, dm.Mid, time.Now().Unix(), 1, []int64{dm.ID}, "status", "", strconv.FormatInt(int64(dm.State), 10), remark, oplog.SourcePlayer, oplog.OperatorMember)
return
}
if filterReply.Ai != nil && len(filterReply.Ai.Scores) > 0 && filterReply.Ai.Scores[0] > filterReply.Ai.Threshold {
dm.State = model.StateAiDelete
s.OpLog(c, dm.Oid, dm.Mid, time.Now().Unix(), 1, []int64{dm.ID}, "status", "", strconv.FormatInt(int64(dm.State), 10), "ai 反垃圾屏蔽", oplog.SourcePlayer, oplog.OperatorMember)
return
}
return
}
// checkUpFilter up主屏蔽词过滤
func (s *Service) checkUpFilter(c context.Context, dm *model.DM, upid int64, rank int32) (err error) {
// 命中屏蔽词系统 或者 mode 8 9 后则不再进行校验up主屏蔽词
if dm.State == model.StateFilter || dm.Content.Mode == model.ModeCode || dm.Content.Mode == model.ModeBAS {
return
}
var (
msg string
fltModes []int32
texts, regexs, users []string
)
// up主全局屏蔽词
filters, err := s.UpFilters(c, upid)
if err != nil {
return
}
for _, f := range filters {
switch f.Type {
case 0: // 文本类型
texts = append(texts, f.Filter)
case 1: // 正则类型
f.Filter = strings.Replace(f.Filter, "/", "\\/", -1)
regexs = append(regexs, f.Filter)
case 2: // 用户黑名单
f.Filter = strings.ToLower(f.Filter)
users = append(users, f.Filter)
case 4, 5, 6, 7: // 4:down,5:up,6:reverse,7:special
fltModes = append(fltModes, int32(f.Type))
}
}
// check content filter
if dm.Content.Mode == model.ModeSpecial {
strs := strings.Split(dm.Content.Msg, ",")
if len(strs) < 4 || len(strs[4]) < 2 {
log.Error("s.checkUpFilter(%s) error(spec content format err)", dm)
return
}
msg = strs[4][1 : len(strs[4])-1]
} else {
msg = dm.Content.Msg
}
// 验证up设置的mode屏蔽
for _, mode := range fltModes {
if mode == dm.Content.Mode {
dm.State = model.StateBlock
s.OpLog(c, dm.Oid, dm.Mid, time.Now().Unix(), 1, []int64{dm.ID}, "status", "", strconv.FormatInt(int64(dm.State), 10), "命中类型屏蔽("+fmt.Sprint(mode)+"", oplog.SourcePlayer, oplog.OperatorMember)
return
}
}
// 关键字过滤
for _, text := range texts {
if strings.Contains(strings.ToLower(msg), strings.ToLower(text)) {
dm.State = model.StateBlock
s.OpLog(c, dm.Oid, dm.Mid, time.Now().Unix(), 1, []int64{dm.ID}, "status", "", strconv.FormatInt(int64(dm.State), 10), "命中关键字屏蔽("+text+"", oplog.SourcePlayer, oplog.OperatorMember)
return
}
}
// 正则过滤
for _, reg := range regexs {
rc, rErr := regexp.Compile(reg)
if rErr != nil {
log.Error("regexp.Compile(%s) error(%v)", reg, rErr)
continue
}
if rc.MatchString(msg) {
dm.State = model.StateBlock
s.OpLog(c, dm.Oid, dm.Mid, time.Now().Unix(), 1, []int64{dm.ID}, "status", "", strconv.FormatInt(int64(dm.State), 10), "命中正则屏蔽("+reg+"", oplog.SourcePlayer, oplog.OperatorMember)
return
}
}
// 验证up设置的屏蔽用户
hashID := model.Hash(dm.Mid, uint32(dm.Content.IP))
for _, user := range users {
if hashID == user {
dm.State = model.StateBlock
s.OpLog(c, dm.Oid, dm.Mid, time.Now().Unix(), 1, []int64{dm.ID}, "status", "", strconv.FormatInt(int64(dm.State), 10), "屏蔽黑名单屏蔽("+hashID+"", oplog.SourcePlayer, oplog.OperatorMember)
return
}
}
return
}
func (s *Service) checkRateLimit(c context.Context, rank int32, mid int64) (err error) {
now := time.Now().Unix()
ltime, ok := model.LimitPerMin[rank]
if !ok {
ltime = model.LimitPerMin[0]
}
limiter, err := s.dao.DMLimitCache(c, mid)
if err != nil {
return
}
if limiter == nil {
limiter = &model.Limiter{Allowance: ltime, Timestamp: now}
}
allowance := limiter.Allowance + ((now - limiter.Timestamp) * ltime / 600)
if allowance > ltime {
allowance = ltime
}
if allowance < 1 {
err = ecode.DMMsgPubTooFast
allowance = 0
} else {
allowance--
}
limiter = &model.Limiter{Allowance: allowance, Timestamp: now}
s.dao.AddDMLimitCache(c, mid, limiter) // NOTE omit error
return
}
func (s *Service) checkAccountInfo(c context.Context, mid int64) (profile *account.Profile, err error) {
var (
profileReply *account.ProfileReply
)
if profileReply, err = s.accountRPC.Profile3(c, &account.MidReq{
Mid: mid,
}); err != nil {
log.Error("accRPC.UserInfo(%v) error(%v)", mid, err)
return
}
if profileReply.GetProfile().GetIdentification() == 0 && profileReply.GetProfile().GetTelStatus() == 0 {
err = ecode.UserCheckNoPhone
return
}
if profileReply.GetProfile().GetIdentification() == 0 && profileReply.GetProfile().GetTelStatus() == 2 {
err = ecode.UserCheckInvalidPhone
return
}
if profileReply.GetProfile().GetEmailStatus() == 0 && profileReply.GetProfile().GetTelStatus() == 0 {
err = ecode.UserCheckNoPhone
return
}
if profileReply.GetProfile().GetSilence() == 1 {
err = ecode.UserDisabled
return
}
if profileReply.GetProfile().GetMoral() < 60 {
err = ecode.LackOfScores
}
if profile = profileReply.GetProfile(); profile == nil {
err = ecode.UserNotExist
}
return
}
func (s *Service) checkMsg(c context.Context, dm *model.DM, profile *account.Profile) (err error) {
var (
msg = dm.Content.Msg
msgLen = len([]rune(dm.Content.Msg))
isNormalMode = s.isNormalMode(dm.Content.Mode)
)
if msgRegex.MatchString(msg) { // 空白弹幕
err = ecode.DMMsgIlleagel
return
}
if profile.GetRank() < 20000 && profile.GetLevel() == 1 && msgLen > 20 {
err = ecode.DMMsgTooLongLevel1
return
}
if (msgLen > model.MaxLenDefMsg && isNormalMode) || (msgLen > model.MaxLen7Msg && dm.Content.Mode == 7) {
err = ecode.DMMsgTooLong
return
}
if isNormalMode && (strings.Contains(msg, `\n`) || strings.Contains(msg, `/n`)) {
err = ecode.DMMsgIlleagel
return
}
// 校验单字符刷屏
if msgLen == 1 {
var count int64
if count, err = s.dao.CharPubCnt(c, dm.Mid, dm.Oid); err != nil {
return
}
if count+1 > 3 {
err = ecode.DMMsgPubTooFast
return
}
if err = s.dao.IncrCharPubCnt(c, dm.Mid, dm.Oid); err != nil {
return
}
} else {
if err = s.dao.DelCharPubCnt(c, dm.Mid, dm.Oid); err != nil {
return
}
}
return
}
func (s *Service) checkPubRate(c context.Context, dm *model.DM, upid, rnd int64, rank int32) (err error) {
ip := metadata.String(c, metadata.RemoteIP)
cached, err := s.dao.MsgPublock(c, dm.Mid, dm.Content.Color, rnd, dm.Content.Mode, dm.Content.FontSize, ip, dm.Content.Msg)
if err != nil {
return
}
if cached {
err = ecode.DMMsgPubTooFast
return
}
s.dao.AddMsgPubLock(c, dm.Mid, dm.Content.Color, rnd, dm.Content.Mode, dm.Content.FontSize, ip, dm.Content.Msg)
if !s.isSuperUser(rank) {
if cached, err = s.dao.OidPubLock(c, dm.Mid, dm.Oid, ip); err != nil {
return
}
if cached {
err = ecode.DMMsgPubTooFast
return
}
s.dao.AddOidPubLock(c, dm.Mid, dm.Oid, ip)
}
if rank <= 15000 {
var count int64
count, err = s.dao.PubCnt(c, dm.Mid, dm.Content.Color, dm.Content.Mode, dm.Content.FontSize, ip, dm.Content.Msg)
if err != nil {
return
}
if count >= 8 && dm.Mid != upid {
arg := &account.MoralReq{
Mid: dm.Mid,
Moral: -0.25,
Oper: "",
Reason: "恶意刷弹幕",
Remark: "云屏蔽",
RealIp: ip,
}
if _, err = s.accountRPC.AddMoral3(c, arg); err != nil {
log.Error("s.accRPC.AddMoral2(%v) error(%v)", arg, err)
return
}
err = ecode.DMMsgPubTooFast
return
}
if err = s.dao.IncrPubCnt(c, dm.Mid, dm.Content.Color, dm.Content.Mode, dm.Content.FontSize, ip, dm.Content.Msg); err != nil {
return
}
}
return
}
func (s *Service) checkArchiveInfo(c context.Context, aid, oid int64, rank, mode int32) (arc *api.Arc, err error) {
// if _, err = s.arcRPC.Video3(c, &arcMdl.ArgVideo2{
// Aid: aid,
// Cid: oid,
// RealIP: metadata.String(c, metadata.RemoteIP),
// }); err != nil {
// log.Error("s.arcRPC.Video3(aid:%v,oid:%v) error(%v)", aid, oid, err)
// return
// }
arg := &arcMdl.ArgAid2{
Aid: aid,
RealIP: metadata.String(c, metadata.RemoteIP),
}
if arc, err = s.arcRPC.Archive3(c, arg); err != nil {
log.Error("s.arcRPC.Archive3(%v) error(%v)", arg, err)
return
}
if arc.State < 0 && arc.State != -6 {
err = ecode.DMArchiveIlleagel // 禁止向未审核的视频发送弹幕
return
}
return
}
func (s *Service) archiveUgcPay(c context.Context, mid int64, aid int64) (err error) {
var (
resp *ugcPayMdl.AssetRelationResp
)
resp, err = s.ugcPayRPC.AssetRelation(c, &ugcPayMdl.AssetRelationReq{
Mid: mid,
Oid: aid,
Otype: model.UgcPayTypeArchive,
})
if err != nil {
log.Error("archiveUgcPay(aid:%d,mid:%d),error(%v)", aid, mid, err)
return
}
if resp.State != model.UgcPayRelationStatePaid {
err = ecode.DMNotpayForPost
return
}
return
}
func (s *Service) advanceComment(c context.Context, oid, mid int64, mode string) (adv *model.AdvanceCmt, err error) {
if adv, err = s.dao.AdvanceCmtCache(c, oid, mid, mode); err != nil {
log.Error("s.dao.AdvanceCmtCache mid=%d oid=%d mode=%s error(%v)", mid, oid, mode, err)
return
}
if adv == nil {
if adv, err = s.dao.AdvanceCmt(c, oid, mid, mode); err != nil {
log.Error("s.dao.AdvanceCmt mid=%d oid=%d mode=%s error(%v)", mid, oid, mode, err)
return
}
if adv == nil {
adv = &model.AdvanceCmt{}
}
if err = s.dao.AddAdvanceCmtCache(c, oid, mid, mode, adv); err != nil {
log.Error("s.dao.AddAdvanceCmtCache mid=%d oid=%d mode=%s error(%v)", mid, oid, mode, err)
return
}
}
return
}
func (s *Service) genDMID(c context.Context, dm *model.DM) (err error) {
dmid, err := s.seqRPC.ID(c, s.seqDmArg)
if err != nil {
return
}
dm.ID = dmid
dm.Content.ID = dmid
if dm.ContentSpe != nil {
dm.ContentSpe.ID = dmid
}
return
}
// checkMonitor check the oid is monitored
func (s *Service) checkMonitor(c context.Context, sub *model.Subject, dm *model.DM) (err error) {
if dm.State != model.StateNormal {
return
}
if sub.AttrVal(model.AttrSubMonitorBefore) == model.AttrYes {
dm.State = model.StateMonitorBefore
} else if sub.AttrVal(model.AttrSubMonitorAfter) == model.AttrYes {
dm.State = model.StateMonitorAfter
} else {
return
}
return
}
func (s *Service) asyncAddDM(dm *model.DM, aid int64, tid int32) (err error) {
var (
data []byte
msg = &struct {
*model.DM
Aid int64 `json:"aid"`
Tid int32 `json:"tid"`
}{
DM: dm,
Aid: aid,
Tid: tid,
}
c = context.TODO()
)
if data, err = json.Marshal(msg); err != nil {
log.Error("json.Marshal(%+v) error(%v)", msg, err)
return
}
act := &model.Action{Action: model.ActAddDM, Data: data}
for i := 0; i < 3; i++ {
if err = s.dao.SendAction(c, fmt.Sprint(dm.Oid), act); err != nil {
continue
} else {
break
}
}
return
}
// 用户是否是特权用户
func (s *Service) isSuperUser(rank int32) bool {
return rank >= 20000
}
// 判断弹幕模式是否是普通弹幕模式
func (s *Service) isNormalMode(mode int32) bool {
if mode == 1 || mode == 4 || mode == 5 || mode == 6 {
return true
}
return false
}
func (s *Service) checkOverseasUser(c context.Context) (err error) {
if s.conf.Supervision.Completed {
err = ecode.ServiceUpdate
return
}
now := time.Now()
loc, _ := time.LoadLocation("Asia/Shanghai")
startTime, _ := time.ParseInLocation("2006-01-02 15:04:05", s.conf.Supervision.StartTime, loc)
endTime, _ := time.ParseInLocation("2006-01-02 15:04:05", s.conf.Supervision.EndTime, loc)
if now.Before(startTime) || now.After(endTime) {
err = nil
return
}
arg := &locmdl.ArgIP{IP: metadata.String(c, metadata.RemoteIP)}
zone, err := s.locationRPC.Info(c, arg)
if err != nil {
log.Error("s.locationRPC.Info(%s) error(%v)", metadata.String(c, metadata.RemoteIP), err)
err = nil
return
}
if !strings.EqualFold(zone.Country, s.conf.Supervision.Location) {
err = ecode.ServiceUpdate
}
return
}
func (s *Service) checkShield(c context.Context, aid int64, dm *model.DM) {
if _, ok := s.aidSheild[aid]; !ok {
return
}
if _, ok := s.midsSheild[dm.Mid]; !ok {
return
}
// hit
dm.State = model.StateBlock
s.OpLog(c, dm.Oid, dm.Mid, time.Now().Unix(), 1, []int64{dm.ID}, "status", "", strconv.FormatInt(int64(dm.State), 10), "弹幕指定稿件黑名单屏蔽", oplog.SourcePlayer, oplog.OperatorMember)
}

View File

@@ -0,0 +1,128 @@
package service
import (
"context"
"testing"
"time"
"go-common/app/interface/main/dm2/model"
"go-common/library/ecode"
. "github.com/smartystreets/goconvey/convey"
)
func TestGenDMID(t *testing.T) {
dm := &model.DM{
Progress: 0,
Pool: 0,
State: 0,
Mid: 1001,
Content: &model.Content{
Msg: "dm msg test",
Mode: 1,
FontSize: 25,
Color: 165000,
IP: 100097834,
},
}
time.Sleep(time.Second * 1)
Convey("err should be nil", t, func() {
err := svr.genDMID(context.Background(), dm)
t.Logf("dmid:%v", dm.ID)
So(err, ShouldBeNil)
})
}
func TestPost(t *testing.T) {
dm := &model.DM{
ID: 1234,
Type: 1,
Oid: 1221,
Progress: 0,
Pool: 0,
State: 0,
Mid: 1001,
Content: &model.Content{
ID: 1234,
Msg: "dm msg test",
Mode: 1,
FontSize: 25,
Color: 165000,
IP: 100097834,
},
}
time.Sleep(time.Second * 1)
Convey("check dm post", t, func() {
err := svr.Post(context.TODO(), dm, 1234, 456)
So(err, ShouldBeNil)
})
}
func TestCheckOversea(t *testing.T) {
time.Sleep(time.Second * 1)
Convey("check oversea user", t, func() {
err := svr.checkOverseasUser(context.TODO())
So(err, ShouldEqual, ecode.ServiceUpdate)
})
}
func TestCheckMonitor(t *testing.T) {
dm := &model.DM{
ID: 1234,
Oid: 1221,
Progress: 0,
Pool: 0,
State: 0,
Mid: 1001,
Content: &model.Content{
ID: 1234,
Msg: "dm msg test",
Mode: 1,
FontSize: 25,
Color: 165000,
IP: 100097834,
},
}
sub := &model.Subject{
Type: 1,
Oid: 1221,
State: 0,
Attr: 16,
}
Convey("check monitor type before", t, func() {
err := svr.checkMonitor(context.TODO(), sub, dm)
So(err, ShouldBeNil)
So(dm.State, ShouldEqual, model.StateMonitorBefore)
})
dm.State = 0
sub.Attr = 32
Convey("check monitor type after", t, func() {
err := svr.checkMonitor(context.TODO(), sub, dm)
So(err, ShouldBeNil)
So(dm.State, ShouldEqual, model.StateMonitorAfter)
})
}
func TestCheckUpFilter(t *testing.T) {
dm := &model.DM{
ID: 1234,
Oid: 1,
Progress: 0,
Pool: 0,
State: 0,
Mid: 1001,
Content: &model.Content{
ID: 1234,
Msg: `[0,0,"1-1",4.5,"adsfasdf",0,0,0,0,500,0,true,"黑体",1]`,
Mode: 7,
FontSize: 25,
Color: 165000,
IP: 100097834,
},
}
Convey("test up filter", t, func() {
svr.checkUpFilter(context.TODO(), dm, 1, 2)
})
}

View File

@@ -0,0 +1,717 @@
package service
import (
"context"
"encoding/json"
"fmt"
"math"
"strings"
"sync/atomic"
"time"
"go-common/app/interface/main/dm2/lib/xregex"
"go-common/app/interface/main/dm2/model"
arcMdl "go-common/app/service/main/archive/model/archive"
assmdl "go-common/app/service/main/assist/model/assist"
"go-common/library/ecode"
"go-common/library/log"
"github.com/zhenjl/cityhash"
)
var (
_defaultVersion = uint64(0)
globalFilterVersion = uint64(time.Now().Nanosecond())
)
// AddUserFilters multi add user filter,fltMap is a map struct,key:filter content,value is comment.
func (s *Service) AddUserFilters(c context.Context, mid int64, fType int8, filters map[string]string) (res []*model.UserFilter, err error) {
// copy map,because delete operation will be used in function
fltMap := make(map[string]string)
for k, v := range filters {
fltMap[k] = v
}
res = make([]*model.UserFilter, 0)
for filter := range fltMap {
if fType == model.FilterTypeRegex {
reg := xregex.New()
if len([]rune(filter)) > model.FilterLenRegex {
err = ecode.DMFilterTooLong
return
}
if _, err = reg.Parse(filter); err != nil {
log.Error("regex parse(filter:%+v) error(%v)", filter, err)
err = ecode.DMFitlerIllegalRegex
return
}
}
if fType == model.FilterTypeText {
if len([]rune(filter)) > model.FilterLenText {
err = ecode.DMFilterTooLong
return
}
}
}
data, err := s.dao.UserFilter(c, mid, fType)
if err != nil {
return
}
for filter := range fltMap {
for _, v := range data {
if fType == model.FilterTypeText && strings.ToLower(filter) == strings.ToLower(v.Filter) {
res = append(res, v)
delete(fltMap, filter) // delete repeat filter in filter map
break
} else if filter == v.Filter {
res = append(res, v)
delete(fltMap, filter) // delete repeat filter in filter map
break
}
}
}
if len(fltMap) == 0 {
return
}
tx, err := s.dao.BeginBiliDMTrans(c)
if err != nil {
return
}
defer func() {
if err != nil {
tx.Rollback()
return
}
err = tx.Commit()
}()
count, err := s.dao.UserFilterCnt(c, tx, mid, fType)
if err != nil {
return
}
var (
num = len(fltMap)
id int64
limit int
)
switch fType {
case model.FilterTypeText:
if count+num > model.FilterMaxUserText {
err = ecode.DMFilterOverMax
return
}
limit = model.FilterMaxUserText
case model.FilterTypeRegex:
if count+num > model.FilterMaxUserReg {
err = ecode.DMFilterOverMax
return
}
limit = model.FilterMaxUserReg
case model.FilterTypeID:
if count+num > model.FilterMaxUserID {
err = ecode.DMFilterOverMax
return
}
limit = model.FilterMaxUserID
}
// the filter id in database will be used in delete method,so add user filter in for loop
for filter, comment := range fltMap {
id, err = s.dao.AddUserFilter(tx, mid, fType, filter, comment)
if err != nil {
return
}
res = append(res, &model.UserFilter{ID: id, Mid: mid, Type: fType, Filter: filter, Comment: comment})
}
if count == model.FilterNotExist { // not exist, insert into table count=num
if _, err = s.dao.InsertUserFilterCnt(c, tx, mid, fType, num); err != nil {
return
}
} else { // already exist, set count=count+1
if _, err = s.dao.UpdateUserFilterCnt(c, tx, mid, fType, 1, int64(limit)); err != nil {
return
}
}
// synchronized delete cache
s.cache.Do(c, func(ctx context.Context) {
s.dao.DelUserFilterCache(ctx, mid)
})
return
}
// UserFilters return user filters
func (s *Service) UserFilters(c context.Context, mid int64) (res []*model.UserFilter, err error) {
if res, err = s.dao.UserFilterCache(c, mid); err != nil {
err = nil // NOTE load from db if cache error
} else if len(res) > 0 {
return
}
if res, err = s.dao.UserFilters(c, mid); err != nil {
return
}
s.cache.Do(c, func(ctx context.Context) {
s.dao.AddUserFilterCache(ctx, mid, res)
})
return
}
// DelUserFilters delete user filters
func (s *Service) DelUserFilters(c context.Context, mid int64, idss []int64) (affect int64, err error) {
var (
idMap = make(map[int8][]int64)
aft, limit int64
)
res, err := s.dao.UserFiltersByID(c, mid, idss)
if err != nil {
return
}
for _, f := range res {
idMap[f.Type] = append(idMap[f.Type], f.ID)
}
tx, err := s.dao.BeginBiliDMTrans(c)
if err != nil {
log.Error("tx.BeginBiliDMTrans() error(%v)", err)
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback() error(%v)", err1)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit() error(%v)", err)
}
}()
for fType, ids := range idMap {
if aft, err = s.dao.DelUserFilter(tx, mid, ids); err != nil {
return
}
switch fType {
case model.FilterTypeText:
limit = model.FilterMaxUserText
case model.FilterTypeRegex:
limit = model.FilterMaxUserReg
case model.FilterTypeID:
limit = model.FilterMaxUserID
}
if _, err = s.dao.UpdateUserFilterCnt(c, tx, mid, fType, -aft, limit+1); err != nil {
return
}
affect = affect + aft
}
s.cache.Do(c, func(ctx context.Context) {
s.dao.DelUserFilterCache(ctx, mid)
})
return
}
// AddUpFilters add up rule.fltMap is a map struct,key:filter content,value is comment.
func (s *Service) AddUpFilters(c context.Context, mid int64, fType int8, filters map[string]string) (err error) {
fltMap := make(map[string]string)
// copy map,because delete operation will be used in function
for k, v := range filters {
fltMap[k] = v
}
for filter := range fltMap {
if fType == model.FilterTypeRegex {
if len([]rune(filter)) > model.FilterLenRegex {
err = ecode.DMFilterTooLong
return
}
reg := xregex.New()
if _, err = reg.Parse(filter); err != nil {
log.Error("filter(%s) parse error(%v)", filter, err)
err = ecode.DMFitlerIllegalRegex
return
}
}
if fType == model.FilterTypeText {
if len([]rune(filter)) > model.FilterLenText {
err = ecode.DMFilterTooLong
return
}
}
}
res, err := s.dao.UpFilter(c, mid, fType)
if err != nil {
return
}
hash := model.Hash(mid, 0)
for filter := range fltMap {
if fType == model.FilterTypeID && filter == hash { // 忽略拉黑自己
delete(fltMap, filter)
}
for _, f := range res {
if fType == model.FilterTypeText && strings.ToLower(filter) == strings.ToLower(f.Filter) {
delete(fltMap, filter)
break
} else if filter == f.Filter {
delete(fltMap, filter)
break
}
}
}
var (
limit int
num = len(fltMap)
)
if num == 0 {
return
}
tx, err := s.dao.BeginBiliDMTrans(c)
if err != nil {
log.Error("tx.BeginBiliDMTrans() error(%v)", err)
return
}
defer func() {
if err != nil {
tx.Rollback()
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit() error(%v)", err)
}
}()
count, err := s.dao.UpFilterCnt(c, tx, mid, fType)
if err != nil {
return
}
switch fType {
case model.FilterTypeText:
if count+num > model.FilterMaxUpText {
err = ecode.DMFilterOverMax
return
}
limit = model.FilterMaxUpText
case model.FilterTypeRegex:
if count+num > model.FilterMaxUpReg {
err = ecode.DMFilterOverMax
return
}
limit = model.FilterMaxUpReg
case model.FilterTypeID:
if count+num > model.FilterMaxUpID {
err = ecode.DMFilterOverMax
return
}
limit = model.FilterMaxUpID
}
if _, err = s.dao.MultiAddUpFilter(tx, mid, fType, fltMap); err != nil {
return
}
if count == model.FilterNotExist { // not exist, insert
if _, err = s.dao.InsertUpFilterCnt(c, tx, mid, fType, num); err != nil {
return
}
} else { // already exist, set count=count+fNum
if _, err = s.dao.UpdateUpFilterCnt(c, tx, mid, fType, num, limit); err != nil {
return
}
}
s.cache.Do(c, func(ctx context.Context) {
s.dao.DelUpFilterCache(ctx, mid)
})
return
}
// AddUpFilterID block user by upper or assist.fltMap is a map struct,key:user hashid,value is dm msg.
func (s *Service) AddUpFilterID(c context.Context, mid, oid int64, fltMap map[string]string) (err error) {
var (
isAssist bool
sub *model.Subject
fType = model.FilterTypeID
)
if sub, err = s.subject(c, model.SubTypeVideo, oid); err != nil {
return
}
if !s.isUpper(sub.Mid, mid) {
if err = s.isAssist(c, sub.Mid, mid); err != nil {
return
}
}
res, err := s.dao.UpFilter(c, sub.Mid, fType)
if err != nil {
return
}
hash := model.Hash(mid, 0)
for filter := range fltMap {
if filter == hash { //忽略拉黑自己
delete(fltMap, filter)
}
for _, f := range res {
if filter == f.Filter {
delete(fltMap, filter)
break
}
}
}
var (
limit int
num = len(fltMap)
)
if num == 0 {
return
}
tx, err := s.dao.BeginBiliDMTrans(c)
if err != nil {
log.Error("tx.BeginBiliDMTrans() error(%v)", err)
return
}
defer func() {
if err != nil {
tx.Rollback()
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit() error(%v)", err)
}
}()
count, err := s.dao.UpFilterCnt(c, tx, sub.Mid, fType)
if err != nil {
return
}
switch fType {
case model.FilterTypeText:
if count+num > model.FilterMaxUpText {
err = ecode.DMFilterOverMax
return
}
limit = model.FilterMaxUpText
case model.FilterTypeRegex:
if count+num > model.FilterMaxUpReg {
err = ecode.DMFilterOverMax
return
}
limit = model.FilterMaxUpReg
case model.FilterTypeID:
if count+num > model.FilterMaxUpID {
err = ecode.DMFilterOverMax
return
}
limit = model.FilterMaxUpID
}
if _, err = s.dao.MultiAddUpFilter(tx, sub.Mid, fType, fltMap); err != nil {
return
}
if count == model.FilterNotExist { // not exist, insert
if _, err = s.dao.InsertUpFilterCnt(c, tx, sub.Mid, fType, num); err != nil {
return
}
} else { // already exist, set count=count+fNum
if _, err = s.dao.UpdateUpFilterCnt(c, tx, sub.Mid, fType, num, limit); err != nil {
return
}
}
s.cache.Do(c, func(ctx context.Context) {
s.dao.DelUpFilterCache(ctx, sub.Mid)
})
if isAssist {
for filter, comment := range fltMap {
if len(comment) > 50 {
comment = fmt.Sprintf("%s...", comment[:50])
}
arg := &assmdl.ArgAssistLogAdd{
Mid: sub.Mid,
AssistMid: mid,
Type: assmdl.TypeDm,
Action: assmdl.ActDisUser,
SubjectID: sub.Mid,
ObjectID: filter,
Detail: comment,
}
select {
case s.assistLogChan <- arg:
default:
log.Error("assistLogChan is full")
}
}
}
return
}
// UpFilters return up filters
func (s *Service) UpFilters(c context.Context, mid int64) (res []*model.UpFilter, err error) {
if res, err = s.dao.UpFilterCache(c, mid); err != nil {
err = nil // load from db if cache error
} else if len(res) > 0 {
return
}
if res, err = s.dao.UpFilters(c, mid); err != nil {
return
}
s.cache.Do(c, func(ctx context.Context) {
s.dao.AddUpFilterCache(ctx, mid, res)
})
return
}
// BanUsers ban user by upper or assist.
func (s *Service) BanUsers(c context.Context, mid, oid int64, dmids []int64) (err error) {
var (
isAssist bool
fltMap = make(map[string]string)
)
sub, err := s.subject(c, model.SubTypeVideo, oid)
if err != nil {
return
}
if !s.isUpper(sub.Mid, mid) {
if err = s.isAssist(c, sub.Mid, mid); err != nil {
return
}
}
idxMap, _, err := s.dao.IndexsByid(c, sub.Type, oid, dmids)
if err != nil || len(idxMap) == 0 {
return
}
ctsmap, err := s.dao.Contents(c, oid, dmids)
if err != nil {
return
}
for _, idx := range idxMap {
hashID := model.Hash(idx.Mid, 0)
if _, ok := fltMap[hashID]; !ok {
var comment string
if v, ok := ctsmap[idx.ID]; ok {
comment = v.Msg
}
fltMap[hashID] = comment
}
}
if len(fltMap) == 0 {
return
}
if err = s.AddUpFilters(c, sub.Mid, model.FilterTypeID, fltMap); err != nil {
return
}
if isAssist {
for filter, comment := range fltMap {
if len(comment) > 50 {
comment = fmt.Sprintf("%s...", comment[:50])
}
arg := &assmdl.ArgAssistLogAdd{
Mid: sub.Mid,
AssistMid: mid,
Type: assmdl.TypeDm,
Action: assmdl.ActDisUser,
SubjectID: sub.Mid,
ObjectID: filter,
Detail: comment,
}
select {
case s.assistLogChan <- arg:
default:
log.Error("assistLogChan is full")
}
}
}
return
}
// CancelBanUsers cancel up filter by assist.
func (s *Service) CancelBanUsers(c context.Context, mid, aid int64, filters []string) (err error) {
var (
isAssist bool
arg = arcMdl.ArgAid2{Aid: aid}
)
res, err := s.arcRPC.Archive3(c, &arg)
if err != nil {
log.Error("s.arcRPC.Archive3(%v) error(%v)", arg, err)
return
}
if !s.isUpper(res.Author.Mid, mid) {
if err = s.isAssist(c, res.Author.Mid, mid); err != nil {
return
}
}
if _, err = s.EditUpFilters(c, res.Author.Mid, model.FilterTypeID, model.FilterUnActive, filters); err != nil {
return
}
if isAssist {
for _, filter := range filters {
arg := &assmdl.ArgAssistLogAdd{
Mid: res.Author.Mid,
AssistMid: mid,
Type: assmdl.TypeDm,
Action: assmdl.ActCancelDisUser,
SubjectID: res.Author.Mid,
ObjectID: filter,
Detail: "cancel ban users",
}
select {
case s.assistLogChan <- arg:
default:
log.Error("assistLogChan is full")
}
}
}
return
}
// EditUpFilters edit up filters.
func (s *Service) EditUpFilters(c context.Context, mid int64, fType, active int8, filters []string) (affect int64, err error) {
var limit int
tx, err := s.dao.BeginBiliDMTrans(c)
if err != nil {
log.Error("tx.BeginBiliDMTrans error(%v)", err)
return
}
defer func() {
if err != nil {
if err1 := tx.Rollback(); err1 != nil {
log.Error("tx.Rollback() error(%v)", err1)
}
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit() error(%v)", err)
}
}()
if affect, err = s.dao.UpdateUpFilter(tx, mid, fType, active, filters); err != nil {
return
}
switch fType {
case model.FilterTypeText:
limit = model.FilterMaxUpText
case model.FilterTypeRegex:
limit = model.FilterMaxUpReg
case model.FilterTypeID:
limit = model.FilterMaxUpID
}
if active == model.FilterUnActive {
affect = -affect
limit = 10000
}
if _, err = s.dao.UpdateUpFilterCnt(c, tx, mid, fType, int(affect), limit+1); err != nil {
return
}
s.cache.Do(c, func(ctx context.Context) {
s.dao.DelUpFilterCache(ctx, mid)
})
return
}
// GlobalFilterVersion return global filter version
func (s *Service) GlobalFilterVersion() uint64 {
return globalFilterVersion
}
// AddGlobalFilter add global filter
func (s *Service) AddGlobalFilter(c context.Context, fType int8, filter string) (ret *model.GlobalFilter, err error) {
ret = &model.GlobalFilter{}
if fType != model.FilterTypeText && fType != model.FilterTypeRegex {
err = ecode.DMFilterIllegalType
return
}
// regex varify
if fType == model.FilterTypeRegex {
if len([]rune(filter)) > model.FilterLenRegex {
err = ecode.DMFilterTooLong
return
}
reg := xregex.New()
if _, err = reg.Parse(filter); err != nil {
log.Error("filter(%s) parse error(%v)", filter, err)
err = ecode.DMFitlerIllegalRegex
return
}
}
if fType == model.FilterTypeText {
if len([]rune(filter)) > model.FilterLenText {
err = ecode.DMFilterTooLong
return
}
}
res, err := s.dao.GlobalFilter(c, fType, filter)
if err != nil {
return
}
for _, f := range res {
switch fType {
case model.FilterTypeText:
if strings.ToLower(filter) == strings.ToLower(f.Filter) {
ret = f
err = ecode.DMFilterExist
return
}
case model.FilterTypeRegex, model.FilterTypeID:
if filter == f.Filter {
ret = f
err = ecode.DMFilterExist
return
}
}
}
if ret.ID, err = s.dao.AddGlobalFilter(c, fType, filter); err != nil {
return
}
s.cache.Do(c, func(ctx context.Context) {
s.dao.DelGlobalFilterCache(ctx)
})
// update global filter version
atomic.StoreUint64(&globalFilterVersion, _defaultVersion)
return
}
// GlobalFilters return global filters
func (s *Service) GlobalFilters(c context.Context) (res []*model.GlobalFilter, err error) {
var (
done bool
sid int64
data []byte
f []*model.GlobalFilter
)
if res, err = s.dao.GlobalFilterCache(c); err != nil {
err = nil
}
if res != nil {
return
}
for !done && (len(res) < 50000) {
if f, err = s.dao.GlobalFilters(c, sid, 1000); err != nil {
return
}
if len(f) == 0 {
break
}
if len(f) < 1000 {
done = true
}
sid = f[len(f)-1].ID + 1
res = append(res, f...)
}
if len(res) > 50000 {
res = res[:50000]
}
// set empty cache
if len(res) == 0 {
res = []*model.GlobalFilter{}
atomic.StoreUint64(&globalFilterVersion, _defaultVersion)
} else {
// get global filter version
if data, err = json.Marshal(res); err != nil {
log.Error("json.Marshal() error(%v)", err)
return
}
atomic.StoreUint64(&globalFilterVersion, cityhash.CityHash64(data, 16)%math.MaxInt64)
}
s.cache.Do(c, func(ctx context.Context) {
s.dao.AddGlobalFilterCache(ctx, res)
})
return
}
// DelGlobalFilters delete global filters
func (s *Service) DelGlobalFilters(c context.Context, ids []int64) (affect int64, err error) {
if affect, err = s.dao.DelGlobalFilters(c, ids); err != nil {
return
}
if err = s.dao.DelGlobalFilterCache(c); err != nil {
return
}
s.cache.Do(c, func(ctx context.Context) {
s.dao.DelGlobalFilterCache(ctx)
})
// update global filter version
atomic.StoreUint64(&globalFilterVersion, _defaultVersion)
return
}

View File

@@ -0,0 +1,79 @@
package service
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestAddUserFilters(t *testing.T) {
Convey("test add user rule", t, func() {
fltMap := map[string]string{"11233": "this is comment"}
res, err := svr.AddUserFilters(context.TODO(), 150781, 1, fltMap)
So(err, ShouldBeNil)
for _, v := range res {
t.Logf("%+v", v)
}
})
}
func TestUserFilters(t *testing.T) {
Convey("test get user rule", t, func() {
rs, err := svr.UserFilters(context.TODO(), 27515256)
So(err, ShouldBeNil)
So(rs, ShouldNotBeEmpty)
})
}
func TestDelUserFilters(t *testing.T) {
Convey("test del user rule", t, func() {
_, err := svr.DelUserFilters(context.TODO(), 27515256, []int64{12, 3, 4})
So(err, ShouldBeNil)
})
}
func TestAddUpFilters(t *testing.T) {
Convey("test add user rule", t, func() {
fltMap := map[string]string{"\\q": "this is comment"}
err := svr.AddUpFilters(context.TODO(), 27515256, 1, fltMap)
So(err, ShouldBeNil)
})
}
func TestUpFilters(t *testing.T) {
Convey("test update user rule", t, func() {
rs, err := svr.UpFilters(context.TODO(), 10097377)
So(err, ShouldBeNil)
So(rs, ShouldNotBeEmpty)
})
}
func TestEditUpFilters(t *testing.T) {
Convey("test edit user rule", t, func() {
_, err := svr.EditUpFilters(context.TODO(), 27515256, 1, 0, []string{"\\q", "bb"})
So(err, ShouldBeNil)
})
}
func TestAddGlobalFilter(t *testing.T) {
Convey("test add global rule", t, func() {
_, err := svr.AddGlobalFilter(context.TODO(), 1, "test")
So(err, ShouldBeNil)
})
}
func TestGlobalFilters(t *testing.T) {
Convey("test global rule", t, func() {
rs, err := svr.GlobalFilters(context.TODO())
So(err, ShouldBeNil)
So(rs, ShouldNotBeEmpty)
})
}
func TestDelGlobalFilters(t *testing.T) {
Convey("test del global rule", t, func() {
_, err := svr.DelGlobalFilters(context.TODO(), []int64{12, 3, 4})
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,86 @@
package service
import (
"context"
"go-common/app/interface/main/dm2/model"
"go-common/library/ecode"
)
// UpdateMask update mask list
func (s *Service) UpdateMask(c context.Context, cid, masktime int64, fps int32, plat int8, list string) (err error) {
var sub *model.Subject
if sub, err = s.subject(c, model.SubTypeVideo, cid); err != nil {
return
}
if sub == nil {
err = ecode.ArchiveNotExist
return
}
if err = s.dao.UpdateMask(c, cid, masktime, fps, plat, list); err != nil {
return
}
if plat == model.MaskPlatMbl {
sub.AttrSet(model.AttrYes, model.AttrSubMblMaskReady)
} else {
sub.AttrSet(model.AttrYes, model.AttrSubWebMaskReady)
}
if _, err = s.dao.UptSubAttr(c, model.SubTypeVideo, cid, sub.Attr); err != nil {
return
}
tmp := *sub
s.cache.Do(c, func(ctx context.Context) {
s.dao.AddSubjectCache(ctx, &tmp)
})
mask, err := s.dao.MaskList(c, cid, plat)
if err != nil || mask == nil {
return
}
maskTmp := *mask
s.cache.Do(c, func(ctx context.Context) {
s.dao.AddMaskCache(ctx, model.SubTypeVideo, &maskTmp)
})
return
}
// MaskListWithSub .
func (s *Service) MaskListWithSub(c context.Context, cid int64, plat int8, sub *model.Subject) (mask *model.Mask, err error) {
var ok bool
if plat == model.MaskPlatWeb {
if sub.AttrVal(model.AttrSubMaskOpen) == model.AttrYes && sub.AttrVal(model.AttrSubWebMaskReady) == model.AttrYes {
ok = true
}
} else {
if sub.AttrVal(model.AttrSubMaskOpen) == model.AttrYes && sub.AttrVal(model.AttrSubMblMaskReady) == model.AttrYes {
ok = true
}
}
if !ok {
return
}
if mask, err = s.dao.DMMaskCache(c, model.SubTypeVideo, cid, plat); err != nil {
err = nil
ok = false
}
if mask == nil {
if mask, err = s.dao.MaskList(c, cid, plat); err != nil || mask == nil {
return
}
if ok {
tmp := *mask
s.cache.Do(c, func(ctx context.Context) {
s.dao.AddMaskCache(ctx, model.SubTypeVideo, &tmp)
})
}
}
return
}
// MaskList get mask info
func (s *Service) MaskList(c context.Context, cid int64, plat int8) (mask *model.Mask, err error) {
var sub *model.Subject
if sub, err = s.subject(c, model.SubTypeVideo, cid); err != nil || sub == nil {
return
}
return s.MaskListWithSub(c, cid, plat, sub)
}

View File

@@ -0,0 +1,37 @@
package service
import (
"context"
"testing"
"go-common/app/interface/main/dm2/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestUpdateMask(t *testing.T) {
var (
c = context.TODO()
cid int64 = 1352
maskTime int64 = 60
fps int32 = 25
list = ""
)
Convey("test update mask", t, func() {
err := svr.UpdateMask(c, cid, maskTime, fps, model.MaskPlatMbl, list)
So(err, ShouldBeNil)
})
}
func TestMaskList(t *testing.T) {
var (
c = context.TODO()
cid int64 = 1352
)
Convey("test mask list", t, func() {
res, err := svr.MaskList(c, cid, model.MaskPlatMbl)
t.Logf("==============%+v", res)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
}

View File

@@ -0,0 +1,211 @@
package service
import (
"context"
"fmt"
"strconv"
"time"
"go-common/app/interface/main/dm2/model"
"go-common/library/database/elastic"
"go-common/library/ecode"
"go-common/library/log"
xtime "go-common/library/time"
)
const (
_dmRecentLimit = 1000
_dateTimeFormart = "2006-01-02 15:04:05"
)
// DMUpRecent recent dm of upper.
func (s *Service) DMUpRecent(c context.Context, mid, pn, ps int64) (res *model.DmRecentResponse, err error) {
var (
mids, aids []int64
aidmap = make(map[int64]struct{})
midMap = make(map[int64]struct{})
searchResult *model.SearchRecentDMResult
)
if ps < 0 || pn < 1 {
err = ecode.RequestErr
return
}
if (pn-1)*ps >= _dmRecentLimit {
return
}
searchParam := &model.SearchRecentDMParam{
Type: model.SubTypeVideo,
UpMid: mid,
States: []int32{model.StateNormal, model.StateHide, model.StateMonitorAfter},
Ps: int(ps),
Pn: int(pn),
Field: "ctime",
Sort: elastic.OrderDesc,
}
searchResult, err = s.dao.SearhcDmRecent(c, searchParam)
if err != nil || searchResult == nil || len(searchResult.Result) == 0 || searchResult.Page == nil {
return
}
for _, item := range searchResult.Result {
if _, ok := aidmap[item.Aid]; !ok {
aids = append(aids, item.Aid)
aidmap[item.Aid] = struct{}{}
}
if _, ok := midMap[item.Mid]; !ok {
mids = append(mids, item.Mid)
midMap[item.Mid] = struct{}{}
}
}
arcMap, err := s.archiveInfos(c, aids)
if err != nil {
return
}
infoMap, err := s.accountInfos(c, mids)
if err != nil {
return
}
memebers := make([]*model.DMMember, 0, len(searchResult.Result))
for _, item := range searchResult.Result {
member := &model.DMMember{
ID: item.ID,
IDStr: strconv.FormatInt(item.ID, 10),
Type: item.Type,
Aid: item.Aid,
Oid: item.Oid,
Mid: item.Mid,
MidHash: model.Hash(item.Mid, 0),
Pool: item.Pool,
State: item.State,
Attrs: model.DMAttrNtoA(item.Attr),
Msg: item.Msg,
Mode: item.Mode,
Color: fmt.Sprintf("%06x", item.Color),
Progress: item.Progress,
FontSize: item.FontSize,
}
if ctime, err := time.ParseInLocation(_dateTimeFormart, item.Ctime, time.Now().Location()); err == nil {
member.Ctime = xtime.Time(ctime.Unix())
}
if arc, ok := arcMap[item.Aid]; ok {
member.Title = arc.Title
}
if info, ok := infoMap[item.Mid]; ok {
member.Uname = info.Name
}
memebers = append(memebers, member)
}
res = &model.DmRecentResponse{
Data: memebers,
Page: searchResult.Page,
}
if res.Page.Total > _dmRecentLimit {
res.Page.Total = _dmRecentLimit
}
return
}
// DMUpSearch danmu list from search.
func (s *Service) DMUpSearch(c context.Context, mid int64, p *model.SearchDMParams) (res *model.SearchDMResult, err error) {
var (
mids, dmids []int64
)
sub, err := s.subject(c, p.Type, p.Oid)
if err != nil {
return
}
if sub.Mid != mid {
err = ecode.AccessDenied
return
}
res = &model.SearchDMResult{}
srchData, err := s.dao.SearchDM(c, p)
if err != nil || srchData == nil {
return
}
for _, v := range srchData.Result {
dmids = append(dmids, v.ID)
}
dms, err := s.dmList(c, p.Type, p.Oid, dmids)
if err != nil {
log.Error("s.dms(%d,%v) error(%v)", p.Oid, dmids, err)
return
}
for _, dm := range dms {
mids = append(mids, dm.Mid)
}
infoMap, err := s.accountInfos(c, mids)
if err != nil {
return
}
for _, dm := range dms {
var msg string
if dm.Content != nil {
msg = dm.Content.Msg
} else {
continue
}
if dm.ContentSpe != nil {
msg = dm.ContentSpe.Msg
}
item := &model.DMMember{
ID: dm.ID,
IDStr: strconv.FormatInt(dm.ID, 10),
Type: dm.Type,
Aid: sub.Pid,
Oid: dm.Oid,
Mid: dm.Mid,
MidHash: model.Hash(dm.Mid, 0),
Pool: dm.Pool,
State: dm.State,
Attrs: dm.AttrNtoA(),
Msg: msg,
Ctime: dm.Ctime,
Mode: dm.Content.Mode,
Color: fmt.Sprintf("%06x", dm.Content.Color),
Progress: dm.Progress,
FontSize: dm.Content.FontSize,
}
if info, ok := infoMap[dm.Mid]; ok {
item.Uname = info.Name
}
res.Result = append(res.Result, item)
}
res.Page.Num = srchData.Page.Num
res.Page.Size = srchData.Page.Size
res.Page.Total = srchData.Page.Total
return
}
// UptSearchDMState update dm search state
func (s *Service) UptSearchDMState(c context.Context, dmids []int64, oid int64, state, tp int32) (err error) {
if err = s.dao.UptSearchDMState(c, dmids, oid, state, tp); err != nil {
return
}
if err = s.dao.UptSearchRecentState(c, dmids, oid, state, tp); err != nil {
return
}
return
}
// UptSearchDMPool update dm search pool
func (s *Service) UptSearchDMPool(c context.Context, dmids []int64, oid int64, pool, tp int32) (err error) {
if err = s.dao.UptSearchDMPool(c, dmids, oid, pool, tp); err != nil {
return
}
if err = s.dao.UptSearchRecentPool(c, dmids, oid, pool, tp); err != nil {
return
}
return
}
// UptSearchDMAttr update dm search attr
func (s *Service) UptSearchDMAttr(c context.Context, dmids []int64, oid int64, attr, tp int32) (err error) {
if err = s.dao.UptSearchDMAttr(c, dmids, oid, attr, tp); err != nil {
return
}
if err = s.dao.UptSearchRecentAttr(c, dmids, oid, attr, tp); err != nil {
return
}
return
}

View File

@@ -0,0 +1,54 @@
package service
import (
"context"
"testing"
"go-common/app/interface/main/dm2/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestDMUpRecent(t *testing.T) {
var (
c = context.TODO()
mid int64 = 123
pn int64 = 1
ps int64 = 10
)
Convey("dm recent", t, func() {
res, err := svr.DMUpRecent(c, mid, pn, ps)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
})
}
func TestDMUpSearch(t *testing.T) {
p := &model.SearchDMParams{
Type: 1,
Oid: 10131156,
Mids: "",
ProgressFrom: model.CondIntNil,
ProgressTo: model.CondIntNil,
CtimeFrom: "",
CtimeTo: "",
Mode: "",
State: "0,2,6",
Pool: "",
Pn: 1,
Ps: 50,
Order: "ctime",
Sort: "asc",
Keyword: "还吃几个",
}
Convey("test up dm list", t, func() {
res, err := svr.DMUpSearch(context.TODO(), 123, p)
So(err, ShouldBeNil)
So(res, ShouldNotBeNil)
for _, v := range res.Result {
t.Logf("===========\n%+v", v)
}
t.Logf("===========\n%+v", res)
So(res.Result, ShouldNotBeEmpty)
})
}

View File

@@ -0,0 +1,324 @@
package service
import (
"context"
"fmt"
"strconv"
"strings"
"sync"
"time"
"go-common/app/interface/main/dm2/conf"
"go-common/app/interface/main/dm2/dao"
"go-common/app/interface/main/dm2/model"
"go-common/app/interface/main/dm2/model/oplog"
accountCli "go-common/app/service/main/account/api"
arcCli "go-common/app/service/main/archive/api/gorpc"
assMdl "go-common/app/service/main/assist/model/assist"
assCli "go-common/app/service/main/assist/rpc/client"
coinCli "go-common/app/service/main/coin/api/gorpc"
figureCli "go-common/app/service/main/figure/rpc/client"
filterCli "go-common/app/service/main/filter/api/grpc/v1"
locCli "go-common/app/service/main/location/rpc/client"
memberCli "go-common/app/service/main/member/api/gorpc"
relCli "go-common/app/service/main/relation/rpc/client"
seqMdl "go-common/app/service/main/seq-server/model"
seqCli "go-common/app/service/main/seq-server/rpc/client"
spyCli "go-common/app/service/main/spy/rpc/client"
thumbupApi "go-common/app/service/main/thumbup/api"
ugcPayCli "go-common/app/service/main/ugcpay/api/grpc/v1"
seasonCli "go-common/app/service/openplatform/pgc-season/api/grpc/season/v1"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/log/infoc"
"go-common/library/sync/pipeline/fanout"
"go-common/library/xstr"
"golang.org/x/sync/singleflight"
)
func keySubject(tp int32, oid int64) string {
return fmt.Sprintf("sub_local_%d_%d", tp, oid)
}
func keyXML(tp int32, oid int64) string {
return fmt.Sprintf("%d_%d", tp, oid)
}
func keySeg(tp int32, oid, cnt, num int64) string {
return fmt.Sprintf("%d_%d_%d_%d", tp, oid, cnt, num)
}
func keyDuration(tp int32, oid int64) string {
return fmt.Sprintf("d_%d_%d", tp, oid)
}
type broadcast struct {
Aid int64
Rnd int64
*model.DM
}
// Service dm2 service
type Service struct {
conf *conf.Config
dao *dao.Dao
arcRPC *arcCli.Service2
accountRPC accountCli.AccountClient
assRPC *assCli.Service
coinRPC *coinCli.Service
relRPC *relCli.Service
thumbupRPC thumbupApi.ThumbupClient
locationRPC *locCli.Service
cache *fanout.Fanout
realname map[int64]int64 // key分区idvalue:cid即该分区中大于cid的视频开启实名制
singleGroup singleflight.Group
arcTypes map[int16]int16
broadcastChan chan *broadcast
// seq serer
seqDmArg *seqMdl.ArgBusiness
seqSubtitleArg *seqMdl.ArgBusiness
seqRPC *seqCli.Service2
// dm xml and dm seg local cache
localCache map[string][]byte
assistLogChan chan *assMdl.ArgAssistLogAdd
// send operation log with infoc2
dmOperationLogSvc *infoc.Infoc
opsLogCh chan *oplog.Infoc
// dm monitor merge proc
moniOidMap map[int64]struct{}
oidLock sync.Mutex
// subtitle singleFlight
subtitleSingleGroup singleflight.Group
subtitleLans model.SubtitleLans
// block
memberRPC *memberCli.Service
// filter
filterRPC filterCli.FilterClient
//figure
figureRPC *figureCli.Service
// ugc pay
ugcPayRPC ugcPayCli.UGCPayClient
// spy RPC
spyRPC *spyCli.Service
// season
seasonRPC seasonCli.SeasonClient
// garbageDanmu
garbageDanmu bool
// broadcast limit
broadcastLimit int
broadcastlimitInterval int
// view localcache
localViewCache map[string]*model.ViewDm
// bnj shield
aidSheild map[int64]struct{}
midsSheild map[int64]struct{}
}
// New return a service instance.
func New(c *conf.Config) (s *Service) {
s = &Service{
conf: c,
dao: dao.New(c),
arcRPC: arcCli.New2(c.ArchiveRPC),
assRPC: assCli.New(c.AssistRPC),
coinRPC: coinCli.New(c.CoinRPC),
relRPC: relCli.New(c.RelationRPC),
locationRPC: locCli.New(c.LocationRPC),
cache: fanout.New("cache", fanout.Worker(1), fanout.Buffer(1024)),
realname: make(map[int64]int64),
arcTypes: make(map[int16]int16),
broadcastChan: make(chan *broadcast, 1024),
localCache: make(map[string][]byte),
seqDmArg: &seqMdl.ArgBusiness{BusinessID: c.Seq.DM.BusinessID, Token: c.Seq.DM.Token},
seqSubtitleArg: &seqMdl.ArgBusiness{BusinessID: c.Seq.Subtitle.BusinessID, Token: c.Seq.Subtitle.Token},
seqRPC: seqCli.New2(c.SeqRPC),
assistLogChan: make(chan *assMdl.ArgAssistLogAdd, 1024),
dmOperationLogSvc: infoc.New(c.Infoc2),
opsLogCh: make(chan *oplog.Infoc, 1024),
moniOidMap: make(map[int64]struct{}),
memberRPC: memberCli.New(c.MemberRPC),
figureRPC: figureCli.New(c.FigureRPC),
spyRPC: spyCli.New(c.SpyRPC),
garbageDanmu: c.Switch.GarbageDanmu,
broadcastLimit: c.BroadcastLimit.Limit,
broadcastlimitInterval: c.BroadcastLimit.Interval,
localViewCache: make(map[string]*model.ViewDm),
aidSheild: make(map[int64]struct{}),
midsSheild: make(map[int64]struct{}),
}
for idStr, cid := range s.conf.Realname.Threshold {
ids, err := xstr.SplitInts(idStr)
if err != nil {
panic(err)
}
for _, id := range ids {
if _, ok := s.realname[id]; !ok {
s.realname[id] = cid
}
}
}
accountRPC, err := accountCli.NewClient(c.AccountRPC)
if err != nil {
panic(err)
}
s.accountRPC = accountRPC
ugcPayRPC, err := ugcPayCli.NewClient(c.UgcPayRPC)
if err != nil {
panic(err)
}
s.ugcPayRPC = ugcPayRPC
filterRPC, err := filterCli.NewClient(s.conf.FilterRPC)
if err != nil {
panic(err)
}
s.filterRPC = filterRPC
seasonRPC, err := seasonCli.NewClient(c.SeasonRPC)
if err != nil {
panic(fmt.Sprintf("seasonCli.NewClient.error(%v)", err))
}
s.seasonRPC = seasonRPC
thumbupRPC, err := thumbupApi.NewClient(c.ThumbupRPC)
if err != nil {
panic(err)
}
s.thumbupRPC = thumbupRPC
subtitleLans, err := s.dao.SubtitleLans(context.Background())
if err != nil {
panic(err)
}
s.subtitleLans = model.SubtitleLans(subtitleLans)
go s.broadcastproc()
go s.archiveTypeproc()
go s.localcacheproc()
go s.assistLogproc()
go s.oplogproc()
go s.monitorproc()
go s.viewProc()
go s.shieldProc()
return
}
// Ping service ping.
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
func (s *Service) archiveTypeproc() {
var mu sync.Mutex
for {
rmap, err := s.dao.TypeMapping(context.TODO())
if err != nil {
log.Error("load archive types error(%v)", err)
} else {
mu.Lock()
s.arcTypes = rmap
mu.Unlock()
}
time.Sleep(5 * time.Minute)
}
}
func (s *Service) broadcastproc() {
broadcastFmt := `["%.2f,%d,%d,%d,%d,%d,%d,%s,%d","%s"]`
for m := range s.broadcastChan {
if m.Pool == model.PoolSpecial {
continue
}
if m.State != model.StateNormal && m.State != model.StateMonitorAfter {
continue
}
if err := s.dao.BroadcastLimit(context.TODO(), m.Oid, m.Type, s.broadcastLimit, s.broadcastlimitInterval); err != nil {
if err != ecode.LimitExceed {
log.Error("dao.BroadcastLimit(oid:%v) error(%v)", m.Oid, err)
}
continue
}
hash := model.Hash(m.Mid, uint32(m.Content.IP))
msg := strings.Replace(m.Content.Msg, `\`, `\\`, -1)
msg = strings.Replace(msg, `"`, `\"`, -1)
info := fmt.Sprintf(broadcastFmt, float32(m.Progress)/1000.0, m.Content.Mode, m.Content.FontSize,
m.Content.Color, m.Ctime, m.Rnd, m.Pool, hash, m.ID, msg)
if err := s.dao.BroadcastInGoim(context.TODO(), m.Oid, m.Aid, []byte(info)); err != nil {
log.Error("dao.BroadcastInGoim(cid:%d,aid:%d,info:%s) error(%v)", m.Oid, m.Aid, info, err)
} else {
log.Info("BroadcastInGoim(%s) succeed", info)
}
if err := s.dao.Broadcast(context.Background(), m.Oid, m.Aid, info); err != nil {
log.Error("dao.Broadcast(cid:%d,aid:%d,info:%s) error(%v)", m.Oid, m.Aid, info, err)
} else {
log.Info("broadcast(%s) succeed", info)
}
}
}
func (s *Service) localcacheproc() {
for {
s.loadLocalcache(s.conf.Localcache.Oids)
time.Sleep(time.Duration(s.conf.Localcache.Expire))
}
}
func (s *Service) oplogproc() {
for opLog := range s.opsLogCh {
if len(opLog.Subject) == 0 || len(opLog.CurrentVal) == 0 || opLog.Source <= 0 ||
opLog.Operator <= 0 || opLog.OperatorType <= 0 {
log.Warn("oplogproc() it is an illegal log, warn(%v, %v, %v)", opLog.Subject, opLog.Subject, opLog.CurrentVal)
continue
} else {
for _, dmid := range opLog.DMIds {
if dmid > 0 {
s.dmOperationLogSvc.Info(opLog.Subject, strconv.FormatInt(opLog.Oid, 10), strconv.Itoa(opLog.Type),
strconv.FormatInt(dmid, 10), opLog.Source.String(), opLog.OriginVal,
opLog.CurrentVal, strconv.FormatInt(opLog.Operator, 10), opLog.OperatorType.String(),
opLog.OperationTime, opLog.Remark)
} else {
log.Warn("oplogproc() it is an illegal log, for dmid value, warn(%d, %+v)", dmid, opLog)
}
}
}
}
}
// OpLog put a new infoc format operation log into the channel
func (s *Service) OpLog(c context.Context, cid, operator, OperationTime int64, typ int, dmids []int64, subject, originVal, currentVal, remark string, source oplog.Source, operatorType oplog.OperatorType) (err error) {
infoLog := new(oplog.Infoc)
infoLog.Oid = cid
infoLog.Type = typ
infoLog.DMIds = dmids
infoLog.Subject = subject
infoLog.OriginVal = originVal
infoLog.CurrentVal = currentVal
infoLog.OperationTime = strconv.FormatInt(OperationTime, 10)
infoLog.Source = source
infoLog.OperatorType = operatorType
infoLog.Operator = operator
infoLog.Remark = remark
select {
case s.opsLogCh <- infoLog:
default:
err = fmt.Errorf("opsLogCh full")
log.Error("opsLogCh full (%v)", infoLog)
}
return
}
func (s *Service) monitorproc() {
for {
time.Sleep(3 * time.Second)
s.oidLock.Lock()
oidMap := s.moniOidMap
s.moniOidMap = make(map[int64]struct{})
s.oidLock.Unlock()
for oid := range oidMap {
sub, err := s.dao.Subject(context.TODO(), model.SubTypeVideo, oid)
if err != nil || sub == nil {
continue
}
if err := s.updateMonitorCnt(context.TODO(), sub); err != nil {
log.Error("s.updateMonitorCnt(%+v) error(%v)", sub, err)
}
}
}
}

View File

@@ -0,0 +1,29 @@
package service
import (
"flag"
"os"
"path/filepath"
"testing"
"go-common/app/interface/main/dm2/conf"
)
var (
svr *Service
)
func TestMain(m *testing.M) {
var (
err error
)
dir, _ := filepath.Abs("../cmd/dm2-test.toml")
if err = flag.Set("conf", dir); err != nil {
panic(err)
}
if err = conf.Init(); err != nil {
panic(err)
}
svr = New(conf.Conf)
os.Exit(m.Run())
}

View File

@@ -0,0 +1,118 @@
package service
import (
"context"
"encoding/json"
"go-common/app/interface/main/dm2/model"
"go-common/library/ecode"
"go-common/library/log"
)
func (s *Service) subject(c context.Context, tp int32, oid int64) (sub *model.Subject, err error) {
var (
cache = true
bs []byte
ok bool
)
if bs, ok = s.localCache[keySubject(tp, oid)]; ok {
sub = &model.Subject{}
if err = json.Unmarshal(bs, &sub); err == nil {
return
}
}
if sub, err = s.dao.SubjectCache(c, tp, oid); err != nil {
err = nil
cache = false
}
if sub == nil {
if sub, err = s.dao.Subject(c, tp, oid); err != nil {
return
}
if sub == nil {
sub = &model.Subject{
Type: tp,
Oid: oid,
}
}
if cache {
s.cache.Do(c, func(ctx context.Context) {
s.dao.AddSubjectCache(ctx, sub)
})
}
}
if sub.ID == 0 {
err = ecode.NothingFound
return
}
return
}
func (s *Service) subjects(c context.Context, tp int32, oids []int64) (res map[int64]*model.Subject, err error) {
var (
cache = true
missed []int64
missedCache map[int64]*model.Subject
hitedCache map[int64]*model.Subject
)
res = make(map[int64]*model.Subject, len(oids))
if hitedCache, missed, err = s.dao.SubjectsCache(c, tp, oids); err != nil {
cache = false
}
if len(hitedCache) == 0 {
missed = oids
}
if len(missed) > 0 {
if missedCache, err = s.dao.Subjects(c, tp, missed); err != nil {
return
}
for _, oid := range missed {
sub, ok := missedCache[oid]
if ok {
res[sub.Oid] = sub
} else {
sub = &model.Subject{
Type: tp,
Oid: oid,
}
}
if cache {
s.cache.Do(c, func(ctx context.Context) {
s.dao.AddSubjectCache(ctx, sub)
})
}
}
}
for _, hit := range hitedCache {
if hit.ID > 0 {
res[hit.Oid] = hit
}
}
return
}
// SubjectInfos get dm subject info by oids.
func (s *Service) SubjectInfos(c context.Context, tp int32, plat int8, oids []int64) (res map[int64]*model.SubjectInfo, err error) {
subs, err := s.subjects(c, tp, oids)
if err != nil {
log.Error("s.subjects(%v) error(%v)", oids, err)
return
}
res = make(map[int64]*model.SubjectInfo, len(oids))
for _, sub := range subs {
subInfo := new(model.SubjectInfo)
if sub.Count > sub.Maxlimit {
subInfo.Count = sub.ACount
} else {
subInfo.Count = sub.Count
}
if s.isRealname(c, sub.Pid, sub.Oid) {
subInfo.Realname = true
}
if sub.State == model.SubStateClosed {
subInfo.Closed = true
}
res[sub.Oid] = subInfo
}
return
}

View File

@@ -0,0 +1,42 @@
package service
import (
"context"
"testing"
"go-common/app/interface/main/dm2/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestSubjectInfos(t *testing.T) {
var (
c = context.TODO()
tp int32 = 1
oids = []int64{1221, 1231, 2386052}
)
Convey("get dm subject info", t, func() {
res, err := svr.SubjectInfos(c, tp, model.MaskPlatMbl, oids)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
for _, hit := range res {
t.Logf(":%+v", hit)
}
})
}
func TestSubjects(t *testing.T) {
var (
c = context.TODO()
tp int32 = 1
oids = []int64{1221, 1231, 49859595}
)
Convey("get dm subject info", t, func() {
res, err := svr.subjects(c, tp, oids)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
for _, hit := range res {
t.Logf(":%+v", hit)
}
})
}

View File

@@ -0,0 +1,300 @@
package service
import (
"context"
"time"
"go-common/app/interface/main/dm2/model"
account "go-common/app/service/main/account/api"
"go-common/app/service/main/archive/api"
archive "go-common/app/service/main/archive/model/archive"
filterCli "go-common/app/service/main/filter/api/grpc/v1"
"go-common/library/ecode"
"go-common/library/log"
)
const (
_filterArea = "subtitle"
)
// SubtitleFilter .
func (s *Service) SubtitleFilter(c context.Context, words string) (hit []string, err error) {
var (
reply *filterCli.HitReply
)
if len(words) > model.SubtitleContentSizeLimit {
err = ecode.SubtitleSizeLimit
return
}
if reply, err = s.filterRPC.Hit(c, &filterCli.HitReq{
Area: _filterArea,
Msg: words,
}); err != nil {
log.Error("SubtitleFilter(params:%+v),error(%v)", words, err)
return
}
hit = reply.Hits
return
}
// SubtitlePermission .
func (s *Service) SubtitlePermission(c context.Context, aid int64, oid int64, tp int32, mid int64) (err error) {
var (
subject *model.Subject
)
if subject, err = s.subject(c, tp, oid); err != nil {
log.Error("params(tp:%v,oid:%v).error(%v)", tp, oid, err)
return
}
if s.checkAidOid(c, aid, oid); err != nil {
return
}
if err = s.checkSubtitlePermission(c, aid, oid, tp, mid, subject); err != nil {
return
}
return
}
// DelSubtitle .
func (s *Service) DelSubtitle(c context.Context, oid int64, subtitleID int64, mid int64) (err error) {
var (
subtitle *model.Subtitle
)
if subtitle, err = s.getSubtitle(c, oid, subtitleID); err != nil {
log.Error("params(oid:%v, subtitleID:%v) error(%v)", oid, subtitleID, err)
return
}
if subtitle == nil {
err = ecode.NothingFound
return
}
if subtitle.Mid != mid {
err = ecode.SubtitlePermissionDenied
return
}
if subtitle.Status != model.SubtitleStatusDraft &&
subtitle.Status != model.SubtitleStatusToAudit &&
subtitle.Status != model.SubtitleStatusAuditBack &&
subtitle.Status != model.SubtitleStatusManagerBack {
err = ecode.SubtitleDelUnExist
return
}
subtitle.Status = model.SubtitleStatusRemove
subtitle.PubTime = time.Now().Unix()
if err = s.updateSubtitle(c, subtitle); err != nil {
return
}
s.subtitleReportDelete(c, oid, subtitleID)
return
}
// addSubtitle new a subtitle draft
func (s *Service) addSubtitle(c context.Context, draft *model.Subtitle) (insertID int64, err error) {
if insertID, err = s.dao.AddSubtitle(c, draft); err != nil {
log.Error("params(draft:%+v).error(%v)", draft, err)
return
}
s.dao.DelSubtitleDraftCache(context.Background(), draft.Oid, draft.Type, draft.Mid, draft.Lan)
s.dao.DelSubtitleCache(context.Background(), draft.Oid, draft.ID)
return
}
// updateSubtitle update an exist subtitle
func (s *Service) updateSubtitle(c context.Context, subtitle *model.Subtitle) (err error) {
if err = s.dao.UpdateSubtitle(c, subtitle); err != nil {
log.Error("params(draft:%+v).error(%v)", subtitle, err)
return
}
s.dao.DelSubtitleDraftCache(context.Background(), subtitle.Oid, subtitle.Type, subtitle.Mid, subtitle.Lan)
s.dao.DelSubtitleCache(context.Background(), subtitle.Oid, subtitle.ID)
return
}
// SubtitleSign .
func (s *Service) SubtitleSign(c context.Context, oid int64, tp int32, mid int64, subtitleID int64, isSign bool) (err error) {
var (
subtitle *model.Subtitle
)
if subtitle, err = s.getSubtitle(c, oid, subtitleID); err != nil {
log.Error("params(oid:%v,subtitleID:%v).error(%v)", oid, subtitleID, err)
return
}
if subtitle == nil {
err = ecode.NothingFound
return
}
if mid != subtitle.Mid {
err = ecode.SubtitlePermissionDenied
return
}
if subtitle.Status != model.SubtitleStatusDraft &&
subtitle.Status != model.SubtitleStatusToAudit &&
subtitle.Status != model.SubtitleStatusAuditBack &&
subtitle.Status != model.SubtitleStatusPublish &&
subtitle.Status != model.SubtitleStatusCheckToAudit &&
subtitle.Status != model.SubtitleStatusCheckPublish &&
subtitle.Status != model.SubtitleStatusManagerBack {
err = ecode.SubtitlePermissionDenied
return
}
subtitle.IsSign = isSign
if err = s.dao.UpdateSubtitle(c, subtitle); err != nil {
log.Error("params(%+v).error(%v)", subtitle, err)
return
}
if err = s.dao.DelSubtitleCache(c, oid, subtitleID); err != nil {
log.Error("DelSubtitleCache.params(oid:%v,subtitleID:%v).error(%v)", oid, subtitleID, err)
return
}
if err = s.dao.DelVideoSubtitleCache(c, oid, tp); err != nil {
log.Error("DelVideoSubtitleCache.params(oid:%v,tp:%v).error(%v)", oid, tp, err)
return
}
return
}
// SubtitleLock .
func (s *Service) SubtitleLock(c context.Context, oid int64, tp int32, mid int64, subtitleID int64, isLock bool) (err error) {
var (
subject *model.Subject
subtitle *model.Subtitle
)
if subtitle, err = s.getSubtitle(c, oid, subtitleID); err != nil {
log.Error("params(oid:%v,subtitleID:%v).error(%v)", oid, subtitleID, err)
return
}
if subtitle == nil {
err = ecode.NothingFound
return
}
if subject, err = s.subject(c, tp, oid); err != nil {
log.Error("params(oid:%v,tp:%v).error(%v)", oid, tp, err)
return
}
if mid != subject.Mid {
err = ecode.SubtitlePermissionDenied
return
}
if subtitle.Status != model.SubtitleStatusPublish &&
subtitle.Status != model.SubtitleStatusCheckPublish {
err = ecode.SubtitleNotPublish
return
}
subtitle.IsLock = isLock
if err = s.dao.UpdateSubtitle(c, subtitle); err != nil {
log.Error("params(%+v).error(%v)", subtitle, err)
return
}
if err = s.dao.DelSubtitleCache(c, oid, subtitleID); err != nil {
log.Error("DelSubtitleCache.params(oid:%v,subtitleID:%v).error(%v)", oid, subtitleID, err)
return
}
if err = s.dao.DelVideoSubtitleCache(c, oid, tp); err != nil {
log.Error("DelVideoSubtitleCache.params(oid:%v,tp:%v).error(%v)", oid, tp, err)
return
}
return
}
// ArchiveName .
func (s *Service) ArchiveName(c context.Context, aid int64) (arcvhiveName string, err error) {
var (
res *api.Arc
)
if res, err = s.arcRPC.Archive3(c, &archive.ArgAid2{
Aid: aid,
}); err != nil {
log.Error("params(aid:%v).error(%v)", aid, err)
return
}
arcvhiveName = res.Title
return
}
// SubtitleShow .
func (s *Service) SubtitleShow(c context.Context, oid int64, subtitleID int64, mid int64) (subtitleShow *model.SubtitleShow, err error) {
var (
subtitle *model.Subtitle
canShow bool
res *api.Arc
infoReply *account.InfoReply
showStatus model.SubtitleStatus
)
if subtitle, err = s.getSubtitle(c, oid, subtitleID); err != nil {
log.Error("params(oid:%v,subtitleID:%v).error(%v)", oid, subtitleID, err)
return
}
if subtitle == nil {
err = ecode.NothingFound
return
}
showStatus = subtitle.Status
// 发布的状态都可见
// 非发布的状态本人可见
// 审核状态 up 主可见
switch subtitle.Status {
case model.SubtitleStatusPublish:
canShow = true
case model.SubtitleStatusToAudit,
model.SubtitleStatusCheckPublish:
if subtitle.UpMid == mid || subtitle.Mid == mid {
canShow = true
}
case model.SubtitleStatusDraft,
model.SubtitleStatusAuditBack,
model.SubtitleStatusCheckToAudit:
if subtitle.Mid == mid {
canShow = true
}
case model.SubtitleStatusManagerBack:
if subtitle.Mid == mid {
canShow = true
}
showStatus = model.SubtitleStatusAuditBack
default:
err = ecode.SubtitlePermissionDenied
return
}
if !canShow {
err = ecode.SubtitlePermissionDenied
return
}
lan, lanDoc := s.subtitleLans.GetByID(int64(subtitle.Lan))
subtitleShow = &model.SubtitleShow{
ID: subtitle.ID,
Oid: subtitle.Oid,
Type: subtitle.Type,
Aid: subtitle.Aid,
Lan: lan,
LanDoc: lanDoc,
Mid: subtitle.Mid,
IsSign: subtitle.IsSign,
IsLock: subtitle.IsLock,
Status: showStatus,
SubtitleURL: subtitle.SubtitleURL,
RejectComment: subtitle.RejectComment,
}
if subtitle.UpMid == mid {
subtitleShow.UpperStatus = model.UpperStatusUpper
}
if subtitle.Mid == mid {
subtitleShow.AuthorStatus = model.AuthorStatusAuthor
}
if res, err = s.arcRPC.Archive3(c, &archive.ArgAid2{
Aid: subtitle.Aid,
}); err != nil {
log.Error("params(aid:%v).error(%v)", subtitle.Aid, err)
err = nil
} else {
subtitleShow.ArchiveName = res.Title
}
if infoReply, err = s.accountRPC.Info3(c, &account.MidReq{
Mid: subtitle.AuthorID,
}); err != nil {
log.Error("params(mid:%v).error(%v)", subtitle.Mid, err)
err = nil
} else {
subtitleShow.Author = infoReply.GetInfo().GetName()
}
return
}

View File

@@ -0,0 +1,177 @@
package service
import (
"context"
"time"
"go-common/app/interface/main/dm2/model"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
)
// AuditSubtitle audit subtitle by uper pr assitant
func (s *Service) AuditSubtitle(c context.Context, oid int64, subtitleID int64, mid int64, pass bool, rejectComment string) (err error) {
var (
draft *model.Subtitle
subject *model.Subject
)
if draft, err = s.getSubtitle(c, oid, subtitleID); err != nil {
log.Error("s.getSubtitle(oid:%v,subtitleID:%v),error(%v)", oid, subtitleID, err)
return
}
if draft == nil {
err = ecode.NothingFound
return
}
// up主协管有权限
if subject, err = s.subject(c, draft.Type, draft.Oid); err != nil {
return
}
// 非up主无权限
if subject.Mid != mid {
err = ecode.SubtitlePermissionDenied
return
}
if draft.Status != model.SubtitleStatusToAudit && draft.Status != model.SubtitleStatusPublish {
err = ecode.SubtitleUnValid
return
}
draft.RejectComment = rejectComment
if !pass {
if draft.Status == model.SubtitleStatusPublish {
if err = s.backPubSubtitle(c, draft); err != nil {
return
}
return
}
if err = s.auditReject(c, draft); err != nil {
log.Error("params(draft:%+v).error(%v)", draft, err)
return
}
} else {
if err = s.auditPass(c, draft); err != nil {
log.Error("params(draft:%+v).error(%v)", draft, err)
return
}
}
return
}
// auditReject subtitle submit
func (s *Service) auditReject(c context.Context, subtitle *model.Subtitle) (err error) {
subtitle.Status = model.SubtitleStatusAuditBack
subtitle.PubTime = time.Now().Unix()
if err = s.dao.UpdateSubtitle(c, subtitle); err != nil {
log.Error("params(%+v).error(%v)", subtitle, err)
return
}
s.dao.DelSubtitleDraftCache(context.Background(), subtitle.Oid, subtitle.Type, subtitle.Mid, subtitle.Lan)
s.dao.DelSubtitleCache(context.Background(), subtitle.Oid, subtitle.ID)
return
}
func (s *Service) auditPass(c context.Context, subtitle *model.Subtitle) (err error) {
var (
tx *sql.Tx
subtitlePub *model.SubtitlePub
)
defer func() {
if err != nil {
tx.Rollback()
log.Error("params(subtitle:%+v).err(%v)", subtitle, err)
return
}
if err = tx.Commit(); err != nil {
log.Error("params(subtitle:%+v).err(%v)", subtitle, err)
return
}
}()
subtitle.Status = model.SubtitleStatusPublish
subtitle.PubTime = time.Now().Unix()
if tx, err = s.dao.BeginBiliDMTrans(c); err != nil {
log.Error("error(%v)", err)
return
}
if err = s.dao.TxUpdateSubtitle(tx, subtitle); err != nil {
log.Error("params(%+v).error(%v)", subtitle, err)
return
}
subtitlePub = &model.SubtitlePub{
Oid: subtitle.Oid,
Type: subtitle.Type,
Lan: subtitle.Lan,
SubtitleID: subtitle.ID,
}
if err = s.dao.TxAddSubtitlePub(tx, subtitlePub); err != nil {
log.Error("params(%+v).error(%v)", subtitlePub, err)
return
}
if err = s.dao.DelSubtitleDraftCache(c, subtitle.Oid, subtitle.Type, subtitle.Mid, subtitle.Lan); err != nil {
log.Error("DelSubtitleDraftCache.params(subtitle:%+v).err(%v)", subtitle, err)
return
}
if err = s.dao.DelSubtitleCache(c, subtitle.Oid, subtitle.ID); err != nil {
log.Error("DelSubtitleCache.params(subtitle:%+v).err(%v)", subtitle, err)
return
}
if err = s.dao.DelVideoSubtitleCache(c, subtitle.Oid, subtitle.Type); err != nil {
log.Error("DelVideoSubtitleCache.params(subtitle:%+v).err(%v)", subtitle, err)
return
}
return
}
func (s *Service) backPubSubtitle(c context.Context, subtitle *model.Subtitle) (err error) {
var (
tx *sql.Tx
subtitleNew *model.Subtitle
subtitlePub *model.SubtitlePub
)
defer func() {
if err != nil {
tx.Rollback()
return
}
if err = tx.Commit(); err != nil {
return
}
}()
subtitle.Status = model.SubtitleStatusAuditBack
subtitle.PubTime = time.Now().Unix()
if tx, err = s.dao.BeginBiliDMTrans(c); err != nil {
log.Error("error(%v)", err)
return
}
if err = s.dao.TxUpdateSubtitle(tx, subtitle); err != nil {
log.Error("params(%+v) error(%v)", subtitle, err)
return
}
if subtitleNew, err = s.dao.TxGetSubtitleOne(tx, subtitle.Oid, subtitle.Type, subtitle.Lan); err != nil {
log.Error("params(%+v) error(%v)", subtitle, err)
return
}
subtitlePub = &model.SubtitlePub{
Oid: subtitle.Oid,
Type: subtitle.Type,
Lan: subtitle.Lan,
}
if subtitleNew == nil {
subtitlePub.IsDelete = true
} else {
subtitlePub.SubtitleID = subtitleNew.ID
}
if err = s.dao.TxAddSubtitlePub(tx, subtitlePub); err != nil {
log.Error("params(%+v) error(%v)", subtitlePub, err)
return
}
if err = s.dao.DelSubtitleCache(context.Background(), subtitle.Oid, subtitle.ID); err != nil {
log.Error("params(oid:%v,subtitleID:%v) error(%v)", subtitle.Oid, subtitle.ID, err)
return
}
if err = s.dao.DelVideoSubtitleCache(context.Background(), subtitle.Oid, subtitle.Type); err != nil {
log.Error("params(oid:%v,subtitleID:%v) error(%v)", subtitle.Oid, subtitle.ID, err)
return
}
return
}

View File

@@ -0,0 +1,210 @@
package service
import (
"context"
"encoding/json"
"go-common/app/interface/main/dm2/model"
account "go-common/app/service/main/account/api"
archive "go-common/app/service/main/archive/model/archive"
memberMdl "go-common/app/service/main/member/model/block"
"go-common/library/ecode"
"go-common/library/log"
)
const (
_subtitleMaxLength = 50 * 10000
)
func (s *Service) checkAidOid(c context.Context, aid, oid int64) (err error) {
if _, err = s.arcRPC.Video3(c, &archive.ArgVideo2{
Aid: aid,
Cid: oid,
}); err != nil {
log.Error("Video3(aid:%v,oid:%v),error(%v)", aid, oid, err)
err = ecode.RequestErr
return
}
return
}
func (s *Service) checkSubtitleLan(c context.Context, lan string) (err error) {
if s.subtitleLans.GetByLan(lan) <= 0 {
err = ecode.SubtitleIllegalLanguage
return
}
return
}
func (s *Service) checkSubtitlePermission(c context.Context, aid, oid int64, tp int32, mid int64, subject *model.Subject) (err error) {
if err = s.checkAudienceDraftAdd(c, aid, oid, tp, mid, subject); err != nil {
log.Error("params(oid:%v, tp:%v, mid:%v,subject:%+v).err(%v)", oid, tp, mid, subject, err)
return
}
return
}
func (s *Service) checkSubtitleData(c context.Context, aid, oid int64, data []byte) (detectErrs []*model.SubtitleDetectError, err error) {
var (
subtitleBody *model.SubtitleBody
duration int64
)
if len(data) > _subtitleMaxLength {
err = ecode.SubtitleSizeLimit
return
}
if err = json.Unmarshal(data, &subtitleBody); err != nil {
err = ecode.SubtitleUnValid
return
}
if duration, err = s.videoDuration(c, aid, oid); err != nil {
return
}
if duration <= 0 {
err = ecode.SubtitleTimeUnValid
return
}
if detectErrs, err = subtitleBody.CheckItem(duration); err != nil {
return
}
return
}
func (s *Service) checkSubtitleLocked(c context.Context, submit bool, oid int64, tp int32, lan string, mid int64) (err error) {
var (
lockSubtitle *model.Subtitle
)
if !submit {
return
}
if lockSubtitle, err = s.isSubtitleLanLock(c, oid, tp, lan); err != nil {
log.Error("params(oid:%v, tp:%v) error(%v)", oid, tp, err)
return
}
if lockSubtitle != nil && lockSubtitle.IsLock && lockSubtitle.UpMid != mid && lockSubtitle.Mid != mid {
err = ecode.SubtileLanLocked
return
}
return
}
func (s *Service) checkSubtitleAuthor(c context.Context, oid, subtitleID int64, lan string, mid int64) (authorID int64, err error) {
var (
originSubtitle *model.Subtitle
originLan int64
)
if subtitleID <= 0 {
authorID = mid
return
}
if originSubtitle, err = s.getSubtitle(c, oid, subtitleID); err != nil {
return
}
if originSubtitle == nil {
err = ecode.NothingFound
return
}
if originSubtitle.Status != model.SubtitleStatusAuditBack &&
originSubtitle.Status != model.SubtitleStatusPublish &&
originSubtitle.Status != model.SubtitleStatusCheckPublish &&
originSubtitle.Status != model.SubtitleStatusManagerBack {
err = ecode.SubtitleOriginUnValid
return
}
if originLan = s.subtitleLans.GetByLan(lan); originLan <= 0 || originLan != int64(originSubtitle.Lan) {
err = ecode.SubtitleIllegalLanguage
return
}
authorID = originSubtitle.AuthorID
return
}
func (s *Service) checkAudienceDraftAdd(c context.Context, aid, oid int64, tp int32, mid int64, subject *model.Subject) (err error) {
var (
profileReply *account.ProfileReply
blackReply *account.BlacksReply
blockInfo *memberMdl.RPCResInfo
resDm []*model.UpFilter
allow bool
closed bool
subtitleSubject *model.SubtitleSubject
)
if subtitleSubject, err = s.subtitleSubject(c, aid); err != nil {
log.Error("subtitleSubject(aid:%v) error(%v)", aid, err)
err = nil
}
if subtitleSubject != nil {
allow = subtitleSubject.Allow
closed = subtitleSubject.AttrVal(model.AttrSubtitleClose) == model.AttrYes
}
if closed {
err = ecode.SubtitleDenied
return
}
if subject.Mid == mid {
return
}
if !allow {
err = ecode.SubtitleDenied
return
}
// 视频观众可以投稿
// 账号绑定手机号
if profileReply, err = s.accountRPC.Profile3(c, &account.MidReq{
Mid: mid,
}); err != nil {
log.Error("accRPC.UserInfo(%v) error(%v)", mid, err)
return
}
if profileReply.GetProfile().GetIdentification() == 0 && profileReply.GetProfile().GetTelStatus() == 0 {
err = ecode.UserCheckNoPhone
return
}
if profileReply.GetProfile().GetIdentification() == 0 && profileReply.GetProfile().GetTelStatus() == 2 {
err = ecode.UserCheckInvalidPhone
return
}
if profileReply.GetProfile().GetTelStatus() == 0 {
err = ecode.UserCheckInvalidPhone
return
}
// 用户等级大于2
if profileReply.GetProfile().GetLevel() < 2 {
err = ecode.UserLevelLow
return
}
// 账号被拉黑
if blackReply, err = s.accountRPC.Blacks3(c, &account.MidReq{
Mid: subject.Mid,
}); err != nil {
log.Error("params(arg:%+v).err(%v)", subject.Mid, err)
return
}
if _, ok := blackReply.GetBlackList()[mid]; ok {
err = ecode.SubtitleUserBalcked
return
}
if resDm, err = s.UpFilters(c, subject.Mid); err != nil {
log.Error("params(mid:%+v).err(%v)", subject.Mid, err)
return
}
hash := model.Hash(mid, 0)
for _, uf := range resDm {
if uf.Filter == hash {
err = ecode.SubtitleUserBalcked
return
}
}
// 账号被封禁
if blockInfo, err = s.memberRPC.BlockInfo(c, &memberMdl.RPCArgInfo{
MID: mid,
}); err != nil {
log.Error("params(arg:%+v).err(%v)", mid, err)
return
}
if blockInfo.BlockStatus != memberMdl.BlockStatusFalse {
err = ecode.UserDisabled
return
}
return
}

View File

@@ -0,0 +1,129 @@
package service
import (
"context"
"go-common/app/interface/main/dm2/model"
"go-common/library/log"
)
// getSubtitlDraft get a subtitle
// cache throught
func (s *Service) getSubtitlDraft(c context.Context, oid int64, tp int32, mid int64, lanCode uint8) (draft *model.Subtitle, err error) {
var (
cacheErr bool
)
if draft, err = s.dao.SubtitleDraftCache(c, oid, tp, mid, lanCode); err != nil {
cacheErr = true
err = nil
}
if draft != nil {
if draft.ID <= 0 {
draft = nil
err = nil
}
return
}
if draft, err = s.dao.GetSubtitleDraft(c, oid, tp, mid, lanCode); err != nil {
log.Error("params(oid:%v,tp:%v,mid:%v,lanCode:%v).error(%v)", oid, tp, mid, lanCode, err)
return
}
if draft == nil {
draft = &model.Subtitle{
Oid: oid,
Type: tp,
Mid: mid,
Lan: lanCode,
}
}
if !cacheErr {
temp := draft
s.cache.Do(c, func(ctx context.Context) {
s.dao.SetSubtitleDraftCache(ctx, temp)
})
}
if draft.ID <= 0 {
draft = nil
err = nil
}
return
}
// GetSubtitle get a subtitle
func (s *Service) getSubtitle(c context.Context, oid int64, subtitleID int64) (subtitle *model.Subtitle, err error) {
var (
cacheErr bool
)
if subtitle, err = s.dao.SubtitleCache(c, oid, subtitleID); err != nil {
cacheErr = true
err = nil
}
if subtitle != nil {
if subtitle.Empty {
subtitle = nil
err = nil
}
return
}
if subtitle, err = s.dao.GetSubtitle(c, oid, subtitleID); err != nil {
log.Error("params(oid:%v, subtitleID:%v).error(%v)", oid, subtitleID, err)
return
}
if subtitle == nil {
subtitle = &model.Subtitle{
Oid: oid,
ID: subtitleID,
Empty: true,
}
}
if !cacheErr {
temp := subtitle
s.cache.Do(c, func(ctx context.Context) {
s.dao.SetSubtitleCache(ctx, temp)
})
}
if subtitle.Empty {
subtitle = nil
err = nil
}
return
}
// getSubtitles 不保证顺序
func (s *Service) getSubtitles(c context.Context, oid int64, subtitleIds []int64) (subtitles map[int64]*model.Subtitle, err error) {
var (
hits map[int64]*model.Subtitle
missed []int64
cacheErr bool
missedSubtitles []*model.Subtitle
)
if hits, missed, err = s.dao.SubtitlesCache(c, oid, subtitleIds); err != nil {
cacheErr = true
err = nil
}
subtitles = make(map[int64]*model.Subtitle)
for _, subtitle := range hits {
if subtitle.Empty {
missed = append(missed, subtitle.ID)
continue
}
subtitles[subtitle.ID] = subtitle
}
if len(missed) > 0 {
if missedSubtitles, err = s.dao.GetSubtitles(c, oid, missed); err != nil {
log.Error("getSubtitles(oid:%v,subtitleIds:%v),error(%v)", oid, subtitleIds, err)
return
}
}
for _, subtitle := range missedSubtitles {
subtitles[subtitle.ID] = subtitle
}
if !cacheErr {
for _, subtitle := range missedSubtitles {
temp := subtitle
s.cache.Do(c, func(ctx context.Context) {
s.dao.SetSubtitleCache(ctx, temp)
})
}
}
return
}

View File

@@ -0,0 +1,167 @@
package service
import (
"context"
"sort"
"encoding/json"
"go-common/app/interface/main/dm2/model"
"go-common/library/log"
)
const (
_subtitleLanFileName = "subtitle_lan.json"
)
// SubtitleLanOp .
func (s *Service) SubtitleLanOp(c context.Context, code uint8, lan, docZh, docEn string, isDelete bool) (err error) {
var (
subtitleLans []*model.SubtitleLan
subtitleLan *model.SubtitleLan
bs []byte
)
subtitleLan = &model.SubtitleLan{
Code: int64(code),
Lan: lan,
DocZh: docZh,
DocEn: docEn,
IsDelete: isDelete,
}
if err = s.dao.SubtitleLanAdd(c, subtitleLan); err != nil {
log.Error("params(subtitleLan:%+v).error(%v)", subtitleLan, err)
return
}
if subtitleLans, err = s.dao.SubtitleLans(c); err != nil {
log.Error("SubtitleLans.error(%v)", err)
return
}
if bs, err = json.Marshal(subtitleLans); err != nil {
log.Error("json.Marshal.params(subtitleLan:%+v).error(%v)", subtitleLan, err)
return
}
// reload bfs
if _, err = s.dao.UploadBfs(c, _subtitleLanFileName, bs); err != nil {
log.Error("UploadBfs.params.error(%v)", err)
return
}
return
}
func (s *Service) isSubtitleLanLock(c context.Context, oid int64, tp int32, lan string) (subtitle *model.Subtitle, err error) {
var (
vss []*model.VideoSubtitle
subtitleID int64
)
if vss, err = s.getVideoSubtitles(c, oid, tp); err != nil {
log.Error("params(oid:%v, tp:%v) error(%v)", oid, tp, err)
return
}
for _, vs := range vss {
if vs.Lan == lan {
subtitleID = vs.ID
break
}
}
if subtitleID > 0 {
if subtitle, err = s.getSubtitle(c, oid, subtitleID); err != nil {
log.Error("params(oid:%v, subtitleID:%v) error(%v)", oid, subtitleID, err)
return
}
}
return
}
// SubtitleLans .
func (s *Service) SubtitleLans(c context.Context, oid int64, tp int32, mid int64) (lans []*model.Language, err error) {
var (
vss []*model.VideoSubtitle
res *model.SearchSubtitleResult
_searchStatus = []int64{int64(model.SubtitleStatusDraft), int64(model.SubtitleStatusToAudit), int64(model.SubtitleStatusAuditBack), int64(model.SubtitleStatusCheckToAudit)}
subtitleIds []int64
subtitles []*model.Subtitle
subtitlesM map[int64]*model.Subtitle
mapLans map[string]*model.Language
_maxPageSize = 200
subtitleMap map[string][]*model.Subtitle
lan *model.Language
ok bool
)
mapLans = make(map[string]*model.Language)
if vss, err = s.getVideoSubtitles(c, oid, tp); err != nil {
log.Error("params(oid:%v,tp:%v).error(%v)", oid, tp, err)
return
}
if res, err = s.dao.SearchSubtitles(c, 1, int32(_maxPageSize), mid, nil, 0, oid, tp, _searchStatus); err == nil {
if res != nil {
for _, result := range res.Results {
subtitleIds = append(subtitleIds, result.ID)
}
}
} else {
log.Error("SearchSubtitles.params(mid:%v,oid:%v),error(%v)", mid, oid, err)
err = nil
}
for _, vs := range vss {
mapLans[vs.Lan] = &model.Language{
Lan: vs.Lan,
LanDoc: vs.LanDoc,
Pub: &model.LanguagePub{
SubtitleID: vs.ID,
IsLock: vs.IsLock,
IsPub: true,
},
}
}
if len(subtitleIds) > 0 {
if subtitlesM, err = s.getSubtitles(c, oid, subtitleIds); err != nil {
log.Error("params(oid:%v,subtitleDraftIds:%v).error(%v)", oid, subtitleIds, err)
return
}
subtitleMap = make(map[string][]*model.Subtitle)
for _, subtitle := range subtitlesM {
tlan, tlanDoc := s.subtitleLans.GetByID(int64(subtitle.Lan))
if lan, ok = mapLans[tlan]; !ok {
lan = &model.Language{
Lan: tlan,
LanDoc: tlanDoc,
}
mapLans[tlan] = lan
}
switch subtitle.Status {
case model.SubtitleStatusDraft:
lan.Draft = &model.LanguageID{
SubtitleID: subtitle.ID,
}
case model.SubtitleStatusToAudit:
lan.Audit = &model.LanguageID{
SubtitleID: subtitle.ID,
}
case model.SubtitleStatusAuditBack:
subtitleMap[tlan] = append(subtitleMap[tlan], subtitle)
}
}
for _, subtitles = range subtitleMap {
if len(subtitles) > 0 {
sort.Slice(subtitles, func(i, j int) bool {
return subtitles[i].PubTime > subtitles[j].PubTime
})
tlan, tlanDoc := s.subtitleLans.GetByID(int64(subtitles[0].Lan))
if lan, ok = mapLans[tlan]; !ok {
lan = &model.Language{
Lan: tlan,
LanDoc: tlanDoc,
}
mapLans[tlan] = lan
}
lan.AuditBack = &model.LanguageID{
SubtitleID: subtitles[0].ID,
}
}
}
}
lans = make([]*model.Language, 0, len(mapLans))
for _, mapLan := range mapLans {
lans = append(lans, mapLan)
}
return
}

View File

@@ -0,0 +1,103 @@
package service
import (
"context"
"go-common/app/interface/main/dm2/model"
"go-common/app/service/main/archive/api"
archiveMdl "go-common/app/service/main/archive/model/archive"
figureMdl "go-common/app/service/main/figure/model"
"go-common/library/ecode"
"go-common/library/log"
)
const (
_workFlowSubtitleBid = 14
_workFlowSubtitleRid = 1
)
// SubtitleReportList .
func (s *Service) SubtitleReportList(c context.Context) (data []*model.WorkFlowTag, err error) {
var (
cacheErr bool
)
if data, err = s.dao.SubtitleWorlFlowTagCache(c, _workFlowSubtitleBid, _workFlowSubtitleRid); err != nil {
cacheErr = true
err = nil
}
if len(data) > 0 {
return
}
if data, err = s.dao.WorkFlowTagList(c, _workFlowSubtitleBid, _workFlowSubtitleRid); err != nil {
return
}
if !cacheErr {
temp := data
s.cache.Do(c, func(ctx context.Context) {
s.dao.SetSubtitleWorlFlowTagCache(ctx, _workFlowSubtitleBid, _workFlowSubtitleRid, temp)
})
}
return
}
// SubtitleReportAdd .
func (s *Service) SubtitleReportAdd(c context.Context, mid int64, param *model.SubtitleReportAddParam) (err error) {
var (
figureWithRank *figureMdl.FigureWithRank
subtitle *model.Subtitle
archiveInfo *api.Arc
score int32
)
if subtitle, err = s.getSubtitle(c, param.Oid, param.SubtitleID); err != nil {
return
}
if subtitle == nil {
err = ecode.NothingFound
return
}
if figureWithRank, err = s.figureRPC.UserFigure(c, &figureMdl.ArgUserFigure{
Mid: mid,
}); err == nil {
score = figureWithRank.Score
} else {
log.Error("UserFigure(mid:%v),error(%v)", mid, err)
}
if archiveInfo, err = s.arcRPC.Archive3(c, &archiveMdl.ArgAid2{
Aid: subtitle.Aid,
}); err != nil {
log.Error("s.arcRPC.Archive3(aid:%v),error(%v)", subtitle.Aid, err)
return
}
req := &model.WorkFlowAppealAddReq{
Business: _workFlowSubtitleBid,
Oid: param.Oid,
Aid: subtitle.Aid,
Rid: _workFlowSubtitleRid,
LanCode: int64(subtitle.Lan),
SubtitleID: param.SubtitleID,
Score: score,
Tid: param.Tid,
Mid: mid,
Description: param.MetaData,
BusinessTypeID: archiveInfo.TypeID,
BusinessTitle: param.Content,
BusinessMid: subtitle.Mid,
Extra: &model.WorkFlowAppealAddExtra{
SubtitleStatus: int64(subtitle.Status),
SubtitleURL: subtitle.SubtitleURL,
ArchiveName: archiveInfo.Title,
},
}
if err = s.dao.WorkFlowAppealAdd(c, req); err != nil {
log.Error("SubtitleReportAdd(req:%+v),error(%v)", req, err)
return
}
return
}
func (s *Service) subtitleReportDelete(c context.Context, oid, subtitleID int64) (err error) {
if err = s.dao.WorkFlowAppealDelete(c, _workFlowSubtitleBid, oid, subtitleID); err != nil {
return
}
return
}

View File

@@ -0,0 +1,148 @@
package service
import (
"context"
"crypto/sha1"
"encoding/hex"
"time"
"go-common/app/interface/main/dm2/model"
"go-common/library/ecode"
"go-common/library/log"
)
func (s *Service) genSubtitleID(c context.Context) (subtitleID int64, err error) {
subtitleID, err = s.seqRPC.ID(c, s.seqSubtitleArg)
if err != nil {
return
}
return
}
// SaveSubtitleDraft save subtitle
func (s *Service) SaveSubtitleDraft(c context.Context, aid, oid int64, tp int32, mid int64, lan string, submit, sign bool, originSubtitleID int64, data []byte) (detectErrs []*model.SubtitleDetectError, err error) {
var (
subject *model.Subject
draft *model.Subtitle
authorID int64
)
if subject, err = s.subject(c, tp, oid); err != nil {
log.Error("params(tp:%v, oid:%v).error(%v)", tp, oid, err)
return
}
if err = s.checkSubtitleLan(c, lan); err != nil {
return
}
if err = s.checkAidOid(c, aid, oid); err != nil {
return
}
if err = s.checkSubtitlePermission(c, aid, oid, tp, mid, subject); err != nil {
return
}
// TODO remove error
if detectErrs, err = s.checkSubtitleData(c, aid, oid, data); err != nil {
return
}
if len(detectErrs) > 0 {
return
}
if err = s.checkSubtitleLocked(c, submit, oid, tp, lan, mid); err != nil {
return
}
if authorID, err = s.checkSubtitleAuthor(c, oid, originSubtitleID, lan, mid); err != nil {
return
}
status := model.SubtitleStatusDraft
if submit {
status = model.SubtitleStatusCheckToAudit
if mid == subject.Mid {
status = model.SubtitleStatusCheckPublish
}
}
if draft, err = s.buildSubtitleDraft(c, oid, tp, mid, authorID, lan, data, status, sign); err != nil {
return
}
if err = s.addSubtitleDraft(c, draft); err != nil {
return
}
if status == model.SubtitleStatusCheckToAudit || status == model.SubtitleStatusCheckPublish {
s.dao.SendSubtitleCheck(c, draft.CheckSum, &model.SubtitleCheckMsg{
Oid: oid,
SubtitleID: draft.ID,
})
}
return
}
func (s *Service) addSubtitleDraft(c context.Context, draft *model.Subtitle) (err error) {
if draft.ID > 0 {
if err = s.updateSubtitle(c, draft); err != nil {
return
}
} else {
if draft.ID, err = s.genSubtitleID(c); err != nil {
return
}
if _, err = s.addSubtitle(c, draft); err != nil {
return
}
}
return
}
// buildSubtitleDraft when save draft or save to submit
func (s *Service) buildSubtitleDraft(c context.Context, oid int64, tp int32, mid, authorID int64, lan string, data []byte, status model.SubtitleStatus, sign bool) (draft *model.Subtitle, err error) {
var (
subtitleURL string
checkSum string
subject *model.Subject
lanCode int64
)
if lanCode = s.subtitleLans.GetByLan(lan); lanCode <= 0 {
err = ecode.SubtitleIllegalLanguage
return
}
if draft, err = s.getSubtitlDraft(c, oid, tp, mid, uint8(lanCode)); err != nil {
log.Error("params(oid:%v,tp:%v,mid:%v,lanCode:%v).error(%v)", oid, tp, mid, lanCode, err)
return
}
if draft == nil {
if subject, err = s.subject(c, tp, oid); err != nil {
log.Error("params(oid:%v,tp:%v).error(%v)", oid, tp, err)
return
}
draft = &model.Subtitle{
Oid: oid,
Type: tp,
Mid: mid,
Aid: subject.Pid,
Lan: uint8(lanCode),
AuthorID: mid,
UpMid: subject.Mid,
PubTime: 0,
IsSign: sign,
Status: model.SubtitleStatusDraft,
}
}
if draft.Status != model.SubtitleStatusDraft && draft.Status != model.SubtitleStatusToAudit && draft.Status != model.SubtitleStatusCheckToAudit {
err = ecode.SubtitlePermissionDenied
return
}
sha := sha1.Sum(data)
if checkSum = hex.EncodeToString(sha[:]); checkSum != draft.CheckSum {
if subtitleURL, err = s.dao.UploadBfs(c, "", data); err != nil {
log.Error("UploadBfs.error(%v)", err)
return
}
draft.SubtitleURL = subtitleURL
draft.CheckSum = checkSum
}
draft.Status = status
draft.IsSign = sign
if status == model.SubtitleStatusCheckPublish {
draft.PubTime = time.Now().Unix()
}
draft.AuthorID = authorID
draft.RejectComment = ""
return
}

View File

@@ -0,0 +1,314 @@
package service
import (
"context"
"fmt"
"sync"
"go-common/app/interface/main/dm2/model"
account "go-common/app/service/main/account/api"
"go-common/app/service/main/archive/api"
archive "go-common/app/service/main/archive/model/archive"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/sync/errgroup"
)
const (
_searchMaxSize = 100
)
// SearchAuthor .
func (s *Service) SearchAuthor(c context.Context, mid int64, status int32, page, size int32) (authorResult *model.SearchSubtitleAuthor, err error) {
var (
res *model.SearchSubtitleResponse
searchItems []*model.SearchSubtitleAuthorItem
formUpper = false
countSubtitle *model.CountSubtitleResult
)
if size > _searchMaxSize {
err = ecode.RequestErr
return
}
if res, err = s.searchSubtitle(c, 0, 0, 0, mid, nil, status, page, size, formUpper); err != nil {
log.Error("psearchSubtitle.params(mid:%v,status:%v,page:%v,size:%v),error(%v)", mid, status, page, size, err)
return
}
if res == nil || res.Page == nil {
log.Error("psearchSubtitle.params(mid:%v,status:%v,page:%v,size:%v),error(%v)", mid, status, page, size, err)
err = ecode.NothingFound
return
}
if countSubtitle, err = s.dao.CountSubtitles(c, mid, nil, 0, 0, 0); err != nil {
log.Error("CountSubtitles.params(mid:%v),error(%v)", mid, err)
return
}
for _, rs := range res.Subtitles {
searchItem := &model.SearchSubtitleAuthorItem{
ID: rs.ID,
Oid: rs.Oid,
Aid: rs.Aid,
Type: rs.Type,
ArchiveName: rs.ArchiveName,
ArchivePic: rs.ArchivePic,
VideoName: rs.VideoName,
Lan: rs.Lan,
LanDoc: rs.LanDoc,
Status: rs.Status,
IsSign: rs.IsSign,
IsLock: rs.IsLock,
Mtime: rs.Mtime,
}
if searchItem.Status == int32(model.SubtitleStatusManagerBack) {
searchItem.Status = int32(model.SubtitleStatusAuditBack)
}
if searchItem.Status == int32(model.SubtitleStatusAuditBack) {
searchItem.RejectComment = rs.RejectComment
}
searchItems = append(searchItems, searchItem)
}
authorResult = &model.SearchSubtitleAuthor{
Page: res.Page,
Subtitles: searchItems,
DraftCount: countSubtitle.Draft,
AuditCount: countSubtitle.ToAudit,
BackCount: countSubtitle.AuditBack,
PublishCount: countSubtitle.Publish,
}
authorResult.Total = authorResult.DraftCount + authorResult.AuditCount + authorResult.BackCount + authorResult.PublishCount
return
}
// SearchAssist .
func (s *Service) SearchAssist(c context.Context, aid, oid int64, tp int32, mid int64, status int32, page, size int32) (assistResult *model.SearchSubtitleAssit, err error) {
var (
res *model.SearchSubtitleResponse
upMids []int64
fromUpper = true
countSubtitle *model.CountSubtitleResult
)
if size > _searchMaxSize {
err = ecode.RequestErr
return
}
upMids = append(upMids, mid)
if res, err = s.searchSubtitle(c, aid, oid, tp, 0, upMids, status, page, size, fromUpper); err != nil {
return
}
if res == nil || res.Page == nil {
err = ecode.NothingFound
return
}
if countSubtitle, err = s.dao.CountSubtitles(c, 0, upMids, aid, oid, tp); err != nil {
log.Error("CountSubtitles.params(mid:%v),error(%v)", mid, err)
return
}
assistResult = &model.SearchSubtitleAssit{
Page: res.Page,
Subtitles: res.Subtitles,
AuditCount: countSubtitle.ToAudit,
PublishCount: countSubtitle.Publish,
}
assistResult.Total = assistResult.AuditCount + assistResult.PublishCount
return
}
func (s *Service) buildSearchStatus(c context.Context, status int32, fromUpper bool) (searchStatus []int64, err error) {
if fromUpper {
switch status {
case 0:
searchStatus = []int64{
int64(model.SubtitleStatusPublish),
int64(model.SubtitleStatusToAudit),
int64(model.SubtitleStatusCheckPublish),
}
case int32(model.SubtitleStatusPublish):
searchStatus = []int64{
int64(model.SubtitleStatusPublish),
int64(model.SubtitleStatusCheckPublish),
}
case int32(model.SubtitleStatusToAudit):
searchStatus = []int64{int64(status)}
default:
err = ecode.SubtitlePermissionDenied
return
}
} else {
switch status {
case int32(model.SubtitleStatusPublish):
searchStatus = []int64{
int64(model.SubtitleStatusPublish),
int64(model.SubtitleStatusCheckPublish),
}
case int32(model.SubtitleStatusAuditBack):
searchStatus = []int64{
int64(model.SubtitleStatusAuditBack),
int64(model.SubtitleStatusManagerBack),
}
case int32(model.SubtitleStatusDraft):
searchStatus = []int64{int64(status)}
case int32(model.SubtitleStatusToAudit):
searchStatus = []int64{
int64(model.SubtitleStatusCheckToAudit),
int64(model.SubtitleStatusToAudit),
}
case 0:
searchStatus = []int64{
int64(model.SubtitleStatusPublish),
int64(model.SubtitleStatusToAudit),
int64(model.SubtitleStatusDraft),
int64(model.SubtitleStatusAuditBack),
int64(model.SubtitleStatusCheckToAudit),
int64(model.SubtitleStatusCheckPublish),
int64(model.SubtitleStatusManagerBack),
}
default:
err = ecode.SubtitlePermissionDenied
return
}
}
return
}
func (s *Service) searchSubtitle(c context.Context, aid, oid int64, tp int32, mid int64, upMids []int64, status int32, page, size int32, fromUpper bool) (result *model.SearchSubtitleResponse, err error) {
var (
res *model.SearchSubtitleResult
dmidsMap map[int64][]int64
eg errgroup.Group
subtitleMap map[string]*model.Subtitle
subtitle *model.Subtitle
results []*model.SearchSubtitle
searchSubtitle *model.SearchSubtitle
mutex sync.Mutex
ok bool
profileReply *account.ProfileReply
archiveAids []int64
archiveMap map[int64]*api.Arc
searchStatus []int64
)
key := func(oid, id int64) string {
return fmt.Sprintf("%d:%d", oid, id)
}
if searchStatus, err = s.buildSearchStatus(c, status, fromUpper); err != nil {
return
}
if res, err = s.dao.SearchSubtitles(c, page, size, mid, upMids, aid, oid, tp, searchStatus); err != nil {
return
}
if res == nil || res.Page == nil {
return
}
result = &model.SearchSubtitleResponse{
Page: &model.SearchPage{
Num: res.Page.Num,
Size: res.Page.Size,
Total: res.Page.Total,
},
}
dmidsMap = make(map[int64][]int64)
subtitleMap = make(map[string]*model.Subtitle)
for _, rs := range res.Results {
dmidsMap[rs.Oid] = append(dmidsMap[rs.Oid], rs.ID)
}
for oid, ids := range dmidsMap {
tempOid := oid
tempIds := ids
eg.Go(func() (err error) {
var (
subtitles map[int64]*model.Subtitle
)
if subtitles, err = s.getSubtitles(c, tempOid, tempIds); err != nil {
log.Error("params(tempOid:%v, tempIds:%v) error(%v)", tempOid, tempIds, err)
return
}
mutex.Lock()
for _, subtitle := range subtitles {
subtitleMap[key(subtitle.Oid, subtitle.ID)] = subtitle
archiveAids = append(archiveAids, subtitle.Aid)
}
mutex.Unlock()
return
})
}
if err = eg.Wait(); err != nil {
log.Error("eg.Wait() error(%v)", err)
return
}
if archiveMap, err = s.arcRPC.Archives3(c, &archive.ArgAids2{
Aids: archiveAids,
}); err != nil {
log.Error("params(aids:%v) error(%v)", archiveAids, err)
return
}
results = make([]*model.SearchSubtitle, 0, len(subtitleMap))
for _, rs := range res.Results {
var (
archiveName string
archivePic string
archiveVideo *api.Page
videoName string
)
if subtitle, ok = subtitleMap[key(rs.Oid, rs.ID)]; !ok {
continue
}
if a, ok := archiveMap[subtitle.Aid]; ok {
archiveName = a.Title
archivePic = a.Pic
}
if archiveVideo, err = s.arcRPC.Video3(c, &archive.ArgVideo2{
Aid: subtitle.Aid,
Cid: subtitle.Oid,
}); err != nil {
log.Error("params(aid:%v,oid:%v) error(%v)", subtitle.Aid, subtitle.Oid, err)
err = nil
} else {
videoName = archiveVideo.Part
}
lan, lanDoc := s.subtitleLans.GetByID(int64(subtitle.Lan))
searchSubtitle = &model.SearchSubtitle{
ID: rs.ID,
Oid: rs.Oid,
Aid: subtitle.Aid,
Type: subtitle.Type,
ArchiveName: archiveName,
ArchivePic: archivePic,
VideoName: videoName,
Lan: lan,
LanDoc: lanDoc,
Status: int32(subtitle.Status),
IsSign: subtitle.IsSign,
IsLock: subtitle.IsLock,
RejectComment: subtitle.RejectComment,
Mtime: subtitle.Mtime,
}
switch subtitle.Status {
case model.SubtitleStatusCheckToAudit:
searchSubtitle.Status = int32(model.SubtitleStatusToAudit)
case model.SubtitleStatusCheckPublish:
searchSubtitle.Status = int32(model.SubtitleStatusPublish)
}
// up主搜索需要以下字段
if fromUpper {
var (
profileName string
authorPic string
)
if profileReply, err = s.accountRPC.Profile3(c, &account.MidReq{
Mid: subtitle.Mid,
}); err != nil {
log.Error("params(Mid:%v) error(%v)", subtitle.Mid, err)
err = nil
} else {
profileName = profileReply.GetProfile().GetName()
authorPic = profileReply.GetProfile().GetFace()
}
searchSubtitle.Author = profileName
searchSubtitle.AuthorPic = authorPic
searchSubtitle.AuthorID = subtitle.AuthorID
}
results = append(results, searchSubtitle)
}
result.Subtitles = results
return
}

View File

@@ -0,0 +1,94 @@
package service
import (
"context"
"go-common/app/interface/main/dm2/model"
"go-common/library/log"
)
// SubtitleSubject .
func (s *Service) SubtitleSubject(c context.Context, aid int64) (subtitleSubjectReply *model.SubtitleSubjectReply, err error) {
var (
subtitleSubject *model.SubtitleSubject
lan, lanDoc string
)
if subtitleSubject, err = s.subtitleSubject(c, aid); err != nil {
log.Error("params(aid:%v).err(%v)", aid, err)
err = nil
}
if subtitleSubject == nil {
subtitleSubject = &model.SubtitleSubject{
Allow: false,
}
}
lan, lanDoc = s.subtitleLans.GetByID(int64(subtitleSubject.Lan))
subtitleSubjectReply = &model.SubtitleSubjectReply{
AllowSubmit: subtitleSubject.Allow,
Lan: lan,
LanDoc: lanDoc,
}
return
}
func (s *Service) subtitleSubject(c context.Context, aid int64) (sSubject *model.SubtitleSubject, err error) {
var (
cacheErr bool
)
if sSubject, err = s.dao.SubtitleSubjectCache(c, aid); err != nil {
cacheErr = true
err = nil
}
if sSubject != nil {
if sSubject.Empty {
err = nil
sSubject = nil
return
}
return
}
if sSubject, err = s.dao.GetSubtitleSubject(c, aid); err != nil {
log.Error("params(aid:%v).err(%v)", aid, err)
return
}
if sSubject == nil {
sSubject = &model.SubtitleSubject{
Aid: aid,
Empty: true,
}
}
if !cacheErr {
temp := sSubject
s.cache.Do(c, func(ctx context.Context) {
s.dao.SetSubtitleSubjectCache(ctx, temp)
})
}
if sSubject.Empty {
err = nil
sSubject = nil
return
}
return
}
// SubtitleSubjectSubmit .
func (s *Service) SubtitleSubjectSubmit(c context.Context, aid int64, allow bool, lan string) (err error) {
var (
lanCode int64
subtitleSubject *model.SubtitleSubject
)
lanCode = s.subtitleLans.GetByLan(lan)
subtitleSubject = &model.SubtitleSubject{
Aid: aid,
Allow: allow,
Lan: uint8(lanCode),
}
if err = s.dao.AddSubtitleSubject(c, subtitleSubject); err != nil {
log.Error("params(subtitleSubject:%+v).err(%v)", subtitleSubject, err)
return
}
if err = s.dao.DelSubtitleSubjectCache(c, aid); err != nil {
return
}
return
}

View File

@@ -0,0 +1,151 @@
package service
import (
"context"
"encoding/json"
"fmt"
"testing"
"time"
"go-common/app/interface/main/dm2/model"
account "go-common/app/service/main/account/api"
rpcmodel "go-common/app/service/main/member/model/block"
. "github.com/smartystreets/goconvey/convey"
)
func TestSaveSubtitleDraft(t *testing.T) {
Convey("", t, func() {
var aid int64 = 10098493
var oid int64 = 401
var tp int32 = 1
mid := int64(88888929)
lan := "zh-CN"
body := &model.SubtitleBody{
FontColor: "#FFFFFF",
FontSize: 0.4,
BackgroundAlpha: 0.5,
BackgroundColor: "#9C27B0",
Stroke: "none",
}
items := make([]*model.SubtitleItem, 0, 10)
for i := 0; i < 10; i++ {
items = append(items, &model.SubtitleItem{
From: float64(i * 10),
To: float64(i*10 + 5),
Location: uint8(8),
Content: fmt.Sprintf("test_1133331seg_%d", i+1),
})
}
body.Bodys = items
bs, err := json.Marshal(&body)
if err != nil {
return
}
_, err = svr.SaveSubtitleDraft(context.Background(), aid, oid, tp, mid, lan, true, true, 0, bs)
time.Sleep(time.Second)
t.Logf("err:%v", err)
So(err, ShouldBeNil)
})
}
func TestDelSubtitle(t *testing.T) {
Convey("", t, func() {
var oid int64 = 101
var subtitleID = int64(6)
mid := int64(5)
err := svr.DelSubtitle(context.Background(), oid, subtitleID, mid)
So(err, ShouldBeNil)
})
}
func TestAuditSubtitle(t *testing.T) {
Convey("", t, func() {
var oid int64 = 101
mid := int64(5)
err := svr.AuditSubtitle(context.Background(), oid, 1, mid, true, "")
So(err, ShouldBeNil)
})
}
func TestSubtitleShow(t *testing.T) {
Convey("", t, func() {
var oid int64 = 101
var aid int64 = 10098493
subtitleID := int64(4)
start := time.Now()
res, err := svr.SubtitleShow(context.Background(), aid, oid, subtitleID)
t.Logf("costing:%v", time.Since(start))
So(err, ShouldBeNil)
t.Logf("%+v:", res)
})
}
func TestSubtitleLock(t *testing.T) {
Convey("", t, func() {
var oid int64 = 101
var tp int32 = 1
mid := int64(5)
subtitleID := int64(5)
start := time.Now()
err := svr.SubtitleLock(context.Background(), oid, tp, mid, subtitleID, true)
t.Logf("costing:%v", time.Since(start))
So(err, ShouldBeNil)
})
}
func TestSearchAuthor(t *testing.T) {
Convey("", t, func() {
var mid int64 = 27515615
var status int32
res, err := svr.SearchAuthor(context.Background(), mid, status, 1, 10)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
t.Logf("page:%+v", res)
t.Logf("err:%v", err)
// for _, rs := range res.Subtitles {
// t.Logf("rs:%+v", rs)
// }
})
}
func TestSearchAssist(t *testing.T) {
Convey("search assist", t, func() {
var mid int64 = 27515615
var status int32
res, err := svr.SearchAssist(context.Background(), 0, 0, 1, mid, status, 1, 50)
So(err, ShouldBeNil)
So(res, ShouldNotBeEmpty)
t.Logf("page:%+v", res)
t.Logf("err:%v", err)
for _, rs := range res.Subtitles {
t.Logf("rs:%+v", rs)
}
})
}
func TestBlack(t *testing.T) {
Convey("", t, func() {
res, err := svr.accountRPC.Blacks3(context.Background(), &account.MidReq{
Mid: 27515266,
})
t.Logf("err:%v", err)
t.Logf("blockINfo:%+v", res)
})
}
func TestBlockInfo(t *testing.T) {
Convey("blokc ", t, func() {
res, err := svr.memberRPC.BlockInfo(context.Background(), &rpcmodel.RPCArgInfo{
MID: 27515256,
})
t.Logf("err:%v", err)
t.Logf("blockINfo:%+v", res)
})
}

View File

@@ -0,0 +1,122 @@
package service
import (
"context"
"fmt"
"go-common/app/interface/main/dm2/model"
"go-common/library/log"
)
func keySubtitleSingle(oid int64, tp int32) string {
return fmt.Sprintf("subtitle_%d_%d", oid, tp)
}
// GetWebVideoSubtitle .
func (s *Service) GetWebVideoSubtitle(c context.Context, aid, oid int64, tp int32) (res *model.VideoSubtitles, err error) {
var (
subtitles []*model.VideoSubtitle
subtitleSubject *model.SubtitleSubject
allowSubmit bool
closed bool
lan, lanDoc string
)
if subtitleSubject, err = s.subtitleSubject(c, aid); err != nil {
log.Error("params(aid:%v).err(%v)", aid, err)
err = nil
}
if subtitleSubject != nil {
allowSubmit = subtitleSubject.Allow
closed = subtitleSubject.AttrVal(model.AttrSubtitleClose) == model.AttrYes
lan, lanDoc = s.subtitleLans.GetByID(int64(subtitleSubject.Lan))
}
res = &model.VideoSubtitles{
AllowSubmit: allowSubmit,
Lan: lan,
LanDoc: lanDoc,
}
if closed {
res.AllowSubmit = false
return
}
if subtitles, err = s.getVideoSubtitles(c, oid, tp); err != nil {
return
}
res.Subtitles = subtitles
return
}
// singleGetVideoSubtitle use singleflight, but not cache sub item
func (s *Service) singleGetVideoSubtitle(c context.Context, oid int64, tp int32) (res []*model.VideoSubtitle, err error) {
var (
v interface{}
subtitleIds []int64
subtitles map[int64]*model.Subtitle
)
v, err, _ = s.subtitleSingleGroup.Do(keySubtitleSingle(oid, tp), func() (reply interface{}, err error) {
if subtitleIds, err = s.dao.GetSubtitleIds(c, oid, tp); err != nil {
log.Error("params(oid:%v, tp:%v).err(%v)", oid, tp, err)
return
}
if len(subtitleIds) == 0 {
return
}
if subtitles, err = s.getSubtitles(c, oid, subtitleIds); err != nil {
log.Error("params(oid:%v, subtitleIds:%v).err(%v)", oid, subtitleIds, err)
return
}
result := make([]*model.VideoSubtitle, 0, len(subtitles))
for _, subtitle := range subtitles {
lan, lanDoc := s.subtitleLans.GetByID(int64(subtitle.Lan))
vs := &model.VideoSubtitle{
ID: subtitle.ID,
IsLock: subtitle.IsLock,
Lan: lan,
LanDoc: lanDoc,
SubtitleURL: subtitle.SubtitleURL,
}
if subtitle.IsSign {
vs.AuthorMid = subtitle.AuthorID
}
result = append(result, vs)
}
reply = result
return
})
if err != nil {
log.Error("params(oid:%v, tp:%v).err(%v)", oid, tp, err)
return
}
res, _ = v.([]*model.VideoSubtitle)
return
}
// getVideoSubtitles get from cache
func (s *Service) getVideoSubtitles(c context.Context, oid int64, tp int32) (subtitles []*model.VideoSubtitle, err error) {
var (
cacheErr bool
videoSubtitleCache *model.VideoSubtitleCache
)
if videoSubtitleCache, err = s.dao.VideoSubtitleCache(c, oid, tp); err != nil {
cacheErr = true
err = nil
}
if videoSubtitleCache != nil {
subtitles = videoSubtitleCache.VideoSubtitles
return
}
if subtitles, err = s.singleGetVideoSubtitle(c, oid, tp); err != nil {
log.Error("params(oid:%v,tp:%v).err(%v)", oid, tp, err)
return
}
videoSubtitleCache = &model.VideoSubtitleCache{
VideoSubtitles: subtitles,
}
if !cacheErr {
temp := videoSubtitleCache
s.cache.Do(c, func(ctx context.Context) {
s.dao.SetVideoSubtitleCache(ctx, oid, tp, temp)
})
}
return
}

View File

@@ -0,0 +1,91 @@
package service
import (
"context"
"go-common/app/interface/main/dm2/model"
account "go-common/app/service/main/account/api"
thumbUpApi "go-common/app/service/main/thumbup/api"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
)
// ThumbupDM like or cancel like a dm
func (s *Service) ThumbupDM(c context.Context, oid, dmid, mid int64, op int8) (err error) {
if !model.CheckThumbup(op) {
err = ecode.RequestErr
return
}
// check dm state
idxMap, _, err := s.dao.IndexsByid(c, model.SubTypeVideo, oid, []int64{dmid})
if err != nil {
log.Error("s.dao.IndexsByid(tp:%d,oid:%d,dmid:%d), error(%v)", 1, oid, dmid, err)
return
}
if len(idxMap) <= 0 || !model.IsDMVisible(idxMap[dmid].State) {
err = ecode.DMNotFound
return
}
reply, err := s.accountRPC.Profile3(c, &account.MidReq{
Mid: mid,
})
if err != nil {
log.Error("s.accountRPC.Profile3(arg:%+v), error(%v)", mid, err)
return
}
if reply.GetProfile().GetEmailStatus() == 0 && reply.GetProfile().GetTelStatus() == 0 {
err = ecode.UserCheckNoPhone
return
}
if reply.GetProfile().GetSilence() == 1 {
err = ecode.DMActSilence
return
}
sub, err := s.subject(c, model.SubTypeVideo, oid)
if err != nil {
return
}
arg2 := &thumbUpApi.LikeReq{
Mid: mid,
UpMid: sub.Mid,
Business: "danmu",
OriginID: oid,
MessageID: dmid,
Action: thumbUpApi.Action(op),
}
_, err = s.thumbupRPC.Like(c, arg2)
if err != nil {
log.Error("dmAct s.thumbupRPC.Like(arg:%+v), error(%v)", arg2, err)
return
}
return
}
// ThumbupList get list
func (s *Service) ThumbupList(c context.Context, oid, mid int64, dmids []int64) (res map[int64]*model.ThumbupStat, err error) {
var (
statsReply *thumbUpApi.StatsReply
)
if statsReply, err = s.thumbupRPC.Stats(c, &thumbUpApi.StatsReq{
Business: "danmu",
OriginID: oid,
MessageIds: dmids,
Mid: mid,
IP: metadata.String(c, metadata.RemoteIP),
}); err != nil {
log.Error("dmAct s.thumbupRPC.StatsWithLike(oid:%+v,dmids:%+v), error(%v)", oid, dmids, err)
return
}
res = make(map[int64]*model.ThumbupStat)
if statsReply == nil {
return
}
for id, li := range statsReply.Stats {
st := new(model.ThumbupStat)
st.Likes = li.LikeNumber
st.UserLike = int8(li.LikeState)
res[id] = st
}
return
}

View File

@@ -0,0 +1,36 @@
package service
import (
"context"
"testing"
)
func TestServiceAddAct(t *testing.T) {
var (
c = context.TODO()
err error
cid, dmid, mid int64 = 5, 719149462, 3078992
op int8 = 1
)
err = svr.ThumbupDM(c, cid, dmid, mid, op)
if err != nil {
t.Error(err)
}
}
func TestServiceLikeList(t *testing.T) {
var (
c = context.TODO()
err error
cid, mid int64 = 5, 3078992
dmids = []int64{719149462, 719149463}
)
m, err := svr.ThumbupList(c, cid, mid, dmids)
if err != nil {
t.Error(err)
}
for k, v := range m {
t.Logf("=====%d: %+v", k, v)
}
t.Logf("=====%+v", m)
}

View File

@@ -0,0 +1,207 @@
package service
import (
"context"
"encoding/json"
"fmt"
"time"
"go-common/app/interface/main/dm2/model"
account "go-common/app/service/main/account/api"
"go-common/app/service/main/archive/api"
archive "go-common/app/service/main/archive/model/archive"
"go-common/library/log"
"go-common/library/sync/errgroup"
)
// .
var (
_dmFlagFmt = `{"rec_flag":%d,"rec_text":"%s","rec_switch":%d}`
)
// View dm view
func (s *Service) View(c context.Context, mid, aid, oid int64, tp int32, plat int32) (res *model.ViewDm, err error) {
var (
sub *model.Subject
eg errgroup.Group
)
// hot cache
if hit, ok := s.localViewCache[keyLocalView(aid, oid)]; ok {
return hit, nil
}
if sub, err = s.subject(c, model.SubTypeVideo, oid); err != nil {
return
}
res = &model.ViewDm{
Closed: sub.State == model.SubStateClosed,
Flag: json.RawMessage([]byte(fmt.Sprintf(_dmFlagFmt, s.conf.DmFlag.RecFlag, s.conf.DmFlag.RecText, s.conf.DmFlag.RecSwitch))),
}
// mask
eg.Go(func() (err error) {
var (
mask *model.Mask
maskPlat int8
)
switch plat {
case model.PlatWeb:
maskPlat = model.MaskPlatWeb
case model.PlatAndroid, model.PlatIPhone, model.PlatIPad, model.PlatPadHd:
maskPlat = model.MaskPlatMbl
default:
return
}
if mask, err = s.MaskListWithSub(c, oid, maskPlat, sub); err != nil {
log.Error("View.MaskListWithSub(oid:%v) error(%v)", oid, err)
return
}
res.ViewDmMask = mask
return
})
// subtitle
eg.Go(func() (err error) {
var (
subtitle *model.ViewSubtitle
)
if subtitle, err = s.viewSubtitles(c, aid, oid, tp); err != nil {
log.Error("View.viewSubtitles(aid:%v,oid:%v) error(%v)", aid, oid, err)
return
}
res.Subtitle = subtitle
return
})
// special dm
// TODO special dm
// dm seg rule
eg.Go(func() (err error) {
var (
dmSeg *model.ViewDmSeg
)
if dmSeg, err = s.viewDmSeg(c, aid, oid); err != nil {
log.Error("View.viewDmSeg(aid:%v,oid:%v) error(%v)", aid, oid, err)
return
}
res.ViewDmSeg = dmSeg
return
})
// ignore error
eg.Wait()
return
}
func (s *Service) viewSubtitles(c context.Context, aid, oid int64, tp int32) (viewSubtitle *model.ViewSubtitle, err error) {
var (
videoSubtitles []*model.VideoSubtitle
subtitles []*model.ViewVideoSubtitle
reply *account.InfoReply
subtitleSubject *model.SubtitleSubjectReply
)
if videoSubtitles, err = s.getVideoSubtitles(c, oid, tp); err != nil {
log.Error("View.getVideoSubtitles(oid:%v) error(%v)", oid, err)
return
}
for _, videoSubtitle := range videoSubtitles {
subtitle := &model.ViewVideoSubtitle{
ID: videoSubtitle.ID,
Lan: videoSubtitle.Lan,
LanDoc: videoSubtitle.LanDoc,
SubtitleURL: videoSubtitle.SubtitleURL,
}
if videoSubtitle.AuthorMid > 0 {
if reply, _ = s.accountRPC.Info3(c, &account.MidReq{Mid: videoSubtitle.AuthorMid}); reply != nil {
subtitle.Author = &model.ViewAuthor{
Mid: reply.GetInfo().GetMid(),
Name: reply.GetInfo().GetName(),
Sex: reply.GetInfo().GetSex(),
Face: reply.GetInfo().GetFace(),
Sign: reply.GetInfo().GetSign(),
Rank: reply.GetInfo().GetRank(),
}
}
}
subtitles = append(subtitles, subtitle)
}
if subtitleSubject, err = s.SubtitleSubject(c, aid); err != nil {
log.Error("View.subtitleSubject(aid:%v) error(%v)", aid, err)
return
}
viewSubtitle = &model.ViewSubtitle{
Subtitles: subtitles,
}
if subtitleSubject != nil {
viewSubtitle.Lan = subtitleSubject.Lan
viewSubtitle.LanDoc = subtitleSubject.LanDoc
}
return
}
func (s *Service) viewDmSeg(c context.Context, aid, oid int64) (dmSeg *model.ViewDmSeg, err error) {
var (
duration int64
cnt int64
)
if duration, err = s.videoDuration(c, aid, oid); err != nil {
return
}
cnt = duration / model.DefaultPageSize
if duration%model.DefaultPageSize > 0 {
cnt++
}
dmSeg = &model.ViewDmSeg{
PageSize: model.DefaultPageSize,
Total: cnt,
}
return
}
func (s *Service) viewProc() {
if len(s.conf.Localcache.ViewAids) <= 0 {
return
}
ticker := time.NewTicker(time.Duration(s.conf.Localcache.ViewExpire))
defer ticker.Stop()
for range ticker.C {
s.cacheView(s.conf.Localcache.ViewAids)
}
}
func keyLocalView(aid, oid int64) string {
return fmt.Sprintf("dm_view_%d_%d", aid, oid)
}
func (s *Service) cacheView(aids []int64) {
var (
sub *model.Subject
pages []*api.Page
err error
cacheMap = make(map[string]*model.ViewDm)
)
for _, aid := range aids {
pages, err = s.arcRPC.Page3(context.Background(), &archive.ArgAid2{
Aid: aid,
})
if err != nil {
log.Error("localCacheView.Page3(aid:%v) error(%v)", aid, err)
continue
}
for _, page := range pages {
if sub, err = s.subject(context.Background(), model.SubTypeVideo, page.Cid); err != nil {
continue
}
res := &model.ViewDm{
Closed: sub.State == model.SubStateClosed,
Flag: json.RawMessage([]byte(fmt.Sprintf(_dmFlagFmt, s.conf.DmFlag.RecFlag, s.conf.DmFlag.RecText, s.conf.DmFlag.RecSwitch))),
}
// ignore error
if res.Subtitle, err = s.viewSubtitles(context.Background(), aid, page.Cid, model.SubTypeVideo); err != nil {
log.Error("View.viewSubtitles(aid:%v,oid:%v) error(%v)", aid, page.Cid, err)
err = nil
}
if res.ViewDmSeg, err = s.viewDmSeg(context.Background(), aid, page.Cid); err != nil {
log.Error("View.viewDmSeg(aid:%v,oid:%v) error(%v)", aid, page.Cid, err)
err = nil
}
cacheMap[keyLocalView(aid, page.Cid)] = res
}
}
s.localViewCache = cacheMap
}

View File

@@ -0,0 +1,124 @@
package service
import (
"context"
"fmt"
"time"
"go-common/app/interface/main/dm2/model"
"go-common/library/log"
)
const (
_waveFormCallBackSuccess = 1
_waveFormPrefix = "http://i0.hdslb.com/bfs"
_expire = 20 // 2*10
)
func (s *Service) waveForm(c context.Context, oid int64, tp int32) (waveForm *model.WaveForm, err error) {
var (
cacheError bool
)
if waveForm, err = s.dao.WaveFormCache(c, oid, tp); err != nil {
cacheError = true
err = nil
}
if waveForm != nil && !waveForm.Empty {
return
}
if waveForm, err = s.dao.GetWaveForm(c, oid, tp); err != nil {
log.Error("params(oid:%v,tp:%v),error(%v)", oid, tp, err)
return
}
if waveForm == nil {
waveForm = &model.WaveForm{
Oid: oid,
Type: tp,
Empty: true,
}
}
if !cacheError {
temp := waveForm
s.cache.Do(c, func(ctx context.Context) {
s.dao.SetWaveFormCache(ctx, temp)
})
}
return
}
// WaveForm .
func (s *Service) WaveForm(c context.Context, aid, oid int64, tp int32, mid int64) (waveFormResp *model.WaveFormResp, err error) {
var (
uposErr error
waveFromURL string
waveForm *model.WaveForm
)
if err = s.SubtitlePermission(c, aid, oid, tp, mid); err != nil {
return
}
if waveForm, err = s.waveForm(c, oid, tp); err != nil {
log.Error("params(oid:%v,tp:%v),error(%v)", oid, tp, err)
return
}
if !waveForm.Empty {
waveFormResp = &model.WaveFormResp{
State: waveForm.State,
WaveFromURL: waveForm.WaveFromURL,
}
switch waveForm.State {
case model.WaveFormStatusFailed, model.WaveFormStatusSuccess:
return
case model.WaveFormStatusWaitting:
if time.Since(time.Unix(waveForm.Mtime, 0)) < _expire {
return
}
}
}
waveForm.State = model.WaveFormStatusWaitting
if waveFromURL, uposErr = s.dao.Upos(c, oid); uposErr != nil {
log.Error("postUpos(oid:%v),error(%v)", oid, err)
waveForm.State = model.WaveFormStatusError
}
waveForm.WaveFromURL = fmt.Sprintf("%s/%s", _waveFormPrefix, waveFromURL)
if err = s.dao.UpsertWaveFrom(c, waveForm); err != nil {
log.Error("params(waveForm:%+v),error(%v)", waveForm, err)
return
}
if err = s.dao.DelWaveFormCache(c, oid, tp); err != nil {
log.Error("DelWaveFormCache.params(oid:%v,tp:%v),error(%v)", oid, tp, err)
return
}
waveFormResp = &model.WaveFormResp{
State: waveForm.State,
WaveFromURL: waveForm.WaveFromURL,
}
return
}
// WaveFormCallBack .
func (s *Service) WaveFormCallBack(c context.Context, oid int64, tp int32, code int32, info string) (err error) {
var (
waveForm *model.WaveForm
)
if waveForm, err = s.waveForm(c, oid, tp); err != nil {
log.Error("params(oid:%v,tp:%v),error(%v)", oid, tp, err)
return
}
if code == _waveFormCallBackSuccess {
waveForm.State = model.WaveFormStatusSuccess
} else {
waveForm.State = model.WaveFormStatusFailed
log.Error("WaveFormCallBack.params(oid:%v,tp:%v).errorInfo(%s)", oid, tp, info)
}
if err = s.dao.UpsertWaveFrom(c, waveForm); err != nil {
log.Error("params(waveForm:%+v),error(%v)", waveForm, err)
return
}
if err = s.dao.DelWaveFormCache(c, oid, tp); err != nil {
log.Error("DelWaveFormCache.params(oid:%v,tp:%v),error(%v)", oid, tp, err)
return
}
return
}