using FreeSql; using LiveForum.Code.Base; using LiveForum.Code.JwtInfrastructure; using LiveForum.Code.SensitiveWord.Interfaces; using LiveForum.IService.Messages; using LiveForum.IService.Posts; using LiveForum.IService.Others; using LiveForum.IService.Users; using LiveForum.Model; using LiveForum.Model.Dto.Base; using LiveForum.Model.Dto.Messages; using LiveForum.Model.Dto.PostComments; using LiveForum.Model.Dto.Users; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LiveForum.Service.Posts { public class PostCommentsService : IPostCommentsService { private readonly JwtUserInfoModel _userInfoModel; private readonly IBaseRepository _commentsRepository; private readonly IBaseRepository _postsRepository; private readonly IBaseRepository _usersRepository; private readonly IBaseRepository _likesRepository; private readonly ISensitiveWordService _sensitiveWordService; private readonly IUserInfoService _userInfoService; private readonly IMessagePublisher _messagePublisher; private readonly ILikeService _likeService; private readonly IPostReplyIntervalService _postReplyIntervalService; private readonly IAntiAddictionService _antiAddictionService; /// /// 构造函数 /// /// JWT用户信息模型 /// 评论仓储 /// 帖子仓储 /// 用户仓储 /// 点赞仓储 /// 敏感词服务 /// 用户信息转换服务 /// 消息发布器 /// 点赞服务 /// 发帖/回复间隔校验服务 /// 防沉迷校验服务 public PostCommentsService( JwtUserInfoModel userInfoModel, IBaseRepository commentsRepository, IBaseRepository postsRepository, IBaseRepository usersRepository, IBaseRepository likesRepository, ISensitiveWordService sensitiveWordService, IUserInfoService userInfoService, IMessagePublisher messagePublisher, ILikeService likeService, IPostReplyIntervalService postReplyIntervalService, IAntiAddictionService antiAddictionService) { _userInfoModel = userInfoModel; _commentsRepository = commentsRepository; _postsRepository = postsRepository; _usersRepository = usersRepository; _likesRepository = likesRepository; _sensitiveWordService = sensitiveWordService; _userInfoService = userInfoService; _messagePublisher = messagePublisher; _likeService = likeService; _postReplyIntervalService = postReplyIntervalService; _antiAddictionService = antiAddictionService; } /// /// 获取评论列表 /// /// 请求参数 /// public async Task> GetPostComments(GetPostCommentsReq request) { var currentUserId = (long)_userInfoModel.UserId; // 1. 验证帖子是否存在 var post = await _postsRepository.Select .Where(x => x.Id == request.PostId && !x.IsDeleted) .FirstAsync(); if (post == null) { return new BaseResponse(ResponseCode.Error, "帖子不存在"); } // 2. 构建查询条件 - 只获取顶级评论(ParentCommentId为null) var query = _commentsRepository.Select .Where(x => x.PostId == request.PostId && x.ParentCommentId == null && !x.IsDeleted); // 3. 排序 if (request.SortType == 1) // 正序 { query = query.OrderBy(x => x.CreatedAt); } else // 倒序 { query = query.OrderByDescending(x => x.CreatedAt); } // 4. 分页 var total = await query.CountAsync(); var comments = await query .Skip((request.PageIndex - 1) * request.PageSize) .Take(request.PageSize) .ToListAsync(); if (!comments.Any()) { var res = new GetPostCommentsRespDto { PageIndex = request.PageIndex, PageSize = request.PageSize, Total = (int)total, TotalPages = (int)Math.Ceiling((double)total / request.PageSize), Items = new List() }; return new BaseResponse(res); } // 5. 获取相关数据 var commentIds = comments.Select(x => x.Id).ToList(); var userIds = comments.Select(x => x.UserId).Distinct().ToList(); // 获取用户信息 //var users = await _usersRepository.Select // .Where(x => userIds.Contains(x.Id)) // .ToListAsync(); // 获取回复数量 var replyCounts = await _commentsRepository.Select .Where(x => commentIds.Contains(x.ParentCommentId!.Value) && !x.IsDeleted) .GroupBy(x => x.ParentCommentId) .ToListAsync(g => new { ParentCommentId = g.Key, Count = g.Count() }); var replyCountDict = replyCounts.ToDictionary(x => x.ParentCommentId!.Value, x => x.Count); // 获取每个顶级评论的前5条回复 var replies = await _commentsRepository.Select .Where(x => commentIds.Contains(x.ParentCommentId!.Value) && !x.IsDeleted) .OrderBy(x => x.CreatedAt) .ToListAsync(); // 按父评论ID分组,每组取前5条 var replyDict = replies .GroupBy(x => x.ParentCommentId!.Value) .ToDictionary( g => g.Key, g => g.Take(5).ToList() ); // 收集所有相关的用户ID(包括回复者和被回复用户) var replyUserIds = replies.Select(x => x.UserId).Distinct().ToList(); var replyToUserIds = replies.Where(x => x.ReplyToUserId.HasValue) .Select(x => x.ReplyToUserId!.Value).Distinct().ToList(); var allUserIds = userIds.Union(replyUserIds).Union(replyToUserIds).Distinct().ToList(); // 重新获取所有用户信息 var users = await _usersRepository.Select .Where(x => allUserIds.Contains(x.Id)) .ToListAsync(); // 获取所有评论和回复的ID,用于查询点赞状态 var allCommentIds = commentIds.Union(replies.Select(x => x.Id)).ToList(); // 批量查询点赞状态和点赞数(包括顶级评论和回复) var likedStatusDict = await _likeService.BatchIsLikedAsync(currentUserId, 2, allCommentIds); var likeCountDict = await _likeService.BatchGetLikeCountAsync(2, allCommentIds); // 6. 批量转换用户信息(自动获取认证类型数据) var usersDict = await _userInfoService.ToUserInfoDtoDictionaryAsync(users); // 7. 构建评论列表 var items = comments.Select(comment => { var user = users.FirstOrDefault(x => x.Id == comment.UserId); var replyCount = replyCountDict.GetValueOrDefault(comment.Id, 0); // 获取该评论的前5条回复 var commentReplies = replyDict.GetValueOrDefault(comment.Id, new List()); var replyDtos = commentReplies.Select(reply => { var replyUser = users.FirstOrDefault(x => x.Id == reply.UserId); var replyToUser = reply.ReplyToUserId.HasValue ? users.FirstOrDefault(x => x.Id == reply.ReplyToUserId.Value) : null; return new CommentReplyDto { CommentId = reply.Id, ParentCommentId = reply.ParentCommentId!.Value, Content = reply.Content, LikeCount = likeCountDict.ContainsKey(reply.Id) ? likeCountDict[reply.Id] : 0, CreatedAt = reply.CreatedAt, ReplyToUserId = reply.ReplyToUserId, ReplyToUserName = replyToUser?.NickName ?? "", User = replyUser != null && usersDict.ContainsKey(replyUser.Id) ? usersDict[replyUser.Id] : new UserInfoDto(), IsLiked = likedStatusDict.ContainsKey(reply.Id) && likedStatusDict[reply.Id] }; }).ToList(); return new CommentListItemDto { CommentId = comment.Id, PostId = comment.PostId, Content = comment.Content, LikeCount = likeCountDict.ContainsKey(comment.Id) ? likeCountDict[comment.Id] : 0, ReplyCount = replyCount, CreatedAt = comment.CreatedAt, User = user != null && usersDict.ContainsKey(user.Id) ? usersDict[user.Id] : new UserInfoDto(), IsLiked = likedStatusDict.ContainsKey(comment.Id) && likedStatusDict[comment.Id], Replies = replyDtos // 显示前5条回复 }; }).ToList(); var result = new GetPostCommentsRespDto { PageIndex = request.PageIndex, PageSize = request.PageSize, Total = (int)total, TotalPages = (int)Math.Ceiling((double)total / request.PageSize), Items = items }; return new BaseResponse(result); } /// /// 获取评论回复列表 /// /// 请求参数 /// public async Task> GetCommentsReplies(GetCommentsRepliesReq request) { var currentUserId = (long)_userInfoModel.UserId; // 1. 验证父评论是否存在 var parentComment = await _commentsRepository.Select .Where(x => x.Id == request.CommentId && !x.IsDeleted) .FirstAsync(); if (parentComment == null) { return new BaseResponse(ResponseCode.Error, "评论不存在"); } // 2. 构建查询条件 - 获取该评论的所有回复 var query = _commentsRepository.Select .Where(x => x.ParentCommentId == request.CommentId && !x.IsDeleted) .OrderBy(x => x.CreatedAt); // 3. 分页 var total = await query.CountAsync(); var replies = await query .Skip((request.PageIndex - 1) * request.PageSize) .Take(request.PageSize) .ToListAsync(); if (!replies.Any()) { var res = new GetCommentsRepliesRespDto { PageIndex = request.PageIndex, PageSize = request.PageSize, Total = (int)total, TotalPages = (int)Math.Ceiling((double)total / request.PageSize), Items = new List() }; return new BaseResponse(res); } // 4. 获取相关数据 var replyIds = replies.Select(x => x.Id).ToList(); var userIds = replies.Select(x => x.UserId).Distinct().ToList(); // 获取用户信息 var users = await _usersRepository.Select .Where(x => userIds.Contains(x.Id)) .ToListAsync(); // 批量查询点赞状态和点赞数 var likedStatusDict = await _likeService.BatchIsLikedAsync(currentUserId, 2, replyIds); var likeCountDict = await _likeService.BatchGetLikeCountAsync(2, replyIds); // 5. 批量转换用户信息(自动获取认证类型数据) var usersDict = await _userInfoService.ToUserInfoDtoDictionaryAsync(users); // 6. 构建回复列表 var items = replies.Select(reply => { var user = users.FirstOrDefault(x => x.Id == reply.UserId); var replyToUser = reply.ReplyToUserId.HasValue ? users.FirstOrDefault(x => x.Id == reply.ReplyToUserId.Value) : null; return new CommentReplyDto { CommentId = reply.Id, ParentCommentId = reply.ParentCommentId, ReplyToUserId = reply.ReplyToUserId, ReplyToUserName = replyToUser?.NickName ?? "", Content = reply.Content, LikeCount = likeCountDict.ContainsKey(reply.Id) ? likeCountDict[reply.Id] : 0, CreatedAt = reply.CreatedAt, User = user != null && usersDict.ContainsKey(user.Id) ? usersDict[user.Id] : new UserInfoDto(), IsLiked = likedStatusDict.ContainsKey(reply.Id) && likedStatusDict[reply.Id] }; }).ToList(); var result = new GetCommentsRepliesRespDto { PageIndex = request.PageIndex, PageSize = request.PageSize, Total = (int)total, TotalPages = (int)Math.Ceiling((double)total / request.PageSize), Items = items }; return new BaseResponse(result); } /// /// 发表评论 /// /// 请求参数 /// public async Task> PublishPostComments(PublishPostCommentsReq request) { var currentUserId = (long)_userInfoModel.UserId; var now = DateTime.Now; if (string.IsNullOrEmpty(request.Content)) { return new BaseResponse(ResponseCode.Error, "评论内容不能为空"); } // 0. 过滤敏感词 request.Content = _sensitiveWordService.Filter(request.Content); if (string.IsNullOrEmpty(request.Content.Replace("*", ""))) { return new BaseResponse(ResponseCode.Error, "评论内容包含敏感词"); } // 1. 验证帖子是否存在 var post = await _postsRepository.Select .Where(x => x.Id == request.PostId) .FirstAsync(); if (post == null) { return new BaseResponse(ResponseCode.Error, "帖子不存在"); } if (post.IsDeleted) { return new BaseResponse(ResponseCode.PostDeleted, "帖子已删除"); } // 1.1 检查帖子是否允许回复 if (!post.AllowReply) { return new BaseResponse(ResponseCode.Error, "该帖子不允许回复"); } // 1.2 回复间隔校验 var remainingSeconds = await _postReplyIntervalService.CheckReplyIntervalAsync(currentUserId); if (remainingSeconds.HasValue) { return new BaseResponse( ResponseCode.Error, $"回复过于频繁,请等待 {remainingSeconds.Value} 秒后再试"); } // 1.3 防沉迷校验 if (await _antiAddictionService.IsRestrictedAsync("Reply")) { return new BaseResponse(ResponseCode.Error, "防沉迷时间内无法操作"); } // 2. 如果是回复评论,验证父评论是否存在 if (request.ParentCommentId.HasValue && request.ParentCommentId > 0) { var parentComment = await _commentsRepository.Select .Where(x => x.Id == request.ParentCommentId.Value && !x.IsDeleted) .FirstAsync(); if (parentComment == null) { return new BaseResponse(ResponseCode.Error, "父评论不存在"); } // 验证父评论是否属于同一个帖子 if (parentComment.PostId != request.PostId) { return new BaseResponse(ResponseCode.Error, "评论帖子不匹配"); } if (request.ReplyToUserId == null || request.ReplyToUserId == 0) { request.ReplyToUserId = parentComment.UserId; } } // 4. 创建评论 var comment = new T_Comments { PostId = request.PostId, UserId = currentUserId, ParentCommentId = request.ParentCommentId, ReplyToUserId = request.ReplyToUserId, Content = request.Content, LikeCount = 0, CreatedAt = now, UpdatedAt = now }; await _commentsRepository.InsertAsync(comment); var commentCount = await _commentsRepository.Where(it => !it.IsDeleted && it.PostId == request.PostId).CountAsync(); // 5. 更新帖子的评论数 post.CommentCount = (int)commentCount; await _postsRepository.UpdateAsync(post); // 6. 发送回复消息 try { if (request.ParentCommentId.HasValue && request.ParentCommentId.Value > 0) { // 回复评论场景 var parentComment = await _commentsRepository.Select .Where(x => x.Id == request.ParentCommentId.Value) .FirstAsync(); if (parentComment != null && parentComment.UserId != currentUserId) { await _messagePublisher.SendReplyMessageAsync( triggerId: currentUserId, receiverId: parentComment.UserId, commentId: comment.Id, commentContent: comment.Content, postId: post.Id, postTitle: post.Title, parentCommentId: parentComment.Id, myCommentContent: parentComment.Content // 传入被回复的评论内容 ); } } else if (post.UserId != currentUserId) { // 直接评论帖子场景 await _messagePublisher.SendReplyMessageAsync( triggerId: currentUserId, receiverId: post.UserId, commentId: comment.Id, commentContent: comment.Content, postId: post.Id, postTitle: post.Title, parentCommentId: null, myCommentContent: null ); } } catch (Exception ex) { // 记录日志但不影响评论发布 Console.WriteLine($"发送回复消息失败: {ex.Message}"); } // 7. 构建返回数据 var replyInterval = await _postReplyIntervalService.GetReplyIntervalAsync(currentUserId); var result = new PublishPostCommentsRespDto { CommentId = comment.Id, PostId = comment.PostId, Content = comment.Content, CreatedAt = comment.CreatedAt, ReplyInterval = replyInterval }; return new BaseResponse("发送评论成功!", result); } /// /// 删除评论 /// /// 请求参数 /// public async Task DeletePostComments(DeletePostCommentsReq request) { var currentUserId = (long)_userInfoModel.UserId; // 1. 获取评论信息 var comment = await _commentsRepository.Select .Where(x => x.Id == request.CommentId && x.UserId == currentUserId && !x.IsDeleted) .FirstAsync(); if (comment == null) { return new BaseResponseBool { Code = ResponseCode.Error, Message = "评论不存在或无权限删除" }; } // 2. 软删除评论 comment.IsDeleted = true; comment.DeletedAt = DateTime.Now; await _commentsRepository.UpdateAsync(comment); // 3. 更新帖子的评论数 var post = await _postsRepository.Select .Where(x => x.Id == comment.PostId) .FirstAsync(); if (post != null) { post.CommentCount = Math.Max(0, post.CommentCount - 1); await _postsRepository.UpdateAsync(post); } return new BaseResponseBool { Code = ResponseCode.Success, Data = true, Message = "删除帖子成功!" }; } /// /// 点赞/取消点赞评论 /// /// 请求参数 /// public async Task> LikeComment(LikeCommentReq request) { var currentUserId = (long)_userInfoModel.UserId; // 1. 检查评论是否存在 var comment = await _commentsRepository.Select .Where(x => x.Id == request.CommentId && !x.IsDeleted) .FirstAsync(); if (comment == null) { return new BaseResponse(ResponseCode.Error, "评论不存在"); } // 2. 查询当前点赞状态 var isCurrentlyLiked = await _likeService.IsLikedAsync(currentUserId, 2, request.CommentId); bool isLiked; if (isCurrentlyLiked) { // 取消点赞 await _likeService.UnlikeAsync(currentUserId, 2, request.CommentId); isLiked = false; } else { // 添加点赞 await _likeService.LikeAsync(currentUserId, 2, request.CommentId, comment.UserId); isLiked = true; } // 3. 获取最新点赞数 var likeCount = await _likeService.GetLikeCountAsync(2, request.CommentId); // 4. 发送点赞消息(如果是点赞且不是自己的评论) if (isLiked && comment.UserId != currentUserId) { try { // 获取帖子信息 var post = await _postsRepository.Select .Where(x => x.Id == comment.PostId) .FirstAsync(); if (post != null) { await _messagePublisher.SendLikeMessageAsync( triggerId: currentUserId, receiverId: comment.UserId, contentType: 2, // 评论类型 contentId: comment.Id, postTitle: post.Title, commentContent: comment.Content ); } } catch (Exception ex) { // 记录日志但不影响点赞操作 Console.WriteLine($"发送评论点赞消息失败: {ex.Message}"); } } // 5. 构建返回数据 var result = new LikeCommentRespDto { CommentId = comment.Id, IsLiked = isLiked, LikeCount = likeCount }; return new BaseResponse(isLiked ? "点赞成功!" : "取消点赞成功!", result); } } }