package srv_commend import ( "context" "strconv" "time" "videoweb/pub/common/enum/redisconst" "videoweb/pub/common/log" "videoweb/pub/common/srvdef" "videoweb/pub/models/commentlikemod" "videoweb/pub/models/commentmod" "videoweb/pub/models/playletchaptermod" "videoweb/pub/models/playletmod" "videoweb/pub/models/videoinformationmod" "videoweb/pub/mongos" "videoweb/pub/pubdef" "videoweb/pub/redis" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo/options" ) func SearchComandList(ctx context.Context, param *CommendSearch) (*CommendSearchResp, *srvdef.SrvErr) { filter := bson.M{} opt := &options.FindOptions{} if param.ObjectId != nil { filter[commentmod.FN.Comment.ObjectId] = *param.ObjectId } if param.ObjectType != nil { filter[commentmod.FN.Comment.ObjectType] = *param.ObjectType } if param.Level != nil { filter[commentmod.FN.Comment.Level] = *param.Level } if param.UserName != nil { filter[commentmod.FN.Comment.UserName] = bson.M{"$regex": *param.UserName} } if param.Text != nil { filter[commentmod.FN.Comment.Text] = bson.M{"$regex": *param.Text} } if param.ReviewStatus != nil { filter[commentmod.FN.Comment.ReviewStatus] = *param.ReviewStatus } sort := bson.D{ {commentmod.FN.Comment.TopRank, -1}, {commentmod.FN.Comment.CreatedAt, -1}, } opt.SetSort(sort) skip := (param.PageNum - 1) * param.PageSize opt.SetSkip(skip) opt.SetLimit(param.PageSize) total, serr := commentmod.CountComments(ctx, filter) if serr != nil { return nil, serr } if total == 0 { return &CommendSearchResp{}, nil } commends, serr := commentmod.FindManyComment(ctx, filter, opt) if serr != nil { return nil, serr } resp := &CommendSearchResp{ Total: total, List: make([]*PostCommentInfo, 0), } for _, commendInfo := range commends { tmp := &PostCommentInfo{ Id: commendInfo.Id, ObjectId: commendInfo.ObjectId, ObjectName: commendInfo.ObjectName, UserName: commendInfo.UserName, Level: commendInfo.Level, ParentsId: commendInfo.ParentsId, ReplyUserName: commendInfo.ReplyUserName, Text: commendInfo.Text, ReviewStatus: commendInfo.ReviewStatus, UpdatedAt: commendInfo.UpdatedAt, Ip: commendInfo.Ip, CreatedAt: commendInfo.CreatedAt, ObjectType: commendInfo.ObjectType, Likes: commendInfo.CountLike, } resp.List = append(resp.List, tmp) } return resp, nil } // DelCommend 删除一条评论 func DelCommend(ctx context.Context, commendId int64) *srvdef.SrvErr { commendInfo, serr := commentmod.FindOneComment(ctx, bson.M{commentmod.FN.Comment.Id: commendId}) if serr != nil { return serr } _, serr = commentmod.DelOneComment(ctx, bson.M{commentmod.FN.Comment.Id: commendId}) if serr != nil { return serr } if serr = commentlikemod.DeleteByCommentIds(ctx, []int64{commendId}); serr != nil { return serr } clearCommentListCacheByComments(commentmod.CommentModelList{commendInfo}) if err := refreshParentReplyCounts(ctx, commentmod.CommentModelList{commendInfo}); err != nil { return err } return updateCommentCountByComments(ctx, commentmod.CommentModelList{commendInfo}) } // DelCommend 删除用户所有评论 func DelUserCommend(ctx context.Context, ip string) *srvdef.SrvErr { commendList, serr := commentmod.FindManyComment(ctx, bson.M{commentmod.FN.Comment.Ip: ip}) if serr != nil { return serr } _, serr = commentmod.DelManyComment(ctx, bson.M{commentmod.FN.Comment.Ip: ip}) if serr != nil { return serr } if serr = commentlikemod.DeleteByCommentIds(ctx, commentIdsFromModels(commendList)); serr != nil { return serr } clearCommentListCacheByComments(commendList) if err := refreshParentReplyCounts(ctx, commendList); err != nil { return err } return updateCommentCountByComments(ctx, commendList) } func commentIdsFromModels(comments commentmod.CommentModelList) []int64 { ids := make([]int64, 0, len(comments)) for _, comment := range comments { if comment != nil { ids = append(ids, comment.Id) } } return ids } // AddPostComment 发布评论 func AddPostComment(ctx context.Context, req *CommendReplay) *srvdef.SrvErr { now := time.Now() if req.Score > 10 { log.Error("AddPostComment ErrParamError", log.Any("Score", req.Score)) return srvdef.New(srvdef.ErrParamError, "", "") } // 2. 生成评论相关信息 commentInfo := &commentmod.CommentModel{ Id: mongos.Lid.Comment.Get(), ObjectId: req.ObjectId, ObjectType: pubdef.CommentObjectTypeVideo, ObjectName: pubdef.CommentObjectTypeName(pubdef.CommentObjectTypeVideo), UserName: req.UserName, ParentsId: req.ParentsId, Text: req.Text, Level: 1, UpdatedAt: now, CreatedAt: now, } if commentInfo.ParentsId > 0 { commentInfo.Level = 2 } else { commentInfo.Level = 1 } commentInfo.ReviewStatus = 1 // 管理端的默认审核通过 // 3. 插入数据库 serr := commentmod.InsertOneComment(ctx, commentInfo) if serr != nil { log.Error("AddPostComment InsertOnePostComment", log.Any("req", req), log.Any("err", serr)) return serr } clearCommentListCacheByComments(commentmod.CommentModelList{commentInfo}) go func() { _ = updateCommentCountByComments(ctx, commentmod.CommentModelList{commentInfo}) // 4. 修改回复条数 if req.ParentsId > 0 { filter := bson.M{ commentmod.FN.Comment.Id: req.ParentsId, } updateDoc := bson.M{ "$inc": bson.M{ commentmod.FN.Comment.CommentCount: 1, }, } serr = commentmod.UpdateOneComment(ctx, filter, updateDoc) if serr != nil { log.Error("AddPostComment UpdateOnePostComment", log.Any("req", req), log.Any("err", serr)) return } } }() return nil } // 置顶评论 func TopPostComment(ctx context.Context, id int64) *srvdef.SrvErr { filter := bson.M{ commentmod.FN.Comment.Id: id, } comment, serr := commentmod.FindOneComment(ctx, filter) if serr != nil { return serr } //获取置顶的一条评论 topRankComment, serr := getTopRankComment(ctx, comment.ObjectId) if serr != nil { return serr } if topRankComment.Id == id && topRankComment.TopRank != 0 { return nil } serr = commentmod.UpdateOneComment(ctx, filter, bson.M{ "$set": bson.M{ commentmod.FN.Comment.TopRank: topRankComment.TopRank + 1, }, }) if serr != nil { return serr } clearCommentListCacheByComments(commentmod.CommentModelList{comment}) return nil } func getTopRankComment(ctx context.Context, objectId int64) (*commentmod.CommentModel, *srvdef.SrvErr) { filter := bson.M{ commentmod.FN.Comment.ObjectId: objectId, } opts := options.FindOne().SetSort(bson.M{ commentmod.FN.Comment.TopRank: -1, }) comment, serr := commentmod.FindOneComment(ctx, filter, opts) if serr != nil { return nil, serr } return comment, nil } func ReviewComment(ctx context.Context, id int64, reviewStatus int) *srvdef.SrvErr { comment, err := commentmod.FindOneComment(ctx, bson.M{commentmod.FN.Comment.Id: id}) if err != nil { return err } updateDoc := bson.M{ commentmod.FN.Comment.ReviewStatus: reviewStatus, } filter := bson.M{ commentmod.FN.Comment.Id: id, } serr := commentmod.UpdateOneComment(ctx, filter, bson.M{"$set": updateDoc}) if serr != nil { log.Error("审核评论失败", log.Any("id", id), log.Any("reviewStatus", reviewStatus), log.Any("err", serr)) return serr } clearCommentListCacheByComments(commentmod.CommentModelList{comment}) if comment.ReviewStatus != reviewStatus { if err := refreshParentReplyCounts(ctx, commentmod.CommentModelList{comment}); err != nil { return err } } return updateCommentCountByComments(ctx, commentmod.CommentModelList{comment}) } // ReviewCommendBatch 批量更新 func ReviewCommendBatch(ctx context.Context, commendIds *[]int64, reviewStatus int) *srvdef.SrvErr { if commendIds == nil || len(*commendIds) == 0 { return srvdef.New(srvdef.ErrParamError, "", "") } commentList, err := commentmod.FindManyComment(ctx, bson.M{ commentmod.FN.Comment.Id: bson.M{"$in": *commendIds}, }) if err != nil { return err } updateDoc := bson.M{ commentmod.FN.Comment.ReviewStatus: reviewStatus, } filter := bson.M{commentmod.FN.Comment.Id: bson.M{"$in": commendIds}} serr := commentmod.UpdateManyComment(ctx, filter, bson.M{"$set": updateDoc}) if serr != nil { log.Error("批量审核评论失败", log.Any("commendId", *commendIds), log.Any("reviewStatus", reviewStatus), log.Any("err", serr)) return serr } clearCommentListCacheByComments(commentList) if err := refreshParentReplyCounts(ctx, commentList); err != nil { return err } return updateCommentCountByComments(ctx, commentList) } // refreshParentReplyCounts 按已审核的直接回复重算父评论回复数 func refreshParentReplyCounts(ctx context.Context, comments commentmod.CommentModelList) *srvdef.SrvErr { seen := make(map[int64]struct{}, len(comments)) for _, comment := range comments { if comment == nil || comment.ParentsId <= 0 { continue } if _, ok := seen[comment.ParentsId]; ok { continue } seen[comment.ParentsId] = struct{}{} count, err := commentmod.CountComments(ctx, bson.M{ commentmod.FN.Comment.ParentsId: comment.ParentsId, commentmod.FN.Comment.ReviewStatus: 1, }) if err != nil { return err } if err := commentmod.UpdateOneComment(ctx, bson.M{commentmod.FN.Comment.Id: comment.ParentsId}, bson.M{"$set": bson.M{commentmod.FN.Comment.CommentCount: count}}); err != nil && err.Code != srvdef.ErrNoData { return err } } return nil } // clearCommentListCacheByComments 按对象ID和对象类型精确清理评论列表缓存 func clearCommentListCacheByComments(commentList commentmod.CommentModelList) { seen := make(map[string]struct{}, len(commentList)) for _, comment := range commentList { if comment == nil { continue } key := commentCacheUniqueKey(comment.ObjectId, comment.ObjectType) if _, ok := seen[key]; ok { continue } seen[key] = struct{}{} cacheKey := redisconst.CommentListCacheKey(comment.ObjectId, comment.ObjectType) if err := redis.Del(cacheKey); err != nil { log.Warn("清理评论列表缓存失败", log.Any("cacheKey", cacheKey), log.E(err)) } } } // commentCacheUniqueKey 生成对象维度去重key,避免批量审核时重复清理同一对象缓存 func commentCacheUniqueKey(objectId int64, objectType pubdef.CommentObjectType) string { return strconv.FormatInt(objectId, 10) + "_" + strconv.FormatInt(int64(objectType), 10) } // UpdateCommentCount 评论审核通过/删除后,同步更新对应媒体内容的真实评论数 // objectType: 0=视频 4=短剧 11=短剧剧集 func UpdateCommentCount(ctx context.Context, commendIds []int64) *srvdef.SrvErr { commentList, _ := commentmod.FindManyComment(ctx, bson.M{ commentmod.FN.Comment.Id: bson.M{"$in": commendIds}, }) return updateCommentCountByComments(ctx, commentList) } func updateCommentCountByComments(ctx context.Context, commentList commentmod.CommentModelList) *srvdef.SrvErr { seen := make(map[string]struct{}, len(commentList)) for _, comment := range commentList { if comment == nil { continue } key := commentCacheUniqueKey(comment.ObjectId, comment.ObjectType) if _, ok := seen[key]; ok { continue } seen[key] = struct{}{} count, countErr := commentmod.CountComments(ctx, bson.M{ commentmod.FN.Comment.ObjectId: comment.ObjectId, commentmod.FN.Comment.ObjectType: comment.ObjectType, commentmod.FN.Comment.ReviewStatus: 1, }) if countErr != nil { return countErr } var serr *srvdef.SrvErr switch comment.ObjectType { case pubdef.CommentObjectTypeVideo: serr = videoinformationmod.UpdateOneVideoInformation(ctx, bson.M{videoinformationmod.FN.VideoInformation.Id: comment.ObjectId}, bson.M{"$set": bson.M{videoinformationmod.FN.VideoInformation.Real.Comments: count}}, ) case pubdef.CommentObjectTypePlaylet: serr = playletmod.UpdateOnePlaylet(ctx, bson.M{playletmod.FN.Playlet.Id: comment.ObjectId}, bson.M{"$set": bson.M{playletmod.FN.Playlet.CountComment: count}}, ) case pubdef.CommentObjectTypePlayletChapter: serr = playletchaptermod.UpdateOnePlayletChapter(ctx, bson.M{playletchaptermod.FN.PlayletChapter.Id: comment.ObjectId}, bson.M{"$set": bson.M{playletchaptermod.FN.PlayletChapter.CountComment: count}}, ) } if serr != nil { log.Error("UpdateCommentCount 更新评论数失败", log.Any("objectId", comment.ObjectId), log.Any("objectType", comment.ObjectType), log.Any("err", serr), ) return serr } } return nil }