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:分区id,value: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) } } } }