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