package service import ( "context" "sync" "time" "go-common/app/interface/main/space/conf" "go-common/app/interface/main/space/model" arcmdl "go-common/app/service/main/archive/api" "go-common/library/ecode" "go-common/library/log" "go-common/library/sync/errgroup" xtime "go-common/library/time" ) const _aidBulkSize = 50 // AddChannelArc add channel archive. func (s *Service) AddChannelArc(c context.Context, mid, cid int64, aids []int64) (fakeAids []int64, err error) { var ( lastID int64 orderNum int chAids, addAids []int64 arcs map[int64]*arcmdl.Arc videos []*model.ChannelArc videoMap map[int64]int64 remainVideos []*model.ChannelArcSort ts = time.Now() ) fakeAids = make([]int64, 0) if _, _, err = s.channel(c, mid, cid); err != nil { log.Error("s.dao.Channel(%d,%d) error(%v)", mid, cid, err) return } if videos, err = s.dao.ChannelVideos(c, mid, cid, false); err != nil { log.Error("s.dao.channelVideos(%d,%d) error(%v)", mid, cid, err) return } else if orderNum = len(videos); orderNum > 0 { if len(aids)+orderNum > conf.Conf.Rule.MaxChArcLimit { err = ecode.ChMaxArcCount return } videoMap = make(map[int64]int64) for _, video := range videos { chAids = append(chAids, video.Aid) videoMap[video.Aid] = video.Aid } } for _, aid := range aids { if _, ok := videoMap[aid]; ok { fakeAids = append(fakeAids, aid) } else { addAids = append(addAids, aid) } } if len(addAids) == 0 { err = ecode.ChAidsExist return } if err = s.arcsCheck(c, mid, chAids); err != nil { return } if arcs, err = s.archives(c, addAids); err != nil { log.Error("s.arc.Archive3(%v) error(%v)", addAids, err) return } for _, aid := range addAids { if arc, ok := arcs[aid]; !ok || !arc.IsNormal() || arc.Author.Mid != mid { fakeAids = append(fakeAids, aid) continue } orderNum++ remainVideos = append(remainVideos, &model.ChannelArcSort{Aid: aid, OrderNum: orderNum}) } if len(remainVideos) == 0 { err = ecode.ChAidsExist return } if lastID, err = s.dao.AddChannelArc(c, mid, cid, ts, remainVideos); err != nil { log.Error("s.dao.AddChannelArc(mid:%d,cid:%d) error(%v)", mid, cid, err) return } else if lastID > 0 { var arcs []*model.ChannelArc for _, v := range remainVideos { arc := &model.ChannelArc{ID: lastID, Mid: mid, Cid: cid, Aid: v.Aid, OrderNum: v.OrderNum, Mtime: xtime.Time(ts.Unix())} arcs = append(arcs, arc) } s.dao.AddChannelArcCache(context.Background(), mid, cid, arcs) } return } func (s *Service) arcsCheck(c context.Context, mid int64, aids []int64) (err error) { var arcs map[int64]*arcmdl.Arc if arcs, err = s.archives(c, aids); err != nil { log.Error("s.archives error(%v)", err) return } for _, aid := range aids { if arc, ok := arcs[aid]; !ok || !arc.IsNormal() || arc.Author.Mid != mid { err = ecode.ChFakeAid return } } return } // DelChannelArc delete channel archive. func (s *Service) DelChannelArc(c context.Context, mid, cid, aid int64) (err error) { var ( affected int64 orderNum int videos []*model.ChannelArc ) if videos, err = s.dao.ChannelVideos(c, mid, cid, false); err != nil { log.Error("s.dao.Channel(%d,%d) error(%v)", mid, cid, err) return } else if len(videos) == 0 { err = ecode.ChNoArcs return } else { check := false for _, video := range videos { if aid == video.Aid { check = true orderNum = video.OrderNum } } if !check { err = ecode.ChNoArc return } } if affected, err = s.dao.DelChannelArc(c, mid, cid, aid, orderNum); err != nil { log.Error("s.dao.DelChannelArc(%d,%d) error(%v)", mid, aid, err) return } else if affected > 0 { s.dao.DelChannelArcCache(c, mid, cid, aid) s.setChannelArcSortCache(c, mid, cid) } return } // SortChannelArc sort channel archive. func (s *Service) SortChannelArc(c context.Context, mid, cid, aid int64, orderNum int) (err error) { var ( videos []*model.ChannelArc bfSortBegin, bfSortEnd, chSort, afSort []*model.ChannelArcSort affected int64 aidIndex, aidOn int aidCheck bool ts = time.Now() ) if videos, err = s.dao.ChannelVideos(c, mid, cid, false); err != nil { log.Error("s.dao.ChannelVideos(%d,%d) error(%v)", mid, cid, err) return } else if len(videos) == 0 { err = ecode.ChNoArcs return } else { videoLen := len(videos) if orderNum > videoLen { err = ecode.RequestErr return } for index, video := range videos { if aid == video.Aid { aidCheck = true aidIndex = index aidOn = video.OrderNum break } } if !aidCheck { err = ecode.RequestErr return } if orderNum > aidOn { chSort = append(chSort, &model.ChannelArcSort{Aid: aid, OrderNum: orderNum}) for i, v := range videos { if i < videoLen-orderNum { bfSortBegin = append(bfSortBegin, &model.ChannelArcSort{Aid: v.Aid, OrderNum: v.OrderNum}) } else if i >= videoLen-orderNum && i < aidIndex { chSort = append(chSort, &model.ChannelArcSort{Aid: v.Aid, OrderNum: v.OrderNum - 1}) } else if i > aidIndex { bfSortEnd = append(bfSortEnd, &model.ChannelArcSort{Aid: v.Aid, OrderNum: v.OrderNum}) } } } else if orderNum < aidOn { for i, v := range videos { if i < aidIndex { bfSortBegin = append(bfSortBegin, &model.ChannelArcSort{Aid: v.Aid, OrderNum: v.OrderNum}) } else if i > aidIndex && i <= videoLen-orderNum { chSort = append(chSort, &model.ChannelArcSort{Aid: v.Aid, OrderNum: v.OrderNum + 1}) } else if i > videoLen-orderNum { bfSortEnd = append(bfSortEnd, &model.ChannelArcSort{Aid: v.Aid, OrderNum: v.OrderNum}) } } chSort = append(chSort, &model.ChannelArcSort{Aid: aid, OrderNum: orderNum}) } else { return } afSort = append(afSort, bfSortBegin...) afSort = append(afSort, chSort...) afSort = append(afSort, bfSortEnd...) } if affected, err = s.dao.EditChannelArc(c, mid, cid, ts, chSort); err != nil { log.Error("s.dao.s.dao.EditChannelArc(%d,%d,%d,%d) error(%v)", mid, cid, aid, orderNum, err) return } else if affected > 0 { s.dao.SetChannelArcSortCache(c, mid, cid, afSort) } return } // ChannelVideos get channel and channel video info. func (s *Service) ChannelVideos(c context.Context, mid, cid int64, pn, ps int, isGuest, order bool) (res *model.ChannelDetail, err error) { var ( channel *model.Channel start = (pn - 1) * ps end = start + ps - 1 ) if channel, err = s.Channel(c, mid, cid); err != nil { return } res = &model.ChannelDetail{Channel: channel} res.Archives, err = s.channelArc(c, mid, cid, start, end, isGuest, order) return } func (s *Service) channelVideos(c context.Context, mid, cid int64, start, end int, order bool) (res []*model.ChannelArc, err error) { var ( videos []*model.ChannelArc addCache = true ) if res, err = s.dao.ChannelArcsCache(c, mid, cid, start, end, order); err != nil { addCache = false } else if len(res) > 0 { return } if videos, err = s.dao.ChannelVideos(c, mid, cid, order); err != nil { log.Error("s.dao.ChannelVideos(%d,%d) error(%v)", mid, cid, err) return } else if len(videos) > 0 { if addCache { s.cache.Do(c, func(c context.Context) { s.dao.SetChannelArcsCache(c, mid, cid, videos) s.setChannelArcSortCache(c, mid, cid) }) } length := len(videos) if length < start { res = make([]*model.ChannelArc, 0) return } if length > end { res = videos[start : end+1] } else { res = videos[start:] } } return } // CheckChannelVideo check useless channel video. func (s *Service) CheckChannelVideo(c context.Context, mid, cid int64) (err error) { var ( videos []*model.ChannelArc aids []int64 ) if videos, err = s.dao.ChannelVideos(c, mid, cid, false); err != nil { log.Error("s.dao.channelVideos(%d,%d) error(%v)", mid, cid, err) return } for _, v := range videos { aids = append(aids, v.Aid) } err = s.arcsCheck(c, mid, aids) return } func (s *Service) channelArc(c context.Context, mid, cid int64, start, end int, isGuest, order bool) (res []*arcmdl.Arc, err error) { var ( videoAids []*model.ChannelArc archives map[int64]*arcmdl.Arc aids []int64 ) if videoAids, err = s.channelVideos(c, mid, cid, start, end, order); err != nil { log.Error("s.dao.ChannelVideos(%d,%d) error(%v)", mid, cid, err) return } else if len(videoAids) == 0 { res = _emptyChArc return } for _, video := range videoAids { aids = append(aids, video.Aid) } if archives, err = s.archives(c, aids); err != nil { log.Error("s.arc.Archives3(%v) error(%v)", aids, err) return } for _, video := range videoAids { if arc, ok := archives[video.Aid]; ok { if arc.IsNormal() { if arc.Access >= 10000 { arc.Stat.View = -1 } res = append(res, arc) } else { res = append(res, &arcmdl.Arc{Aid: video.Aid, Title: arc.Title, Pic: arc.Pic, Stat: arc.Stat, PubDate: arc.PubDate, State: arc.State}) } } } return } func (s *Service) setChannelArcSortCache(c context.Context, mid, cid int64) (err error) { var ( videos []*model.ChannelArc sorts []*model.ChannelArcSort ) if videos, err = s.dao.ChannelVideos(c, mid, cid, false); err != nil { log.Error("s.dao.ChannelVideos(%d,%d) error(%v)", mid, cid, err) return } else if len(videos) == 0 { return } for _, v := range videos { sort := &model.ChannelArcSort{Aid: v.Aid, OrderNum: v.OrderNum} sorts = append(sorts, sort) } return s.dao.SetChannelArcSortCache(c, mid, cid, sorts) } func (s *Service) archives(c context.Context, aids []int64) (archives map[int64]*arcmdl.Arc, err error) { var ( mutex = sync.Mutex{} aidsLen = len(aids) group, errCtx = errgroup.WithContext(c) ) archives = make(map[int64]*arcmdl.Arc, aidsLen) for i := 0; i < aidsLen; i += _aidBulkSize { var partAids []int64 if i+_aidBulkSize > aidsLen { partAids = aids[i:] } else { partAids = aids[i : i+_aidBulkSize] } group.Go(func() (err error) { var arcs *arcmdl.ArcsReply arg := &arcmdl.ArcsRequest{Aids: partAids} if arcs, err = s.arcClient.Arcs(errCtx, arg); err != nil { log.Error("s.arcClient.Arcs(%v) error(%v)", partAids, err) return } mutex.Lock() for _, v := range arcs.Arcs { archives[v.Aid] = v } mutex.Unlock() return }) } err = group.Wait() return }