live-forum/server/webapi/LiveForum/LiveForum.Service/Messages/MessagesService.cs
2026-03-24 11:27:37 +08:00

1320 lines
55 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using FreeSql;
using LiveForum.Code.Base;
using LiveForum.Code.JwtInfrastructure;
using LiveForum.IService.Messages;
using LiveForum.IService.Others;
using LiveForum.IService.Users;
using LiveForum.Model;
using LiveForum.Model.Dto.Messages;
using LiveForum.Model.Dto.Users;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LiveForum.Service.Messages
{
public class MessagesService : IMessagesService
{
private readonly JwtUserInfoModel _userInfoModel;
private readonly IBaseRepository<T_Messages> _messagesRepository;
private readonly IBaseRepository<T_SystemNotifications> _notificationsRepository;
private readonly IBaseRepository<T_Users> _usersRepository;
private readonly IBaseRepository<T_Comments> _commentsRepository;
private readonly IBaseRepository<T_Posts> _postsRepository;
private readonly IBaseRepository<T_Likes> _likesRepository;
private readonly IBaseRepository<T_Follows> _followsRepository;
private readonly IBaseRepository<T_SystemNotificationParticipants> _participantsRepository;
private readonly IUserInfoService _userInfoService;
private readonly IFreeSql _freeSql;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="userInfoModel">JWT用户信息模型</param>
/// <param name="messagesRepository">消息仓储</param>
/// <param name="notificationsRepository">系统通知仓储</param>
/// <param name="usersRepository">用户仓储</param>
/// <param name="commentsRepository">评论仓储</param>
/// <param name="postsRepository">帖子仓储</param>
/// <param name="likesRepository">点赞仓储</param>
/// <param name="followsRepository">关注仓储</param>
/// <param name="participantsRepository">系统通知参与记录仓储</param>
/// <param name="userInfoService">用户信息转换服务</param>
/// <param name="freeSql">FreeSql实例</param>
public MessagesService(
JwtUserInfoModel userInfoModel,
IBaseRepository<T_Messages> messagesRepository,
IBaseRepository<T_SystemNotifications> notificationsRepository,
IBaseRepository<T_Users> usersRepository,
IBaseRepository<T_Comments> commentsRepository,
IBaseRepository<T_Posts> postsRepository,
IBaseRepository<T_Likes> likesRepository,
IBaseRepository<T_Follows> followsRepository,
IBaseRepository<T_SystemNotificationParticipants> participantsRepository,
IUserInfoService userInfoService,
IFreeSql freeSql)
{
_userInfoModel = userInfoModel;
_messagesRepository = messagesRepository;
_notificationsRepository = notificationsRepository;
_usersRepository = usersRepository;
_commentsRepository = commentsRepository;
_postsRepository = postsRepository;
_likesRepository = likesRepository;
_followsRepository = followsRepository;
_participantsRepository = participantsRepository;
_userInfoService = userInfoService;
_freeSql = freeSql;
}
#region
/// <summary>
/// 尝试从快照反序列化发送者信息
/// </summary>
private UserInfoDto TryDeserializeSenderInfo(string senderInfoJson)
{
if (string.IsNullOrEmpty(senderInfoJson))
{
return null;
}
try
{
var userInfo = JsonConvert.DeserializeObject<UserInfoDto>(senderInfoJson);
return userInfo;
}
catch
{
return null;
}
}
/// <summary>
/// 尝试从快照反序列化内容信息
/// </summary>
private MessageRelatedContentDto TryDeserializeContentSnapshot(string contentSnapshotJson)
{
if (string.IsNullOrEmpty(contentSnapshotJson))
{
return null;
}
try
{
var snapshot = JsonConvert.DeserializeObject<Model.Events.ContentSnapshot>(contentSnapshotJson);
if (snapshot == null) return null;
return new MessageRelatedContentDto
{
PostId = snapshot.PostId,
PostTitle = snapshot.PostTitle,
CommentContent = snapshot.CommentContent
};
}
catch
{
return null;
}
}
/// <summary>
/// 批量回填消息快照数据
/// </summary>
private async Task BackfillMessageSnapshots(
List<T_Messages> messages,
Dictionary<long, T_Users> users,
Dictionary<long, T_Posts> posts,
Dictionary<long, T_Comments> comments)
{
var messagesToUpdate = new List<T_Messages>();
foreach (var message in messages)
{
bool needUpdate = false;
// 回填发送者信息快照
if (string.IsNullOrEmpty(message.SenderInfo) && message.SenderId.HasValue)
{
if (users.ContainsKey(message.SenderId.Value))
{
var user = users[message.SenderId.Value];
var senderInfo = await _userInfoService.ToUserInfoDtoAsync(user);
message.SenderInfo = JsonConvert.SerializeObject(senderInfo, new JsonSerializerSettings
{
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
});
needUpdate = true;
}
else
{
// 发送者已不存在,保存占位快照
var deletedUserInfo = new UserInfoDto
{
UserId = 0,
NickName = "已删除的用户",
Avatar = "",
IsVip = false,
IsCertified = false,
CertifiedType = null
};
message.SenderInfo = JsonConvert.SerializeObject(deletedUserInfo, new JsonSerializerSettings
{
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
});
needUpdate = true;
}
}
// 回填内容快照
if (string.IsNullOrEmpty(message.ContentSnapshot) && message.ContentId.HasValue)
{
var contentSnapshot = new Model.Events.ContentSnapshot();
if (message.ContentType == 1 && posts.ContainsKey(message.ContentId.Value))
{
// 帖子快照
var post = posts[message.ContentId.Value];
contentSnapshot.PostId = post.Id;
contentSnapshot.PostTitle = post.Title;
message.ContentSnapshot = JsonConvert.SerializeObject(contentSnapshot);
needUpdate = true;
}
else if (message.ContentType == 2 && comments.ContainsKey(message.ContentId.Value))
{
// 评论快照
var comment = comments[message.ContentId.Value];
contentSnapshot.CommentId = comment.Id;
contentSnapshot.CommentContent = comment.Content;
// 同时获取帖子标题
if (posts.ContainsKey(comment.PostId))
{
contentSnapshot.PostId = comment.PostId;
contentSnapshot.PostTitle = posts[comment.PostId].Title;
}
message.ContentSnapshot = JsonConvert.SerializeObject(contentSnapshot);
needUpdate = true;
}
else if (!posts.ContainsKey(message.ContentId.Value) && !comments.ContainsKey(message.ContentId.Value))
{
// 内容已被删除,保存占位快照
contentSnapshot.PostTitle = "[已删除]";
contentSnapshot.CommentContent = "[已删除]";
message.ContentSnapshot = JsonConvert.SerializeObject(contentSnapshot);
needUpdate = true;
}
}
if (needUpdate)
{
messagesToUpdate.Add(message);
}
}
// 批量更新数据库
if (messagesToUpdate.Any())
{
await _messagesRepository.UpdateAsync(messagesToUpdate);
}
}
#endregion
/// <summary>
/// 确保系统通知消息已创建
/// 查询未创建消息记录的系统通知,并根据目标用户类型筛选,为用户创建消息记录
/// </summary>
/// <param name="userId">用户ID</param>
private async Task EnsureSystemNotificationsCreated(long userId)
{
var now = DateTime.Now;
// 查询未创建消息记录的系统通知
// SQL: select n.* from T_SystemNotifications n
// left join T_Messages m on n.Id=m.SenderId and m.MessageType=3 and m.ReceiverId=userId
// where m.Id is null and (ExpireTime>NOW() or ExpireTime is null) and PublishTime<=NOW()
var allNotificationsWithoutMessages = _freeSql.Select<T_SystemNotifications, T_Messages>()
.LeftJoin((n, m) => n.Id == m.SenderId && m.MessageType == 3 && m.ReceiverId == userId)
.Where((n, m) => m == null && (n.ExpireTime > now || n.ExpireTime == null) && n.PublishTime <= now && n.IsActive)
.ToList((a, b) => a);
if (!allNotificationsWithoutMessages.Any())
{
return;
}
// 根据目标用户类型筛选通知
var notificationsForUser = new List<T_SystemNotifications>();
foreach (var notification in allNotificationsWithoutMessages)
{
// TargetUserType: null或0-所有用户1-指定用户列表2-按认证类型筛选
if (notification.TargetUserType == null || notification.TargetUserType == 0)
{
// 所有用户
notificationsForUser.Add(notification);
}
else if (notification.TargetUserType == 1)
{
// 指定用户列表
if (!string.IsNullOrEmpty(notification.TargetUserIds))
{
try
{
var targetUserIds = JsonConvert.DeserializeObject<List<long>>(notification.TargetUserIds);
if (targetUserIds != null && targetUserIds.Contains(userId))
{
notificationsForUser.Add(notification);
}
}
catch
{
// JSON解析失败跳过该通知
}
}
}
else if (notification.TargetUserType == 2)
{
// 按认证类型筛选
if (notification.TargetCertifiedType.HasValue)
{
var user = await _usersRepository.Select.Where(u => u.Id == userId).FirstAsync();
if (user != null)
{
// 直接比较认证类型ID
if (user.CertifiedType.HasValue && user.CertifiedType.Value == notification.TargetCertifiedType.Value)
{
notificationsForUser.Add(notification);
}
}
}
}
}
// 如果存在未创建消息的系统通知,需要为它们创建消息记录
if (notificationsForUser.Any())
{
var newMessages = notificationsForUser.Select(n => new T_Messages
{
ReceiverId = userId,
SenderId = n.Id, // 使用通知ID作为SenderId
MessageType = 3, // 系统消息
MessageContent = $"{n.Content}",
IsRead = false,
CreatedAt = DateTime.Now,
MessageTitle = n.Title,
}).ToList();
await _messagesRepository.InsertAsync(newMessages);
}
}
public async Task<BaseResponse<GetMessagesRespDto>> GetMessages(GetMessagesReq request)
{
var currentUserId = (long)_userInfoModel.UserId;
// 构建查询条件
var query = _messagesRepository.Select
.Where(x => x.ReceiverId == currentUserId);
// 过滤消息类型
if (request.MessageType.HasValue)
{
query = query.Where(x => x.MessageType == request.MessageType.Value);
if (request.MessageType == 3)
{
// 确保系统消息已创建
await EnsureSystemNotificationsCreated(currentUserId);
}
}
// 过滤是否已读
if (request.IsRead.HasValue)
{
query = query.Where(x => x.IsRead == request.IsRead.Value);
}
// 排序
query = query.OrderByDescending(x => x.CreatedAt);
// 获取总数
var total = await query.CountAsync();
// 分页
var messages = await query
.Skip((request.PageIndex - 1) * request.PageSize)
.Take(request.PageSize)
.ToListAsync();
if (!messages.Any())
{
// 获取未读总数
var unreadCount = await query.Where(x => !x.IsRead)
.CountAsync();
return new BaseResponse<GetMessagesRespDto>(new GetMessagesRespDto
{
PageIndex = request.PageIndex,
PageSize = request.PageSize,
Total = (int)total,
TotalPages = (int)Math.Ceiling((double)total / request.PageSize),
UnreadCount = (int)unreadCount,
Items = new List<MessageListItemDto>()
});
}
// 分类消息:有快照的和没快照的
var messagesWithSnapshot = messages.Where(x => !string.IsNullOrEmpty(x.SenderInfo) || x.MessageType == 3).ToList();
var messagesWithoutSnapshot = messages.Where(x => string.IsNullOrEmpty(x.SenderInfo) && x.MessageType != 3).ToList();
// 对于没有快照的消息收集需要查询的ID
var senderIds = messagesWithoutSnapshot.Where(x => x.SenderId.HasValue).Select(x => x.SenderId!.Value).Distinct().ToList();
var contentIds = messagesWithoutSnapshot.Where(x => x.ContentId.HasValue && string.IsNullOrEmpty(x.ContentSnapshot))
.Select(x => new { x.ContentType, x.ContentId!.Value }).ToList();
// 获取用户信息(仅查询无快照的消息需要的)
var users = new Dictionary<long, T_Users>();
if (senderIds.Any())
{
var usersList = await _usersRepository.Select
.Where(x => senderIds.Contains(x.Id))
.ToListAsync();
foreach (var user in usersList)
{
users[user.Id] = user;
}
}
// 获取帖子信息
var postIds = contentIds.Where(x => x.ContentType == 1).Select(x => x.Value).Distinct().ToList();
var posts = new Dictionary<long, T_Posts>();
if (postIds.Any())
{
var postsList = await _postsRepository.Select
.Where(x => postIds.Contains(x.Id))
.ToListAsync();
foreach (var post in postsList)
{
posts[post.Id] = post;
}
}
// 获取评论信息
var commentIds = contentIds.Where(x => x.ContentType == 2).Select(x => x.Value).Distinct().ToList();
var comments = new Dictionary<long, T_Comments>();
if (commentIds.Any())
{
var commentsList = await _commentsRepository.Select
.Where(x => commentIds.Contains(x.Id))
.ToListAsync();
foreach (var comment in commentsList)
{
comments[comment.Id] = comment;
}
// 同时获取评论关联的帖子
var commentPostIds = commentsList.Select(x => x.PostId).Where(x => !posts.ContainsKey(x)).Distinct().ToList();
if (commentPostIds.Any())
{
var commentPostsList = await _postsRepository.Select
.Where(x => commentPostIds.Contains(x.Id))
.ToListAsync();
foreach (var post in commentPostsList)
{
posts[post.Id] = post;
}
}
}
// 批量回填无快照的消息
if (messagesWithoutSnapshot.Any())
{
await BackfillMessageSnapshots(messagesWithoutSnapshot, users, posts, comments);
}
// 获取系统通知信息(仅当有系统消息时)
var systemNotificationIds = messages.Where(x => x.MessageType == 3 && x.SenderId.HasValue).Select(x => x.SenderId!.Value).Distinct().ToList();
var systemNotifications = new Dictionary<long, T_SystemNotifications>();
if (systemNotificationIds.Any())
{
var notificationsList = await _notificationsRepository.Select
.Where(x => systemNotificationIds.Contains(x.Id))
.ToListAsync();
foreach (var notification in notificationsList)
{
systemNotifications[notification.Id] = notification;
}
}
// 获取用户参与状态(仅当有活动通知时)
var activityNotificationIds = systemNotifications.Values
.Where(n => n.NotificationType == 2)
.Select(n => n.Id)
.ToList();
var participants = new Dictionary<long, T_SystemNotificationParticipants>();
if (activityNotificationIds.Any())
{
var participantsList = await _participantsRepository.Select
.Where(x => activityNotificationIds.Contains(x.NotificationId) && x.UserId == currentUserId)
.ToListAsync();
foreach (var participant in participantsList)
{
participants[participant.NotificationId] = participant;
}
}
var unreadCountTotal = await _messagesRepository.Select
.Where(x => x.ReceiverId == currentUserId && !x.IsRead)
.CountAsync();
// 批量转换用户信息(自动获取认证类型数据)
var usersDict = await _userInfoService.ToUserInfoDtoDictionaryAsync(users.Values);
// 构建返回数据
var items = messages.Select(message =>
{
// 优先使用快照,如果没有快照则使用实时查询的数据
UserInfoDto sender = null;
if (message.MessageType != 3)
{
// 先尝试从快照反序列化(非系统消息)
sender = TryDeserializeSenderInfo(message.SenderInfo);
// 如果快照为空,尝试从实时数据获取
if (sender == null && message.SenderId.HasValue && usersDict.ContainsKey(message.SenderId.Value))
{
sender = usersDict[message.SenderId.Value];
}
}
sender ??= new UserInfoDto();
// 优先使用内容快照
var relatedContent = TryDeserializeContentSnapshot(message.ContentSnapshot);
// 如果快照为空,尝试从实时数据获取
if (relatedContent == null && message.ContentId.HasValue)
{
relatedContent = new MessageRelatedContentDto();
if (message.ContentType == 1 && posts.ContainsKey(message.ContentId.Value))
{
var post = posts[message.ContentId.Value];
relatedContent.PostId = post.Id;
relatedContent.PostTitle = post.Title;
}
else if (message.ContentType == 2 && comments.ContainsKey(message.ContentId.Value))
{
var comment = comments[message.ContentId.Value];
relatedContent.CommentContent = comment.Content;
if (posts.ContainsKey(comment.PostId))
{
relatedContent.PostId = comment.PostId;
relatedContent.PostTitle = posts[comment.PostId].Title;
}
}
}
relatedContent ??= new MessageRelatedContentDto();
// 组装活动信息(仅当系统消息为活动类型时)
ActivityInfoDto? activityInfo = null;
if (message.MessageType == 3 && message.SenderId.HasValue && systemNotifications.ContainsKey(message.SenderId.Value))
{
var notification = systemNotifications[message.SenderId.Value];
if (notification.NotificationType == 2)
{
var isParticipated = participants.ContainsKey(notification.Id);
if (isParticipated)
{
var _participants = participants[notification.Id];
var status = _participants?.Status ?? 0;
if (notification?.ActivityEndTime != null && notification?.ActivityEndTime < DateTime.Now)
{
//活动已经结束
status = 3;
}
activityInfo = new ActivityInfoDto
{
IsActivity = true,
ActivityEndTime = notification.ActivityEndTime,
ActivityButtonText = "已提交",
IsParticipated = isParticipated,
ParticipatedAt = _participants.SubmittedAt,
ContactInfo = _participants.ContactInfo ?? "",
ShippingAddress = _participants.ShippingAddress ?? "",
Status = status
};
}
else
{
activityInfo = new ActivityInfoDto
{
IsActivity = true,
ActivityEndTime = notification.ActivityEndTime,
ActivityButtonText = notification.ActivityButtonText ?? "参与活动",
IsParticipated = isParticipated,
ParticipatedAt = null,
ContactInfo = "",
ShippingAddress = "",
Status = 0
};
}
}
}
return new MessageListItemDto
{
MessageId = message.Id,
MessageType = message.MessageType,
MessageTitle = message.MessageTitle ?? "",
MessageContent = message.MessageContent ?? "",
ContentType = message.ContentType,
ContentId = message.ContentId ?? 0,
IsRead = message.IsRead,
CreatedAt = message.CreatedAt,
Sender = sender,
RelatedContent = relatedContent,
ActivityInfo = activityInfo
};
}).ToList();
return new BaseResponse<GetMessagesRespDto>(new GetMessagesRespDto
{
PageIndex = request.PageIndex,
PageSize = request.PageSize,
Total = (int)total,
TotalPages = (int)Math.Ceiling((double)total / request.PageSize),
UnreadCount = (int)unreadCountTotal,
Items = items
});
}
public async Task<BaseResponse<GetSystemNotificationsRespDto>> GetSystemNotifications(GetSystemNotificationsReq request)
{
var currentUserId = (long)_userInfoModel.UserId;
var now = DateTime.Now;
// 构建查询条件
var query = _notificationsRepository.Select
.Where(x => x.IsActive &&
(x.ExpireTime == null || x.ExpireTime > now) &&
(x.PublishTime == null || x.PublishTime <= now));
// 过滤通知类型
//if (request.NotificationType.HasValue)
//{
// query = query.Where(x => x.NotificationType == request.NotificationType.Value);
//}
// 排序
query = query.OrderByDescending(x => x.PublishTime ?? x.CreatedAt);
// 获取总数
var total = await query.CountAsync();
// 分页
var notifications = await query
.Skip((request.PageIndex - 1) * request.PageSize)
.Take(request.PageSize)
.ToListAsync();
var items = notifications.Select(n => new SystemNotificationDto
{
NotificationId = n.Id,
Title = n.Title,
Content = n.Content,
//NotificationType = n.NotificationType,
PublishTime = n.PublishTime ?? n.CreatedAt,
ExpireTime = n.ExpireTime
}).ToList();
return new BaseResponse<GetSystemNotificationsRespDto>(new GetSystemNotificationsRespDto
{
PageIndex = request.PageIndex,
PageSize = request.PageSize,
Total = (int)total,
TotalPages = (int)Math.Ceiling((double)total / request.PageSize),
Items = items
});
}
public async Task<BaseResponse<ReadMessageRespDto>> ReadMessage(ReadMessageReq request)
{
var currentUserId = (long)_userInfoModel.UserId;
// 获取消息
var message = await _messagesRepository.Select
.Where(x => x.Id == request.MessageId && x.ReceiverId == currentUserId)
.FirstAsync();
if (message == null)
{
return new BaseResponse<ReadMessageRespDto>(ResponseCode.Error, "消息不存在或无权限访问");
}
// 标记为已读
message.IsRead = true;
message.ReadAt = DateTime.Now;
await _messagesRepository.UpdateAsync(message);
return new BaseResponse<ReadMessageRespDto>(new ReadMessageRespDto
{
MessageId = message.Id,
IsRead = true,
ReadAt = message.ReadAt!.Value
});
}
public async Task<BaseResponse<ReadBatchMessagesRespDto>> ReadBatchMessages(ReadBatchMessagesReq request)
{
var currentUserId = (long)_userInfoModel.UserId;
if (request.MessageIds == null || !request.MessageIds.Any())
{
return new BaseResponse<ReadBatchMessagesRespDto>(ResponseCode.Error, "消息ID列表不能为空");
}
if (request.MessageIds.Count > 100)
{
return new BaseResponse<ReadBatchMessagesRespDto>(ResponseCode.Error, "批量操作数量超过限制");
}
var now = DateTime.Now;
// 更新消息状态
var result = await _messagesRepository.UpdateDiy
.Set(x => x.IsRead == true)
.Set(x => x.ReadAt == now)
.Where(x => x.ReceiverId == currentUserId && request.MessageIds.Contains(x.Id))
.ExecuteAffrowsAsync();
return new BaseResponse<ReadBatchMessagesRespDto>(new ReadBatchMessagesRespDto
{
SuccessCount = (int)result,
ReadAt = now
});
}
public async Task<BaseResponse<ReadAllMessagesRespDto>> ReadAllMessages(ReadAllMessagesReq request)
{
var currentUserId = (long)_userInfoModel.UserId;
var now = DateTime.Now;
// 构建查询条件
ISelect<T_Messages> query = _messagesRepository.Select
.Where(x => x.ReceiverId == currentUserId && !x.IsRead);
// 如果指定了消息类型
if (request.MessageType.HasValue)
{
query = query.Where(x => x.MessageType == request.MessageType.Value);
}
// 获取未读消息
var messages = await query.ToListAsync();
if (!messages.Any())
{
return new BaseResponse<ReadAllMessagesRespDto>(new ReadAllMessagesRespDto
{
SuccessCount = 0,
ReadAt = now
});
}
// 批量更新消息
foreach (var message in messages)
{
message.IsRead = true;
message.ReadAt = now;
}
await _messagesRepository.UpdateAsync(messages);
return new BaseResponse<ReadAllMessagesRespDto>(new ReadAllMessagesRespDto
{
SuccessCount = messages.Count,
ReadAt = now
});
}
public async Task<BaseResponseBool> DeleteMessage(DeleteMessageReq request)
{
var currentUserId = (long)_userInfoModel.UserId;
// 检查消息是否存在且属于当前用户
var message = await _messagesRepository.Select
.Where(x => x.Id == request.MessageId && x.ReceiverId == currentUserId)
.FirstAsync();
if (message == null)
{
return new BaseResponseBool { Code = ResponseCode.Error, Message = "消息不存在或无权限删除" };
}
// 软删除消息(设置为已读并标记为已删除,或者物理删除)
await _messagesRepository.DeleteAsync(message);
return new BaseResponseBool { Code = ResponseCode.Success, Data = true };
}
public async Task<BaseResponse<UnreadCountRespDto>> GetUnreadCount()
{
var currentUserId = (long)_userInfoModel.UserId;
// 确保系统通知消息已创建,以便统计未读数量
await EnsureSystemNotificationsCreated(currentUserId);
var result = await _messagesRepository.Select
.Where(x => x.ReceiverId == currentUserId && !x.IsRead)
.ToListAsync(x => new { x.MessageType });
var totalUnread = result.Count;
var commentReplyUnread = result.Count(x => x.MessageType == 1);
var likeUnread = result.Count(x => x.MessageType == 2);
var systemUnread = result.Count(x => x.MessageType == 3);
return new BaseResponse<UnreadCountRespDto>(new UnreadCountRespDto
{
TotalUnread = totalUnread,
CommentReplyUnread = commentReplyUnread,
LikeUnread = likeUnread,
SystemUnread = systemUnread
});
}
public async Task<BaseResponse<GetCommentRepliesMessagesRespDto>> GetCommentRepliesMessages(GetCommentRepliesMessagesReq request)
{
var currentUserId = (long)_userInfoModel.UserId;
// 构建查询条件 - 只获取评论回复消息
var query = _messagesRepository.Select
.Where(x => x.ReceiverId == currentUserId && x.MessageType == 1);
// 过滤是否已读
if (request.IsRead.HasValue)
{
query = query.Where(x => x.IsRead == request.IsRead.Value);
}
// 排序
query = query.OrderByDescending(x => x.CreatedAt);
// 获取总数
var total = await query.CountAsync();
// 分页
var messages = await query
.Skip((request.PageIndex - 1) * request.PageSize)
.Take(request.PageSize)
.ToListAsync();
if (!messages.Any())
{
var unreadCount = await _messagesRepository.Select
.Where(x => x.ReceiverId == currentUserId && x.MessageType == 1 && !x.IsRead)
.CountAsync();
return new BaseResponse<GetCommentRepliesMessagesRespDto>(new GetCommentRepliesMessagesRespDto
{
PageIndex = request.PageIndex,
PageSize = request.PageSize,
Total = (int)total,
TotalPages = (int)Math.Ceiling((double)total / request.PageSize),
UnreadCount = (int)unreadCount,
Items = new List<CommentReplyMessageDto>()
});
}
// 分类消息:有快照的和没快照的
var messagesWithoutSnapshot = messages.Where(x => string.IsNullOrEmpty(x.SenderInfo) || string.IsNullOrEmpty(x.ContentSnapshot)).ToList();
// 收集需要查询的ID仅针对无快照的消息
var senderIds = messagesWithoutSnapshot.Where(x => x.SenderId.HasValue && string.IsNullOrEmpty(x.SenderInfo))
.Select(x => x.SenderId!.Value).Distinct().ToList();
var commentIds = messagesWithoutSnapshot.Where(x => x.ContentType == 2 && x.ContentId.HasValue && string.IsNullOrEmpty(x.ContentSnapshot))
.Select(x => x.ContentId!.Value).ToList();
// 获取用户信息
var users = new Dictionary<long, T_Users>();
if (senderIds.Any())
{
var usersList = await _usersRepository.Select
.Where(x => senderIds.Contains(x.Id))
.ToListAsync();
foreach (var user in usersList)
{
users[user.Id] = user;
}
}
// 获取评论信息
var comments = new Dictionary<long, T_Comments>();
var posts = new Dictionary<long, T_Posts>();
if (commentIds.Any())
{
var commentsList = await _commentsRepository.Select
.Where(x => commentIds.Contains(x.Id))
.ToListAsync();
foreach (var comment in commentsList)
{
comments[comment.Id] = comment;
if (!posts.ContainsKey(comment.PostId))
{
var post = await _postsRepository.Select
.Where(x => x.Id == comment.PostId)
.FirstAsync();
if (post != null)
{
posts[comment.PostId] = post;
}
}
}
}
// 获取当前用户被回复的评论(父评论)
var myCommentIds = new List<long>();
foreach (var message in messagesWithoutSnapshot)
{
if (message.ContentType == 2 && message.ContentId.HasValue)
{
if (comments.ContainsKey(message.ContentId.Value))
{
var comment = comments[message.ContentId.Value];
if (comment.ParentCommentId.HasValue && !myCommentIds.Contains(comment.ParentCommentId.Value))
{
myCommentIds.Add(comment.ParentCommentId.Value);
}
}
}
}
var myComments = new Dictionary<long, T_Comments>();
if (myCommentIds.Any())
{
var myCommentsList = await _commentsRepository.Select
.Where(x => myCommentIds.Contains(x.Id))
.ToListAsync();
foreach (var comment in myCommentsList)
{
myComments[comment.Id] = comment;
}
}
// 批量回填无快照的消息
if (messagesWithoutSnapshot.Any())
{
await BackfillMessageSnapshots(messagesWithoutSnapshot, users, posts, comments);
}
var unreadCountTotal = await _messagesRepository.Select
.Where(x => x.ReceiverId == currentUserId && x.MessageType == 1 && !x.IsRead)
.CountAsync();
// 批量转换用户信息(自动获取认证类型数据)
var usersDict = await _userInfoService.ToUserInfoDtoDictionaryAsync(users.Values);
// 构建返回数据
var items = messages.Select(message =>
{
// 优先使用快照
var sender = TryDeserializeSenderInfo(message.SenderInfo);
// 如果快照为空,使用实时数据
if (sender == null && message.SenderId.HasValue && usersDict.ContainsKey(message.SenderId.Value))
{
sender = usersDict[message.SenderId.Value];
}
sender ??= new UserInfoDto();
// 优先从快照获取评论信息
var contentSnapshot = TryDeserializeContentSnapshot(message.ContentSnapshot);
var commentDto = new CommentInfoDto();
var myCommentDto = new MyCommentInfoDto();
if (contentSnapshot != null)
{
// 使用快照数据
commentDto = new CommentInfoDto
{
//CommentId = contentSnapshot.Id ?? 0,
Content = contentSnapshot.CommentContent ?? "",
PostId = contentSnapshot.PostId ?? 0,
PostTitle = contentSnapshot.PostTitle ?? ""
};
// 父评论信息也在快照中ContentSnapshot 中的 CommentContent 是父评论)
myCommentDto = new MyCommentInfoDto
{
CommentId = 0, // 快照中不保存父评论ID
Content = "" // 需要单独查询或从消息内容中提取
};
}
else if (message.ContentType == 2 && message.ContentId.HasValue)
{
// 使用实时数据
var comment = comments.ContainsKey(message.ContentId.Value)
? comments[message.ContentId.Value]
: null;
var myComment = comment != null && comment.ParentCommentId.HasValue && myComments.ContainsKey(comment.ParentCommentId.Value)
? myComments[comment.ParentCommentId.Value]
: null;
commentDto = comment != null
? new CommentInfoDto
{
CommentId = comment.Id,
Content = comment.Content,
PostId = comment.PostId,
PostTitle = posts.ContainsKey(comment.PostId) ? posts[comment.PostId].Title : ""
}
: new CommentInfoDto
{
CommentId = 0,
Content = "",
PostId = 0,
PostTitle = ""
};
myCommentDto = myComment != null
? new MyCommentInfoDto
{
CommentId = myComment.Id,
Content = myComment.Content
}
: new MyCommentInfoDto
{
CommentId = 0,
Content = ""
};
}
return new CommentReplyMessageDto
{
MessageId = message.Id,
IsRead = message.IsRead,
CreatedAt = message.CreatedAt,
Sender = sender,
Comment = commentDto,
MyComment = myCommentDto
};
}).ToList();
return new BaseResponse<GetCommentRepliesMessagesRespDto>(new GetCommentRepliesMessagesRespDto
{
PageIndex = request.PageIndex,
PageSize = request.PageSize,
Total = (int)total,
TotalPages = (int)Math.Ceiling((double)total / request.PageSize),
UnreadCount = (int)unreadCountTotal,
Items = items
});
}
public async Task<BaseResponse<GetLikesMessagesRespDto>> GetLikesMessages(GetLikesMessagesReq request)
{
var currentUserId = (long)_userInfoModel.UserId;
// 构建查询条件 - 只获取点赞消息
var query = _messagesRepository.Select
.Where(x => x.ReceiverId == currentUserId && x.MessageType == 2);
// 过滤是否已读
if (request.IsRead.HasValue)
{
query = query.Where(x => x.IsRead == request.IsRead.Value);
}
// 排序
query = query.OrderByDescending(x => x.CreatedAt);
// 获取总数
var total = await query.CountAsync();
// 分页
var messages = await query
.Skip((request.PageIndex - 1) * request.PageSize)
.Take(request.PageSize)
.ToListAsync();
if (!messages.Any())
{
var unreadCount = await _messagesRepository.Select
.Where(x => x.ReceiverId == currentUserId && x.MessageType == 2 && !x.IsRead)
.CountAsync();
return new BaseResponse<GetLikesMessagesRespDto>(new GetLikesMessagesRespDto
{
PageIndex = request.PageIndex,
PageSize = request.PageSize,
Total = (int)total,
TotalPages = (int)Math.Ceiling((double)total / request.PageSize),
UnreadCount = (int)unreadCount,
Items = new List<LikeMessageDto>()
});
}
// 分类消息:有快照的和没快照的
var messagesWithoutSnapshot = messages.Where(x => string.IsNullOrEmpty(x.SenderInfo) || string.IsNullOrEmpty(x.ContentSnapshot)).ToList();
// 收集需要查询的ID
var senderIds = messagesWithoutSnapshot.Where(x => x.SenderId.HasValue && string.IsNullOrEmpty(x.SenderInfo))
.Select(x => x.SenderId!.Value).Distinct().ToList();
var contentIds = messagesWithoutSnapshot.Where(x => x.ContentId.HasValue && string.IsNullOrEmpty(x.ContentSnapshot))
.Select(x => new { x.ContentType, x.ContentId!.Value }).ToList();
// 获取用户信息
var users = new Dictionary<long, T_Users>();
if (senderIds.Any())
{
var usersList = await _usersRepository.Select
.Where(x => senderIds.Contains(x.Id))
.ToListAsync();
foreach (var user in usersList)
{
users[user.Id] = user;
}
}
// 获取帖子信息
var postIds = contentIds.Where(x => x.ContentType == 1).Select(x => x.Value).Distinct().ToList();
var posts = new Dictionary<long, T_Posts>();
if (postIds.Any())
{
var postsList = await _postsRepository.Select
.Where(x => postIds.Contains(x.Id))
.ToListAsync();
foreach (var post in postsList)
{
posts[post.Id] = post;
}
}
// 获取评论信息
var commentIds = contentIds.Where(x => x.ContentType == 2).Select(x => x.Value).Distinct().ToList();
var comments = new Dictionary<long, T_Comments>();
if (commentIds.Any())
{
var commentsList = await _commentsRepository.Select
.Where(x => commentIds.Contains(x.Id))
.ToListAsync();
foreach (var comment in commentsList)
{
comments[comment.Id] = comment;
}
// 获取评论对应的帖子
var commentPostIds = commentsList.Select(x => x.PostId).Where(x => !posts.ContainsKey(x)).Distinct().ToList();
if (commentPostIds.Any())
{
var commentPostsList = await _postsRepository.Select
.Where(x => commentPostIds.Contains(x.Id))
.ToListAsync();
foreach (var post in commentPostsList)
{
posts[post.Id] = post;
}
}
}
// 批量回填无快照的消息
if (messagesWithoutSnapshot.Any())
{
await BackfillMessageSnapshots(messagesWithoutSnapshot, users, posts, comments);
}
var unreadCountTotal = await _messagesRepository.Select
.Where(x => x.ReceiverId == currentUserId && x.MessageType == 2 && !x.IsRead)
.CountAsync();
// 批量转换用户信息(自动获取认证类型数据)
var usersDict = await _userInfoService.ToUserInfoDtoDictionaryAsync(users.Values);
// 构建返回数据
var items = messages.Select(message =>
{
// 优先使用快照
var sender = TryDeserializeSenderInfo(message.SenderInfo);
// 如果快照为空,使用实时数据
if (sender == null && message.SenderId.HasValue && usersDict.ContainsKey(message.SenderId.Value))
{
sender = usersDict[message.SenderId.Value];
}
sender ??= new UserInfoDto();
// 优先从快照获取内容信息
var contentSnapshot = TryDeserializeContentSnapshot(message.ContentSnapshot);
var likedContent = new LikedContentDto
{
ContentType = message.ContentType,
ContentId = message.ContentId ?? 0
};
if (contentSnapshot != null)
{
// 使用快照数据
likedContent.PostTitle = contentSnapshot.PostTitle;
likedContent.CommentContent = contentSnapshot.CommentContent;
}
else if (message.ContentId.HasValue)
{
// 使用实时数据
if (message.ContentType == 1 && posts.ContainsKey(message.ContentId.Value))
{
likedContent.PostTitle = posts[message.ContentId.Value].Title;
}
else if (message.ContentType == 2 && comments.ContainsKey(message.ContentId.Value))
{
likedContent.CommentContent = comments[message.ContentId.Value].Content;
// 评论的帖子标题需要额外查询
var comment = comments[message.ContentId.Value];
if (posts.ContainsKey(comment.PostId))
{
likedContent.PostTitle = posts[comment.PostId].Title;
}
}
}
return new LikeMessageDto
{
MessageId = message.Id,
IsRead = message.IsRead,
CreatedAt = message.CreatedAt,
Sender = sender,
LikedContent = likedContent
};
}).ToList();
return new BaseResponse<GetLikesMessagesRespDto>(new GetLikesMessagesRespDto
{
PageIndex = request.PageIndex,
PageSize = request.PageSize,
Total = (int)total,
TotalPages = (int)Math.Ceiling((double)total / request.PageSize),
UnreadCount = (int)unreadCountTotal,
Items = items
});
}
public async Task<BaseResponse<ParticipateActivityResp>> ParticipateActivity(ParticipateActivityReq request)
{
var currentUserId = (long)_userInfoModel.UserId;
var now = DateTime.Now;
// 验证通知是否存在且为活动类型
var message = await _messagesRepository.Select.Where(it => it.Id == request.NotificationId).FirstAsync();
if (message == null || message.SenderId == null)
{
return new BaseResponse<ParticipateActivityResp>(ResponseCode.Error, "消息不存在或已删除");
}
var notification = await _notificationsRepository.Select
.Where(x => x.Id == message.SenderId && x.IsActive)
.FirstAsync();
if (notification == null)
{
return new BaseResponse<ParticipateActivityResp>(ResponseCode.Error, "活动通知不存在或已禁用");
}
if (notification.NotificationType != 2)
{
return new BaseResponse<ParticipateActivityResp>(ResponseCode.Error, "该通知不是活动类型");
}
// 验证活动是否已过期
if (notification.ActivityEndTime.HasValue && notification.ActivityEndTime.Value < now)
{
return new BaseResponse<ParticipateActivityResp>(ResponseCode.Error, "活动已结束");
}
// 验证活动是否已发布
if (notification.PublishTime.HasValue && notification.PublishTime.Value > now)
{
return new BaseResponse<ParticipateActivityResp>(ResponseCode.Error, "活动尚未开始");
}
// 验证用户是否已参与(防止重复提交)
var existingParticipant = await _participantsRepository.Select
.Where(x => x.NotificationId == notification.Id && x.UserId == currentUserId)
.FirstAsync();
if (existingParticipant != null && existingParticipant.Status == 3)
{
return new BaseResponse<ParticipateActivityResp>(ResponseCode.Error, "活动奖品已发货,无法修改地址!");
}
// 验证必填字段
if (string.IsNullOrWhiteSpace(request.ContactInfo))
{
return new BaseResponse<ParticipateActivityResp>(ResponseCode.Error, "联系方式不能为空");
}
if (string.IsNullOrWhiteSpace(request.ShippingAddress))
{
return new BaseResponse<ParticipateActivityResp>(ResponseCode.Error, "收货地址不能为空");
}
// 验证字段长度
if (request.ContactInfo.Length > 100)
{
return new BaseResponse<ParticipateActivityResp>(ResponseCode.Error, "联系方式长度不能超过100字符");
}
if (request.ShippingAddress.Length > 500)
{
return new BaseResponse<ParticipateActivityResp>(ResponseCode.Error, "收货地址长度不能超过500字符");
}
// 判断是新增还是更新
var isUpdate = existingParticipant != null;
if (existingParticipant == null)
{
// 创建新的参与记录
existingParticipant = new T_SystemNotificationParticipants
{
NotificationId = notification.Id,
UserId = currentUserId,
ContactInfo = request.ContactInfo,
ShippingAddress = request.ShippingAddress,
Status = 1, // 已提交
SubmittedAt = now,
CreatedAt = now,
UpdatedAt = now,
SenerId = message.Id,
SenerTitle = notification.Title,
};
}
else
{
// 更新现有参与记录(保留 Id、CreatedAt、SubmittedAt 等字段)
existingParticipant.ContactInfo = request.ContactInfo;
existingParticipant.ShippingAddress = request.ShippingAddress;
existingParticipant.Status = 1; // 已提交
existingParticipant.UpdatedAt = now;
existingParticipant.SenerId = message.Id;
existingParticipant.SenerTitle = notification.Title;
// 注意:不更新 SubmittedAt保持首次提交时间
}
await _participantsRepository.InsertOrUpdateAsync(existingParticipant);
return new BaseResponse<ParticipateActivityResp>(isUpdate ? "修改成功!" : "提交成功!", new ParticipateActivityResp
{
ParticipantId = existingParticipant.Id,
SubmittedAt = existingParticipant.SubmittedAt
});
}
}
}