提交
This commit is contained in:
@@ -1,53 +1,53 @@
|
||||
using AutoMapper;
|
||||
using IM_API.Dtos.Auth;
|
||||
using IM_API.Dtos.User;
|
||||
using IM_API.Exceptions;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly ImContext _context;
|
||||
private readonly ILogger<AuthService> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ICacheService _cache;
|
||||
public AuthService(ImContext context, ILogger<AuthService> logger, IMapper mapper, ICacheService cache)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_mapper = mapper;
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public async Task<User> LoginAsync(LoginRequestDto dto)
|
||||
{
|
||||
var userinfo = await _cache.GetUserCacheAsync(dto.Username);
|
||||
if (userinfo != null && userinfo.Password == dto.Password) return userinfo;
|
||||
string username = dto.Username;
|
||||
string password = dto.Password;
|
||||
var user = await _context.Users.FirstOrDefaultAsync(x => x.Username == username && x.Password == password);
|
||||
if(user is null)
|
||||
{
|
||||
throw new BaseException(CodeDefine.PASSWORD_ERROR);
|
||||
}
|
||||
await _cache.SetUserCacheAsync(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
public async Task<UserInfoDto> RegisterAsync(RegisterRequestDto dto)
|
||||
{
|
||||
string username = dto.Username;
|
||||
//用户是否存在
|
||||
bool isExist = await _context.Users.AnyAsync(x => x.Username == username);
|
||||
if (isExist) throw new BaseException(CodeDefine.USER_ALREADY_EXISTS);
|
||||
User user = _mapper.Map<User>(dto);
|
||||
_context.Users.Add(user);
|
||||
await _context.SaveChangesAsync();
|
||||
return _mapper.Map<UserInfoDto>(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
using AutoMapper;
|
||||
using IM_API.Dtos.Auth;
|
||||
using IM_API.Dtos.User;
|
||||
using IM_API.Exceptions;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly ImContext _context;
|
||||
private readonly ILogger<AuthService> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ICacheService _cache;
|
||||
public AuthService(ImContext context, ILogger<AuthService> logger, IMapper mapper, ICacheService cache)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_mapper = mapper;
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public async Task<User> LoginAsync(LoginRequestDto dto)
|
||||
{
|
||||
var userinfo = await _cache.GetUserCacheAsync(dto.Username);
|
||||
if (userinfo != null && userinfo.Password == dto.Password) return userinfo;
|
||||
string username = dto.Username;
|
||||
string password = dto.Password;
|
||||
var user = await _context.Users.FirstOrDefaultAsync(x => x.Username == username && x.Password == password);
|
||||
if(user is null)
|
||||
{
|
||||
throw new BaseException(CodeDefine.PASSWORD_ERROR);
|
||||
}
|
||||
await _cache.SetUserCacheAsync(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
public async Task<UserInfoDto> RegisterAsync(RegisterRequestDto dto)
|
||||
{
|
||||
string username = dto.Username;
|
||||
//用户是否存在
|
||||
bool isExist = await _context.Users.AnyAsync(x => x.Username == username);
|
||||
if (isExist) throw new BaseException(CodeDefine.USER_ALREADY_EXISTS);
|
||||
User user = _mapper.Map<User>(dto);
|
||||
_context.Users.Add(user);
|
||||
await _context.SaveChangesAsync();
|
||||
return _mapper.Map<UserInfoDto>(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,182 +1,182 @@
|
||||
using AutoMapper;
|
||||
using IM_API.Dtos.Conversation;
|
||||
using IM_API.Exceptions;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using IM_API.VOs.Conversation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class ConversationService : IConversationService
|
||||
{
|
||||
private readonly ImContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
public ConversationService(ImContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
#region 删除用户会话
|
||||
public async Task<bool> ClearConversationsAsync(int userId)
|
||||
{
|
||||
await _context.Conversations.Where(x => x.UserId == userId).ExecuteDeleteAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
#region 获取用户会话列表
|
||||
public async Task<List<ConversationVo>> GetConversationsAsync(int userId)
|
||||
{
|
||||
// 1. 获取私聊会话
|
||||
var privateList = await (from c in _context.Conversations
|
||||
join f in _context.Friends on new { c.UserId, c.TargetId }
|
||||
equals new { UserId = f.UserId, TargetId = f.FriendId }
|
||||
where c.UserId == userId && c.ChatType == ChatType.PRIVATE
|
||||
select new { c, f.Avatar, f.RemarkName })
|
||||
.ToListAsync();
|
||||
|
||||
// 2. 获取群聊会话
|
||||
var groupList = await (from c in _context.Conversations
|
||||
join g in _context.Groups on c.TargetId equals g.Id
|
||||
where c.UserId == userId && c.ChatType == ChatType.GROUP
|
||||
select new { c, g.Avatar, g.Name,g.MaxSequenceId,g.LastMessage })
|
||||
.ToListAsync();
|
||||
|
||||
var privateDtos = privateList.Select(x =>
|
||||
{
|
||||
var dto = _mapper.Map<ConversationVo>(x.c);
|
||||
dto.TargetAvatar = x.Avatar;
|
||||
dto.TargetName = x.RemarkName;
|
||||
return dto;
|
||||
});
|
||||
|
||||
var groupDtos = groupList.Select(x =>
|
||||
{
|
||||
var dto = _mapper.Map<ConversationVo>(x.c);
|
||||
dto.TargetAvatar = x.Avatar;
|
||||
dto.TargetName = x.Name;
|
||||
dto.UnreadCount = (int)(x.MaxSequenceId - x.c.LastReadSequenceId ?? 0);
|
||||
dto.LastSequenceId = x.MaxSequenceId;
|
||||
dto.LastMessage = x.LastMessage;
|
||||
return dto;
|
||||
});
|
||||
|
||||
// 4. 合并并排序
|
||||
return privateDtos.Concat(groupDtos)
|
||||
.OrderByDescending(x => x.DateTime)
|
||||
.ToList();
|
||||
}
|
||||
#endregion
|
||||
#region 删除单个会话
|
||||
public async Task<bool> DeleteConversationAsync(int conversationId)
|
||||
{
|
||||
var conversation = await _context.Conversations.FirstOrDefaultAsync(x => x.Id == conversationId);
|
||||
if (conversation == null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
|
||||
_context.Conversations.Remove(conversation);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
#region 获取用户所有统一聊天凭证
|
||||
public async Task<List<string>> GetUserAllStreamKeyAsync(int userId)
|
||||
{
|
||||
return await _context.Conversations.Where(x => x.UserId == userId)
|
||||
.Select(x => x.StreamKey)
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 获取单个会话信息
|
||||
public async Task<ConversationVo> GetConversationByIdAsync(int userId, int conversationId)
|
||||
{
|
||||
var conversation = await _context.Conversations
|
||||
.FirstOrDefaultAsync(
|
||||
x => x.UserId == userId && x.Id == conversationId
|
||||
);
|
||||
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
|
||||
var dto = _mapper.Map<ConversationVo>(conversation);
|
||||
if(conversation.ChatType == ChatType.PRIVATE)
|
||||
{
|
||||
var friendInfo = await _context.Friends.Include(n => n.FriendNavigation).FirstOrDefaultAsync(
|
||||
x => x.UserId == conversation.UserId && x.FriendId == conversation.TargetId
|
||||
);
|
||||
if (friendInfo is null) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
|
||||
_mapper.Map(friendInfo,dto);
|
||||
}
|
||||
if(conversation.ChatType == ChatType.GROUP)
|
||||
{
|
||||
var groupInfo = await _context.Groups.FirstOrDefaultAsync(
|
||||
x => x.Id == conversation.TargetId
|
||||
);
|
||||
if (groupInfo is null) throw new BaseException(CodeDefine.GROUP_NOT_FOUND);
|
||||
_mapper.Map(groupInfo, dto);
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task<bool> ClearUnreadCountAsync(int userId, int conversationId)
|
||||
{
|
||||
var conversation = await _context.Conversations.FirstOrDefaultAsync(x => x.UserId == userId && x.Id == conversationId);
|
||||
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
|
||||
var message = await _context.Messages
|
||||
.Where(x => x.StreamKey == conversation.StreamKey)
|
||||
.OrderByDescending(x => x.SequenceId)
|
||||
.FirstOrDefaultAsync();
|
||||
if(message != null)
|
||||
{
|
||||
conversation.UnreadCount = 0;
|
||||
conversation.LastMessage = message.Content;
|
||||
conversation.LastReadSequenceId = message.SequenceId;
|
||||
conversation.LastMessageTime = message.Created;
|
||||
_context.Conversations.Update(conversation);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public async Task MakeConversationAsync(int userAId, int userBId, ChatType chatType)
|
||||
{
|
||||
var userAcExist = await _context.Conversations.AnyAsync(x => x.UserId == userAId && x.TargetId == userBId);
|
||||
if (userAcExist) return;
|
||||
var streamKey = chatType == ChatType.PRIVATE ?
|
||||
StreamKeyBuilder.Private(userAId, userBId) : StreamKeyBuilder.Group(userBId);
|
||||
var conversation = new Conversation()
|
||||
{
|
||||
ChatType = chatType,
|
||||
LastMessage = "",
|
||||
LastMessageTime = DateTime.Now,
|
||||
LastReadSequenceId = null,
|
||||
StreamKey = streamKey,
|
||||
TargetId = userBId,
|
||||
UnreadCount = 0,
|
||||
UserId = userAId
|
||||
|
||||
};
|
||||
_context.Conversations.Add(conversation);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
public async Task UpdateConversationAfterSentAsync(UpdateConversationDto dto)
|
||||
{
|
||||
var cList = await _context.Conversations.Where(x => x.StreamKey == dto.StreamKey).ToListAsync();
|
||||
foreach(var c in cList)
|
||||
{
|
||||
bool isSender = dto.SenderId == c.UserId;
|
||||
c.LastMessage = dto.LastMessage;
|
||||
c.LastMessageTime = dto.DateTime;
|
||||
c.LastReadSequenceId = isSender ? dto.LastSequenceId : c.LastReadSequenceId;
|
||||
c.UnreadCount = isSender ? 0 : c.UnreadCount + 1;
|
||||
}
|
||||
_context.Conversations.UpdateRange(cList);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
using AutoMapper;
|
||||
using IM_API.Dtos.Conversation;
|
||||
using IM_API.Exceptions;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using IM_API.VOs.Conversation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class ConversationService : IConversationService
|
||||
{
|
||||
private readonly ImContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
public ConversationService(ImContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
#region 删除用户会话
|
||||
public async Task<bool> ClearConversationsAsync(int userId)
|
||||
{
|
||||
await _context.Conversations.Where(x => x.UserId == userId).ExecuteDeleteAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
#region 获取用户会话列表
|
||||
public async Task<List<ConversationVo>> GetConversationsAsync(int userId)
|
||||
{
|
||||
// 1. 获取私聊会话
|
||||
var privateList = await (from c in _context.Conversations
|
||||
join f in _context.Friends on new { c.UserId, c.TargetId }
|
||||
equals new { UserId = f.UserId, TargetId = f.FriendId }
|
||||
where c.UserId == userId && c.ChatType == ChatType.PRIVATE
|
||||
select new { c, f.Avatar, f.RemarkName })
|
||||
.ToListAsync();
|
||||
|
||||
// 2. 获取群聊会话
|
||||
var groupList = await (from c in _context.Conversations
|
||||
join g in _context.Groups on c.TargetId equals g.Id
|
||||
where c.UserId == userId && c.ChatType == ChatType.GROUP
|
||||
select new { c, g.Avatar, g.Name,g.MaxSequenceId,g.LastMessage })
|
||||
.ToListAsync();
|
||||
|
||||
var privateDtos = privateList.Select(x =>
|
||||
{
|
||||
var dto = _mapper.Map<ConversationVo>(x.c);
|
||||
dto.TargetAvatar = x.Avatar;
|
||||
dto.TargetName = x.RemarkName;
|
||||
return dto;
|
||||
});
|
||||
|
||||
var groupDtos = groupList.Select(x =>
|
||||
{
|
||||
var dto = _mapper.Map<ConversationVo>(x.c);
|
||||
dto.TargetAvatar = x.Avatar;
|
||||
dto.TargetName = x.Name;
|
||||
dto.UnreadCount = (int)(x.MaxSequenceId - x.c.LastReadSequenceId ?? 0);
|
||||
dto.LastSequenceId = x.MaxSequenceId;
|
||||
dto.LastMessage = x.LastMessage;
|
||||
return dto;
|
||||
});
|
||||
|
||||
// 4. 合并并排序
|
||||
return privateDtos.Concat(groupDtos)
|
||||
.OrderByDescending(x => x.DateTime)
|
||||
.ToList();
|
||||
}
|
||||
#endregion
|
||||
#region 删除单个会话
|
||||
public async Task<bool> DeleteConversationAsync(int conversationId)
|
||||
{
|
||||
var conversation = await _context.Conversations.FirstOrDefaultAsync(x => x.Id == conversationId);
|
||||
if (conversation == null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
|
||||
_context.Conversations.Remove(conversation);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
#region 获取用户所有统一聊天凭证
|
||||
public async Task<List<string>> GetUserAllStreamKeyAsync(int userId)
|
||||
{
|
||||
return await _context.Conversations.Where(x => x.UserId == userId)
|
||||
.Select(x => x.StreamKey)
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 获取单个会话信息
|
||||
public async Task<ConversationVo> GetConversationByIdAsync(int userId, int conversationId)
|
||||
{
|
||||
var conversation = await _context.Conversations
|
||||
.FirstOrDefaultAsync(
|
||||
x => x.UserId == userId && x.Id == conversationId
|
||||
);
|
||||
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
|
||||
var dto = _mapper.Map<ConversationVo>(conversation);
|
||||
if(conversation.ChatType == ChatType.PRIVATE)
|
||||
{
|
||||
var friendInfo = await _context.Friends.Include(n => n.FriendNavigation).FirstOrDefaultAsync(
|
||||
x => x.UserId == conversation.UserId && x.FriendId == conversation.TargetId
|
||||
);
|
||||
if (friendInfo is null) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
|
||||
_mapper.Map(friendInfo,dto);
|
||||
}
|
||||
if(conversation.ChatType == ChatType.GROUP)
|
||||
{
|
||||
var groupInfo = await _context.Groups.FirstOrDefaultAsync(
|
||||
x => x.Id == conversation.TargetId
|
||||
);
|
||||
if (groupInfo is null) throw new BaseException(CodeDefine.GROUP_NOT_FOUND);
|
||||
_mapper.Map(groupInfo, dto);
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task<bool> ClearUnreadCountAsync(int userId, int conversationId)
|
||||
{
|
||||
var conversation = await _context.Conversations.FirstOrDefaultAsync(x => x.UserId == userId && x.Id == conversationId);
|
||||
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
|
||||
var message = await _context.Messages
|
||||
.Where(x => x.StreamKey == conversation.StreamKey)
|
||||
.OrderByDescending(x => x.SequenceId)
|
||||
.FirstOrDefaultAsync();
|
||||
if(message != null)
|
||||
{
|
||||
conversation.UnreadCount = 0;
|
||||
conversation.LastMessage = message.Content;
|
||||
conversation.LastReadSequenceId = message.SequenceId;
|
||||
conversation.LastMessageTime = message.Created;
|
||||
_context.Conversations.Update(conversation);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public async Task MakeConversationAsync(int userAId, int userBId, ChatType chatType)
|
||||
{
|
||||
var userAcExist = await _context.Conversations.AnyAsync(x => x.UserId == userAId && x.TargetId == userBId);
|
||||
if (userAcExist) return;
|
||||
var streamKey = chatType == ChatType.PRIVATE ?
|
||||
StreamKeyBuilder.Private(userAId, userBId) : StreamKeyBuilder.Group(userBId);
|
||||
var conversation = new Conversation()
|
||||
{
|
||||
ChatType = chatType,
|
||||
LastMessage = "",
|
||||
LastMessageTime = DateTime.Now,
|
||||
LastReadSequenceId = null,
|
||||
StreamKey = streamKey,
|
||||
TargetId = userBId,
|
||||
UnreadCount = 0,
|
||||
UserId = userAId
|
||||
|
||||
};
|
||||
_context.Conversations.Add(conversation);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
public async Task UpdateConversationAfterSentAsync(UpdateConversationDto dto)
|
||||
{
|
||||
var cList = await _context.Conversations.Where(x => x.StreamKey == dto.StreamKey).ToListAsync();
|
||||
foreach(var c in cList)
|
||||
{
|
||||
bool isSender = dto.SenderId == c.UserId;
|
||||
c.LastMessage = dto.LastMessage;
|
||||
c.LastMessageTime = dto.DateTime;
|
||||
c.LastReadSequenceId = isSender ? dto.LastSequenceId : c.LastReadSequenceId;
|
||||
c.UnreadCount = isSender ? 0 : c.UnreadCount + 1;
|
||||
}
|
||||
_context.Conversations.UpdateRange(cList);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,220 +1,220 @@
|
||||
using AutoMapper;
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Dtos.Friend;
|
||||
using IM_API.Exceptions;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using MassTransit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class FriendService : IFriendSerivce
|
||||
{
|
||||
private readonly ImContext _context;
|
||||
private readonly ILogger<FriendService> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IPublishEndpoint _endpoint;
|
||||
public FriendService(ImContext context, ILogger<FriendService> logger, IMapper mapper, IPublishEndpoint endpoint)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_mapper = mapper;
|
||||
_endpoint = endpoint;
|
||||
}
|
||||
#region 拉黑好友
|
||||
public async Task<bool> BlockeFriendAsync(int friendId)
|
||||
{
|
||||
var friend = await _context.Friends.FirstOrDefaultAsync(x => x.Id == friendId);
|
||||
if (friend == null) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
|
||||
friend.StatusEnum = FriendStatus.Blocked;
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
#region 通过用户id拉黑好友
|
||||
public async Task<bool> BlockFriendByUserIdAsync(int userId, int toUserId)
|
||||
{
|
||||
var friend = await _context.Friends.FirstOrDefaultAsync(x => x.UserId == userId && x.FriendId == toUserId);
|
||||
if (friend == null) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
|
||||
friend.StatusEnum = FriendStatus.Blocked;
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
#region 删除好友关系
|
||||
public async Task<bool> DeleteFriendAsync(int friendId)
|
||||
{
|
||||
var friend = await _context.Friends.FirstOrDefaultAsync(x => x.Id == friendId);
|
||||
if (friend is null) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
|
||||
_context.Friends.Remove(friend);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
#region 通过用户id删除好友关系
|
||||
public async Task<bool> DeleteFriendByUserIdAsync(int userId, int toUserId)
|
||||
{
|
||||
var friend = await _context.Friends.FirstOrDefaultAsync(x => x.UserId == userId && x.FriendId == toUserId);
|
||||
if (friend is null) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
|
||||
_context.Friends.Remove(friend);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
#region 获取好友列表
|
||||
|
||||
public async Task<List<FriendInfoDto>> GetFriendListAsync(int userId, int page, int limit, bool desc)
|
||||
{
|
||||
var query = _context.Friends.Include(u => u.FriendNavigation).Where(x => x.UserId == userId && x.Status == (sbyte)FriendStatus.Added);
|
||||
if (desc)
|
||||
{
|
||||
query = query.OrderByDescending(x => x.UserId);
|
||||
}
|
||||
var friendList = await query.Skip(((page - 1) * limit)).Take(limit).ToListAsync();
|
||||
return _mapper.Map<List<FriendInfoDto>>(friendList);
|
||||
}
|
||||
#endregion
|
||||
#region 获取好友请求列表
|
||||
public async Task<List<FriendRequestResDto>> GetFriendRequestListAsync(int userId, int page, int limit, bool desc)
|
||||
{
|
||||
var query = _context.FriendRequests
|
||||
.Include(x => x.ResponseUserNavigation)
|
||||
.Include(x => x.RequestUserNavigation)
|
||||
.Where(
|
||||
x => (x.ResponseUser == userId) ||
|
||||
x.RequestUser == userId
|
||||
)
|
||||
.Select(s => new FriendRequestResDto
|
||||
{
|
||||
Id = s.Id,
|
||||
RequestUser = s.RequestUser,
|
||||
ResponseUser = s.ResponseUser,
|
||||
Avatar = s.RequestUser == userId ? s.ResponseUserNavigation.Avatar : s.RequestUserNavigation.Avatar,
|
||||
Created = s.Created,
|
||||
NickName = s.RequestUser == userId ? s.ResponseUserNavigation.NickName : s.RequestUserNavigation.NickName,
|
||||
Description = s.Description,
|
||||
State = (FriendRequestState)s.State
|
||||
})
|
||||
;
|
||||
query = query.OrderByDescending(x => x.Id);
|
||||
var friendRequestList = await query.Skip(((page - 1) * limit)).Take(limit).ToListAsync();
|
||||
return friendRequestList;
|
||||
}
|
||||
#endregion
|
||||
#region 处理好友请求
|
||||
public async Task<bool> HandleFriendRequestAsync(HandleFriendRequestDto requestDto)
|
||||
{
|
||||
//查询好友请求记录
|
||||
var friendRequest = await _context.FriendRequests
|
||||
.Include(e => e.ResponseUserNavigation)
|
||||
.FirstOrDefaultAsync(x => x.Id == requestDto.RequestId);
|
||||
|
||||
if (friendRequest is null) throw new BaseException(CodeDefine.FRIEND_REQUEST_NOT_FOUND);
|
||||
|
||||
//查询好友关系
|
||||
var friend = await _context.Friends.FirstOrDefaultAsync(
|
||||
x => x.UserId == friendRequest.RequestUser && x.FriendId == friendRequest.ResponseUser
|
||||
);
|
||||
if (friend != null) throw new BaseException(CodeDefine.ALREADY_FRIENDS);
|
||||
//处理好友请求操作
|
||||
switch (requestDto.Action)
|
||||
{
|
||||
//拒绝后标记
|
||||
case HandleFriendRequestAction.Reject:
|
||||
friendRequest.StateEnum = FriendRequestState.Declined;
|
||||
break;
|
||||
|
||||
//同意后标记
|
||||
case HandleFriendRequestAction.Accept:
|
||||
friendRequest.StateEnum = FriendRequestState.Passed;
|
||||
await _endpoint.Publish(new FriendAddEvent()
|
||||
{
|
||||
AggregateId = friendRequest.Id.ToString(),
|
||||
OccurredAt = DateTime.Now,
|
||||
Created = DateTime.Now,
|
||||
EventId = Guid.NewGuid(),
|
||||
OperatorId = friendRequest.ResponseUser,
|
||||
RequestInfo = _mapper.Map<FriendRequestDto>(friendRequest),
|
||||
requestUserRemarkname = requestDto.RemarkName,
|
||||
RequestUserId = friendRequest.RequestUser,
|
||||
ResponseUserId = friendRequest.ResponseUser
|
||||
|
||||
});
|
||||
break;
|
||||
|
||||
//无效操作
|
||||
default:
|
||||
throw new BaseException(CodeDefine.INVALID_ACTION);
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
#region 发起好友请求
|
||||
public async Task<bool> SendFriendRequestAsync(FriendRequestDto dto)
|
||||
{
|
||||
//查询用户是否存在
|
||||
bool isExist = await _context.Users.AnyAsync(x => x.Id == dto.ToUserId);
|
||||
if (!isExist) throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
bool isExistUser2 = await _context.Users.AnyAsync(x => x.Id == dto.FromUserId);
|
||||
if(!isExistUser2) throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
// 检查是否已有好友关系或待处理请求
|
||||
bool alreadyExists = await _context.FriendRequests.AnyAsync(x =>
|
||||
x.RequestUser == dto.FromUserId && x.ResponseUser == dto.ToUserId && x.State == (sbyte)FriendRequestState.Pending
|
||||
);
|
||||
if (alreadyExists)
|
||||
throw new BaseException(CodeDefine.FRIEND_REQUEST_EXISTS);
|
||||
|
||||
var friendShip = await _context.Friends.FirstOrDefaultAsync(x => x.UserId == dto.FromUserId && x.FriendId == dto.ToUserId);
|
||||
|
||||
//检查是否被对方拉黑
|
||||
bool isBlocked = friendShip != null && friendShip.StatusEnum == FriendStatus.Blocked;
|
||||
if (isBlocked)
|
||||
throw new BaseException(CodeDefine.FRIEND_REQUEST_REJECTED);
|
||||
if (friendShip != null)
|
||||
throw new BaseException(CodeDefine.ALREADY_FRIENDS);
|
||||
//生成实体
|
||||
var friendRequst = _mapper.Map<FriendRequest>(dto);
|
||||
_context.FriendRequests.Add(friendRequst);
|
||||
await _context.SaveChangesAsync();
|
||||
await _endpoint.Publish(new RequestFriendEvent()
|
||||
{
|
||||
AggregateId = friendRequst.Id.ToString(),
|
||||
OccurredAt = friendRequst.Created.UtcDateTime,
|
||||
Description = friendRequst.Description,
|
||||
EventId = Guid.NewGuid(),
|
||||
FromUserId = friendRequst.RequestUser,
|
||||
ToUserId = friendRequst.ResponseUser,
|
||||
OperatorId = friendRequst.RequestUser
|
||||
});
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 创建好友关系
|
||||
public async Task MakeFriendshipAsync(int userAId, int userBId, string? remarkName)
|
||||
{
|
||||
bool userAexist = await _context.Friends.AnyAsync(x => x.UserId == userAId && x.FriendId == userBId);
|
||||
if (!userAexist)
|
||||
{
|
||||
User? userbInfo = await _context.Users.FirstOrDefaultAsync(x => x.Id == userBId);
|
||||
if (userbInfo is null) throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
Friend friendA = new Friend()
|
||||
{
|
||||
Avatar = userbInfo.Avatar,
|
||||
Created = DateTime.Now,
|
||||
FriendId = userbInfo.Id,
|
||||
RemarkName = remarkName ?? userbInfo.NickName,
|
||||
StatusEnum = FriendStatus.Added,
|
||||
UserId = userAId
|
||||
};
|
||||
_context.Friends.Add(friendA);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
using AutoMapper;
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Dtos.Friend;
|
||||
using IM_API.Exceptions;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using MassTransit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class FriendService : IFriendSerivce
|
||||
{
|
||||
private readonly ImContext _context;
|
||||
private readonly ILogger<FriendService> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IPublishEndpoint _endpoint;
|
||||
public FriendService(ImContext context, ILogger<FriendService> logger, IMapper mapper, IPublishEndpoint endpoint)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_mapper = mapper;
|
||||
_endpoint = endpoint;
|
||||
}
|
||||
#region 拉黑好友
|
||||
public async Task<bool> BlockeFriendAsync(int friendId)
|
||||
{
|
||||
var friend = await _context.Friends.FirstOrDefaultAsync(x => x.Id == friendId);
|
||||
if (friend == null) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
|
||||
friend.StatusEnum = FriendStatus.Blocked;
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
#region 通过用户id拉黑好友
|
||||
public async Task<bool> BlockFriendByUserIdAsync(int userId, int toUserId)
|
||||
{
|
||||
var friend = await _context.Friends.FirstOrDefaultAsync(x => x.UserId == userId && x.FriendId == toUserId);
|
||||
if (friend == null) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
|
||||
friend.StatusEnum = FriendStatus.Blocked;
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
#region 删除好友关系
|
||||
public async Task<bool> DeleteFriendAsync(int friendId)
|
||||
{
|
||||
var friend = await _context.Friends.FirstOrDefaultAsync(x => x.Id == friendId);
|
||||
if (friend is null) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
|
||||
_context.Friends.Remove(friend);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
#region 通过用户id删除好友关系
|
||||
public async Task<bool> DeleteFriendByUserIdAsync(int userId, int toUserId)
|
||||
{
|
||||
var friend = await _context.Friends.FirstOrDefaultAsync(x => x.UserId == userId && x.FriendId == toUserId);
|
||||
if (friend is null) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
|
||||
_context.Friends.Remove(friend);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
#region 获取好友列表
|
||||
|
||||
public async Task<List<FriendInfoDto>> GetFriendListAsync(int userId, int page, int limit, bool desc)
|
||||
{
|
||||
var query = _context.Friends.Include(u => u.FriendNavigation).Where(x => x.UserId == userId && x.Status == (sbyte)FriendStatus.Added);
|
||||
if (desc)
|
||||
{
|
||||
query = query.OrderByDescending(x => x.UserId);
|
||||
}
|
||||
var friendList = await query.Skip(((page - 1) * limit)).Take(limit).ToListAsync();
|
||||
return _mapper.Map<List<FriendInfoDto>>(friendList);
|
||||
}
|
||||
#endregion
|
||||
#region 获取好友请求列表
|
||||
public async Task<List<FriendRequestResDto>> GetFriendRequestListAsync(int userId, int page, int limit, bool desc)
|
||||
{
|
||||
var query = _context.FriendRequests
|
||||
.Include(x => x.ResponseUserNavigation)
|
||||
.Include(x => x.RequestUserNavigation)
|
||||
.Where(
|
||||
x => (x.ResponseUser == userId) ||
|
||||
x.RequestUser == userId
|
||||
)
|
||||
.Select(s => new FriendRequestResDto
|
||||
{
|
||||
Id = s.Id,
|
||||
RequestUser = s.RequestUser,
|
||||
ResponseUser = s.ResponseUser,
|
||||
Avatar = s.RequestUser == userId ? s.ResponseUserNavigation.Avatar : s.RequestUserNavigation.Avatar,
|
||||
Created = s.Created,
|
||||
NickName = s.RequestUser == userId ? s.ResponseUserNavigation.NickName : s.RequestUserNavigation.NickName,
|
||||
Description = s.Description,
|
||||
State = (FriendRequestState)s.State
|
||||
})
|
||||
;
|
||||
query = query.OrderByDescending(x => x.Id);
|
||||
var friendRequestList = await query.Skip(((page - 1) * limit)).Take(limit).ToListAsync();
|
||||
return friendRequestList;
|
||||
}
|
||||
#endregion
|
||||
#region 处理好友请求
|
||||
public async Task<bool> HandleFriendRequestAsync(HandleFriendRequestDto requestDto)
|
||||
{
|
||||
//查询好友请求记录
|
||||
var friendRequest = await _context.FriendRequests
|
||||
.Include(e => e.ResponseUserNavigation)
|
||||
.FirstOrDefaultAsync(x => x.Id == requestDto.RequestId);
|
||||
|
||||
if (friendRequest is null) throw new BaseException(CodeDefine.FRIEND_REQUEST_NOT_FOUND);
|
||||
|
||||
//查询好友关系
|
||||
var friend = await _context.Friends.FirstOrDefaultAsync(
|
||||
x => x.UserId == friendRequest.RequestUser && x.FriendId == friendRequest.ResponseUser
|
||||
);
|
||||
if (friend != null) throw new BaseException(CodeDefine.ALREADY_FRIENDS);
|
||||
//处理好友请求操作
|
||||
switch (requestDto.Action)
|
||||
{
|
||||
//拒绝后标记
|
||||
case HandleFriendRequestAction.Reject:
|
||||
friendRequest.StateEnum = FriendRequestState.Declined;
|
||||
break;
|
||||
|
||||
//同意后标记
|
||||
case HandleFriendRequestAction.Accept:
|
||||
friendRequest.StateEnum = FriendRequestState.Passed;
|
||||
await _endpoint.Publish(new FriendAddEvent()
|
||||
{
|
||||
AggregateId = friendRequest.Id.ToString(),
|
||||
OccurredAt = DateTime.Now,
|
||||
Created = DateTime.Now,
|
||||
EventId = Guid.NewGuid(),
|
||||
OperatorId = friendRequest.ResponseUser,
|
||||
RequestInfo = _mapper.Map<FriendRequestDto>(friendRequest),
|
||||
requestUserRemarkname = requestDto.RemarkName,
|
||||
RequestUserId = friendRequest.RequestUser,
|
||||
ResponseUserId = friendRequest.ResponseUser
|
||||
|
||||
});
|
||||
break;
|
||||
|
||||
//无效操作
|
||||
default:
|
||||
throw new BaseException(CodeDefine.INVALID_ACTION);
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
#region 发起好友请求
|
||||
public async Task<bool> SendFriendRequestAsync(FriendRequestDto dto)
|
||||
{
|
||||
//查询用户是否存在
|
||||
bool isExist = await _context.Users.AnyAsync(x => x.Id == dto.ToUserId);
|
||||
if (!isExist) throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
bool isExistUser2 = await _context.Users.AnyAsync(x => x.Id == dto.FromUserId);
|
||||
if(!isExistUser2) throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
// 检查是否已有好友关系或待处理请求
|
||||
bool alreadyExists = await _context.FriendRequests.AnyAsync(x =>
|
||||
x.RequestUser == dto.FromUserId && x.ResponseUser == dto.ToUserId && x.State == (sbyte)FriendRequestState.Pending
|
||||
);
|
||||
if (alreadyExists)
|
||||
throw new BaseException(CodeDefine.FRIEND_REQUEST_EXISTS);
|
||||
|
||||
var friendShip = await _context.Friends.FirstOrDefaultAsync(x => x.UserId == dto.FromUserId && x.FriendId == dto.ToUserId);
|
||||
|
||||
//检查是否被对方拉黑
|
||||
bool isBlocked = friendShip != null && friendShip.StatusEnum == FriendStatus.Blocked;
|
||||
if (isBlocked)
|
||||
throw new BaseException(CodeDefine.FRIEND_REQUEST_REJECTED);
|
||||
if (friendShip != null)
|
||||
throw new BaseException(CodeDefine.ALREADY_FRIENDS);
|
||||
//生成实体
|
||||
var friendRequst = _mapper.Map<FriendRequest>(dto);
|
||||
_context.FriendRequests.Add(friendRequst);
|
||||
await _context.SaveChangesAsync();
|
||||
await _endpoint.Publish(new RequestFriendEvent()
|
||||
{
|
||||
AggregateId = friendRequst.Id.ToString(),
|
||||
OccurredAt = friendRequst.Created.UtcDateTime,
|
||||
Description = friendRequst.Description,
|
||||
EventId = Guid.NewGuid(),
|
||||
FromUserId = friendRequst.RequestUser,
|
||||
ToUserId = friendRequst.ResponseUser,
|
||||
OperatorId = friendRequst.RequestUser
|
||||
});
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 创建好友关系
|
||||
public async Task MakeFriendshipAsync(int userAId, int userBId, string? remarkName)
|
||||
{
|
||||
bool userAexist = await _context.Friends.AnyAsync(x => x.UserId == userAId && x.FriendId == userBId);
|
||||
if (!userAexist)
|
||||
{
|
||||
User? userbInfo = await _context.Users.FirstOrDefaultAsync(x => x.Id == userBId);
|
||||
if (userbInfo is null) throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
Friend friendA = new Friend()
|
||||
{
|
||||
Avatar = userbInfo.Avatar,
|
||||
Created = DateTime.Now,
|
||||
FriendId = userbInfo.Id,
|
||||
RemarkName = remarkName ?? userbInfo.NickName,
|
||||
StatusEnum = FriendStatus.Added,
|
||||
UserId = userAId
|
||||
};
|
||||
_context.Friends.Add(friendA);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,251 +1,251 @@
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Dtos.Group;
|
||||
using IM_API.Exceptions;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using MassTransit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class GroupService : IGroupService
|
||||
{
|
||||
private readonly ImContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ILogger<GroupService> _logger;
|
||||
private readonly IPublishEndpoint _endPoint;
|
||||
private readonly IUserService _userService;
|
||||
public GroupService(ImContext context, IMapper mapper, ILogger<GroupService> logger,
|
||||
IPublishEndpoint publishEndpoint, IUserService userService)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
_logger = logger;
|
||||
_endPoint = publishEndpoint;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
private async Task<List<int>> validFriendshipAsync (int userId, List<int> ids)
|
||||
{
|
||||
DateTime dateTime = DateTime.UtcNow;
|
||||
//验证被邀请用户是否为好友
|
||||
return await _context.Friends
|
||||
.Where(f => f.UserId == userId && ids.Contains(f.FriendId))
|
||||
.Select(f => f.FriendId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<GroupInfoDto> CreateGroupAsync(int userId, GroupCreateDto groupCreateDto)
|
||||
{
|
||||
List<int> userIds = groupCreateDto.UserIDs ?? [];
|
||||
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
//先创建群
|
||||
DateTime dateTime = DateTime.Now;
|
||||
Group group = _mapper.Map<Group>(groupCreateDto);
|
||||
group.GroupMaster = userId;
|
||||
_context.Groups.Add(group);
|
||||
await _context.SaveChangesAsync();
|
||||
if (userIds.Count > 0)
|
||||
{
|
||||
//邀请好友
|
||||
await InviteUsersAsync(userId, group.Id ,userIds);
|
||||
}
|
||||
await transaction.CommitAsync();
|
||||
await _endPoint.Publish(new GroupJoinEvent
|
||||
{
|
||||
EventId = Guid.NewGuid(),
|
||||
AggregateId = userId.ToString(),
|
||||
GroupId = group.Id,
|
||||
OccurredAt = dateTime,
|
||||
OperatorId = userId,
|
||||
UserId = userId,
|
||||
IsCreated = true
|
||||
});
|
||||
return _mapper.Map<GroupInfoDto>(group);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Task DeleteGroupAsync(int userId, int groupId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task InviteUsersAsync(int userId, int groupId, List<int> userIds)
|
||||
{
|
||||
var group = await _context.Groups.FirstOrDefaultAsync(
|
||||
x => x.Id == groupId) ?? throw new BaseException(CodeDefine.GROUP_NOT_FOUND);
|
||||
//过滤非好友
|
||||
var groupInviteIds = await validFriendshipAsync(userId, userIds);
|
||||
var inviteList = groupInviteIds.Select(id => new GroupInvite
|
||||
{
|
||||
Created = DateTime.UtcNow,
|
||||
GroupId = group.Id,
|
||||
InviteUser = userId,
|
||||
InvitedUser = id,
|
||||
StateEnum = GroupInviteState.Pending
|
||||
}).ToList();
|
||||
_context.GroupInvites.AddRange(inviteList);
|
||||
await _context.SaveChangesAsync();
|
||||
await _endPoint.Publish(new GroupInviteEvent
|
||||
{
|
||||
GroupId = groupId,
|
||||
AggregateId = userId.ToString(),
|
||||
OccurredAt = DateTime.UtcNow,
|
||||
EventId = Guid.NewGuid(),
|
||||
Ids = userIds,
|
||||
OperatorId = userId,
|
||||
UserId = userId
|
||||
});
|
||||
}
|
||||
|
||||
public async Task MakeGroupMemberAsync(int userId, int groupId ,GroupMemberRole? role)
|
||||
{
|
||||
var isExist = await _context.GroupMembers.AnyAsync(x => x.GroupId == groupId && x.UserId == userId);
|
||||
if (isExist) return;
|
||||
var groupMember = new GroupMember
|
||||
{
|
||||
UserId = userId,
|
||||
Created = DateTime.UtcNow,
|
||||
RoleEnum = role ?? GroupMemberRole.Normal,
|
||||
GroupId = groupId
|
||||
};
|
||||
_context.GroupMembers.Add(groupMember);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public Task JoinGroupAsync(int userId, int groupId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<List<GroupInfoDto>> GetGroupListAsync(int userId, int page, int limit, bool desc)
|
||||
{
|
||||
var query = _context.GroupMembers
|
||||
.Where(x => x.UserId == userId)
|
||||
.Select(s => s.Group);
|
||||
if (desc)
|
||||
{
|
||||
query = query.OrderByDescending(x => x.Id);
|
||||
}
|
||||
var list = await query
|
||||
.Skip((page - 1) * limit)
|
||||
.Take(limit)
|
||||
.ProjectTo<GroupInfoDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
return list;
|
||||
}
|
||||
public async Task UpdateGroupConversationAsync(GroupUpdateConversationDto dto)
|
||||
{
|
||||
var group = await _context.Groups.FirstOrDefaultAsync(x => x.Id == dto.GroupId);
|
||||
if (group is null) return;
|
||||
group.LastMessage = dto.LastMessage;
|
||||
group.MaxSequenceId = dto.MaxSequenceId;
|
||||
group.LastSenderName = dto.LastSenderName;
|
||||
group.LastUpdateTime = dto.LastUpdateTime;
|
||||
_context.Groups.Update(group);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
public async Task HandleGroupInviteAsync(int userid, HandleGroupInviteDto dto)
|
||||
{
|
||||
var user = _userService.GetUserInfoAsync(userid);
|
||||
var inviteInfo = await _context.GroupInvites.FirstOrDefaultAsync(x => x.Id == dto.InviteId)
|
||||
?? throw new BaseException(CodeDefine.INVALID_ACTION);
|
||||
if (inviteInfo.InvitedUser != userid) throw new BaseException(CodeDefine.AUTH_FAILED);
|
||||
inviteInfo.StateEnum = dto.Action;
|
||||
_context.GroupInvites.Update(inviteInfo);
|
||||
await _context.SaveChangesAsync();
|
||||
await _endPoint.Publish(new GroupInviteActionUpdateEvent
|
||||
{
|
||||
Action = dto.Action,
|
||||
AggregateId = userid.ToString(),
|
||||
OccurredAt = DateTime.UtcNow,
|
||||
EventId = Guid.NewGuid(),
|
||||
GroupId = inviteInfo.GroupId,
|
||||
InviteId = inviteInfo.Id,
|
||||
InviteUserId = inviteInfo.InviteUser.Value,
|
||||
OperatorId = userid,
|
||||
UserId = userid
|
||||
|
||||
});
|
||||
}
|
||||
public async Task HandleGroupRequestAsync(int userid, HandleGroupRequestDto dto)
|
||||
{
|
||||
var user = _userService.GetUserInfoAsync(userid);
|
||||
//判断请求存在
|
||||
var requestInfo = await _context.GroupRequests.FirstOrDefaultAsync(x => x.Id == dto.RequestId)
|
||||
?? throw new BaseException(CodeDefine.INVALID_ACTION);
|
||||
//判断成员存在
|
||||
var memberInfo = await _context.GroupMembers.FirstOrDefaultAsync(x => x.UserId == userid)
|
||||
?? throw new BaseException(CodeDefine.NO_GROUP_PERMISSION);
|
||||
//判断成员权限
|
||||
if (memberInfo.RoleEnum != GroupMemberRole.Master && memberInfo.RoleEnum != GroupMemberRole.Administrator)
|
||||
throw new BaseException(CodeDefine.NO_GROUP_PERMISSION);
|
||||
|
||||
requestInfo.StateEnum = dto.Action;
|
||||
_context.GroupRequests.Update(requestInfo);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
await _endPoint.Publish(new GroupRequestUpdateEvent
|
||||
{
|
||||
Action = requestInfo.StateEnum,
|
||||
AdminUserId = userid,
|
||||
AggregateId = userid.ToString(),
|
||||
OccurredAt = DateTime.UtcNow,
|
||||
EventId = Guid.NewGuid(),
|
||||
GroupId = requestInfo.GroupId,
|
||||
OperatorId = userid,
|
||||
UserId = requestInfo.UserId,
|
||||
RequestId = requestInfo.Id
|
||||
});
|
||||
}
|
||||
public async Task MakeGroupRequestAsync(int userId, int? adminUserId, int groupId)
|
||||
{
|
||||
var requestInfo = await _context.GroupRequests
|
||||
.FirstOrDefaultAsync(x => x.UserId == userId && x.GroupId == groupId);
|
||||
if (requestInfo != null) return;
|
||||
|
||||
var member = await _context.GroupMembers.FirstOrDefaultAsync(
|
||||
x => x.UserId == adminUserId && x.GroupId == groupId);
|
||||
var request = new GroupRequest
|
||||
{
|
||||
Created = DateTime.UtcNow,
|
||||
Description = string.Empty,
|
||||
GroupId = groupId,
|
||||
UserId = userId,
|
||||
StateEnum = GroupRequestState.Pending
|
||||
};
|
||||
if(member != null && (
|
||||
member.RoleEnum == GroupMemberRole.Administrator || member.RoleEnum == GroupMemberRole.Master))
|
||||
{
|
||||
request.StateEnum = GroupRequestState.Passed;
|
||||
}
|
||||
|
||||
_context.GroupRequests.Add(request);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
await _endPoint.Publish(new GroupRequestEvent
|
||||
{
|
||||
OccurredAt = DateTime.UtcNow,
|
||||
Description = request.Description,
|
||||
GroupId = request.GroupId,
|
||||
Action = request.StateEnum,
|
||||
UserId = userId,
|
||||
AggregateId = userId.ToString(),
|
||||
EventId = Guid.NewGuid(),
|
||||
OperatorId = userId
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Dtos.Group;
|
||||
using IM_API.Exceptions;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using MassTransit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class GroupService : IGroupService
|
||||
{
|
||||
private readonly ImContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ILogger<GroupService> _logger;
|
||||
private readonly IPublishEndpoint _endPoint;
|
||||
private readonly IUserService _userService;
|
||||
public GroupService(ImContext context, IMapper mapper, ILogger<GroupService> logger,
|
||||
IPublishEndpoint publishEndpoint, IUserService userService)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
_logger = logger;
|
||||
_endPoint = publishEndpoint;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
private async Task<List<int>> validFriendshipAsync (int userId, List<int> ids)
|
||||
{
|
||||
DateTime dateTime = DateTime.UtcNow;
|
||||
//验证被邀请用户是否为好友
|
||||
return await _context.Friends
|
||||
.Where(f => f.UserId == userId && ids.Contains(f.FriendId))
|
||||
.Select(f => f.FriendId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<GroupInfoDto> CreateGroupAsync(int userId, GroupCreateDto groupCreateDto)
|
||||
{
|
||||
List<int> userIds = groupCreateDto.UserIDs ?? [];
|
||||
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
//先创建群
|
||||
DateTime dateTime = DateTime.Now;
|
||||
Group group = _mapper.Map<Group>(groupCreateDto);
|
||||
group.GroupMaster = userId;
|
||||
_context.Groups.Add(group);
|
||||
await _context.SaveChangesAsync();
|
||||
if (userIds.Count > 0)
|
||||
{
|
||||
//邀请好友
|
||||
await InviteUsersAsync(userId, group.Id ,userIds);
|
||||
}
|
||||
await transaction.CommitAsync();
|
||||
await _endPoint.Publish(new GroupJoinEvent
|
||||
{
|
||||
EventId = Guid.NewGuid(),
|
||||
AggregateId = userId.ToString(),
|
||||
GroupId = group.Id,
|
||||
OccurredAt = dateTime,
|
||||
OperatorId = userId,
|
||||
UserId = userId,
|
||||
IsCreated = true
|
||||
});
|
||||
return _mapper.Map<GroupInfoDto>(group);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Task DeleteGroupAsync(int userId, int groupId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task InviteUsersAsync(int userId, int groupId, List<int> userIds)
|
||||
{
|
||||
var group = await _context.Groups.FirstOrDefaultAsync(
|
||||
x => x.Id == groupId) ?? throw new BaseException(CodeDefine.GROUP_NOT_FOUND);
|
||||
//过滤非好友
|
||||
var groupInviteIds = await validFriendshipAsync(userId, userIds);
|
||||
var inviteList = groupInviteIds.Select(id => new GroupInvite
|
||||
{
|
||||
Created = DateTime.UtcNow,
|
||||
GroupId = group.Id,
|
||||
InviteUser = userId,
|
||||
InvitedUser = id,
|
||||
StateEnum = GroupInviteState.Pending
|
||||
}).ToList();
|
||||
_context.GroupInvites.AddRange(inviteList);
|
||||
await _context.SaveChangesAsync();
|
||||
await _endPoint.Publish(new GroupInviteEvent
|
||||
{
|
||||
GroupId = groupId,
|
||||
AggregateId = userId.ToString(),
|
||||
OccurredAt = DateTime.UtcNow,
|
||||
EventId = Guid.NewGuid(),
|
||||
Ids = userIds,
|
||||
OperatorId = userId,
|
||||
UserId = userId
|
||||
});
|
||||
}
|
||||
|
||||
public async Task MakeGroupMemberAsync(int userId, int groupId ,GroupMemberRole? role)
|
||||
{
|
||||
var isExist = await _context.GroupMembers.AnyAsync(x => x.GroupId == groupId && x.UserId == userId);
|
||||
if (isExist) return;
|
||||
var groupMember = new GroupMember
|
||||
{
|
||||
UserId = userId,
|
||||
Created = DateTime.UtcNow,
|
||||
RoleEnum = role ?? GroupMemberRole.Normal,
|
||||
GroupId = groupId
|
||||
};
|
||||
_context.GroupMembers.Add(groupMember);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public Task JoinGroupAsync(int userId, int groupId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<List<GroupInfoDto>> GetGroupListAsync(int userId, int page, int limit, bool desc)
|
||||
{
|
||||
var query = _context.GroupMembers
|
||||
.Where(x => x.UserId == userId)
|
||||
.Select(s => s.Group);
|
||||
if (desc)
|
||||
{
|
||||
query = query.OrderByDescending(x => x.Id);
|
||||
}
|
||||
var list = await query
|
||||
.Skip((page - 1) * limit)
|
||||
.Take(limit)
|
||||
.ProjectTo<GroupInfoDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
return list;
|
||||
}
|
||||
public async Task UpdateGroupConversationAsync(GroupUpdateConversationDto dto)
|
||||
{
|
||||
var group = await _context.Groups.FirstOrDefaultAsync(x => x.Id == dto.GroupId);
|
||||
if (group is null) return;
|
||||
group.LastMessage = dto.LastMessage;
|
||||
group.MaxSequenceId = dto.MaxSequenceId;
|
||||
group.LastSenderName = dto.LastSenderName;
|
||||
group.LastUpdateTime = dto.LastUpdateTime;
|
||||
_context.Groups.Update(group);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
public async Task HandleGroupInviteAsync(int userid, HandleGroupInviteDto dto)
|
||||
{
|
||||
var user = _userService.GetUserInfoAsync(userid);
|
||||
var inviteInfo = await _context.GroupInvites.FirstOrDefaultAsync(x => x.Id == dto.InviteId)
|
||||
?? throw new BaseException(CodeDefine.INVALID_ACTION);
|
||||
if (inviteInfo.InvitedUser != userid) throw new BaseException(CodeDefine.AUTH_FAILED);
|
||||
inviteInfo.StateEnum = dto.Action;
|
||||
_context.GroupInvites.Update(inviteInfo);
|
||||
await _context.SaveChangesAsync();
|
||||
await _endPoint.Publish(new GroupInviteActionUpdateEvent
|
||||
{
|
||||
Action = dto.Action,
|
||||
AggregateId = userid.ToString(),
|
||||
OccurredAt = DateTime.UtcNow,
|
||||
EventId = Guid.NewGuid(),
|
||||
GroupId = inviteInfo.GroupId,
|
||||
InviteId = inviteInfo.Id,
|
||||
InviteUserId = inviteInfo.InviteUser.Value,
|
||||
OperatorId = userid,
|
||||
UserId = userid
|
||||
|
||||
});
|
||||
}
|
||||
public async Task HandleGroupRequestAsync(int userid, HandleGroupRequestDto dto)
|
||||
{
|
||||
var user = _userService.GetUserInfoAsync(userid);
|
||||
//判断请求存在
|
||||
var requestInfo = await _context.GroupRequests.FirstOrDefaultAsync(x => x.Id == dto.RequestId)
|
||||
?? throw new BaseException(CodeDefine.INVALID_ACTION);
|
||||
//判断成员存在
|
||||
var memberInfo = await _context.GroupMembers.FirstOrDefaultAsync(x => x.UserId == userid)
|
||||
?? throw new BaseException(CodeDefine.NO_GROUP_PERMISSION);
|
||||
//判断成员权限
|
||||
if (memberInfo.RoleEnum != GroupMemberRole.Master && memberInfo.RoleEnum != GroupMemberRole.Administrator)
|
||||
throw new BaseException(CodeDefine.NO_GROUP_PERMISSION);
|
||||
|
||||
requestInfo.StateEnum = dto.Action;
|
||||
_context.GroupRequests.Update(requestInfo);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
await _endPoint.Publish(new GroupRequestUpdateEvent
|
||||
{
|
||||
Action = requestInfo.StateEnum,
|
||||
AdminUserId = userid,
|
||||
AggregateId = userid.ToString(),
|
||||
OccurredAt = DateTime.UtcNow,
|
||||
EventId = Guid.NewGuid(),
|
||||
GroupId = requestInfo.GroupId,
|
||||
OperatorId = userid,
|
||||
UserId = requestInfo.UserId,
|
||||
RequestId = requestInfo.Id
|
||||
});
|
||||
}
|
||||
public async Task MakeGroupRequestAsync(int userId, int? adminUserId, int groupId)
|
||||
{
|
||||
var requestInfo = await _context.GroupRequests
|
||||
.FirstOrDefaultAsync(x => x.UserId == userId && x.GroupId == groupId);
|
||||
if (requestInfo != null) return;
|
||||
|
||||
var member = await _context.GroupMembers.FirstOrDefaultAsync(
|
||||
x => x.UserId == adminUserId && x.GroupId == groupId);
|
||||
var request = new GroupRequest
|
||||
{
|
||||
Created = DateTime.UtcNow,
|
||||
Description = string.Empty,
|
||||
GroupId = groupId,
|
||||
UserId = userId,
|
||||
StateEnum = GroupRequestState.Pending
|
||||
};
|
||||
if(member != null && (
|
||||
member.RoleEnum == GroupMemberRole.Administrator || member.RoleEnum == GroupMemberRole.Master))
|
||||
{
|
||||
request.StateEnum = GroupRequestState.Passed;
|
||||
}
|
||||
|
||||
_context.GroupRequests.Add(request);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
await _endPoint.Publish(new GroupRequestEvent
|
||||
{
|
||||
OccurredAt = DateTime.UtcNow,
|
||||
Description = request.Description,
|
||||
GroupId = request.GroupId,
|
||||
Action = request.StateEnum,
|
||||
UserId = userId,
|
||||
AggregateId = userId.ToString(),
|
||||
EventId = Guid.NewGuid(),
|
||||
OperatorId = userId
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
using IM_API.Interface.Services;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class JWTService : IJWTService
|
||||
{
|
||||
private readonly IConfiguration _config;
|
||||
private readonly string _key;
|
||||
private readonly string _issuer;
|
||||
private readonly string _audience;
|
||||
private readonly int _accessMinutes;
|
||||
|
||||
public JWTService(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
_key = _config["Jwt:Key"]!;
|
||||
_issuer = _config["Jwt:Issuer"]!;
|
||||
_audience = _config["Jwt:Audience"]!;
|
||||
_accessMinutes = int.Parse(_config["Jwt:AccessTokenMinutes"] ?? "15");
|
||||
}
|
||||
|
||||
public string GenerateAccessToken(IEnumerable<Claim> claims, DateTime expiresAt)
|
||||
{
|
||||
var keyBytes = Encoding.UTF8.GetBytes(_key);
|
||||
var creds = new SigningCredentials(new SymmetricSecurityKey(keyBytes), SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: _issuer,
|
||||
audience: _audience,
|
||||
claims: claims,
|
||||
expires: expiresAt,
|
||||
signingCredentials: creds
|
||||
);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
|
||||
public (string token, DateTime expiresAt) CreateAccessTokenForUser(int userId, string username, string role)
|
||||
{
|
||||
var expiresAt = DateTime.Now.AddMinutes(_accessMinutes);
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
||||
new Claim(ClaimTypes.Name, username),
|
||||
new Claim(ClaimTypes.Role, role)
|
||||
};
|
||||
var token = GenerateAccessToken(claims, expiresAt);
|
||||
return (token, expiresAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
using IM_API.Interface.Services;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class JWTService : IJWTService
|
||||
{
|
||||
private readonly IConfiguration _config;
|
||||
private readonly string _key;
|
||||
private readonly string _issuer;
|
||||
private readonly string _audience;
|
||||
private readonly int _accessMinutes;
|
||||
|
||||
public JWTService(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
_key = _config["Jwt:Key"]!;
|
||||
_issuer = _config["Jwt:Issuer"]!;
|
||||
_audience = _config["Jwt:Audience"]!;
|
||||
_accessMinutes = int.Parse(_config["Jwt:AccessTokenMinutes"] ?? "15");
|
||||
}
|
||||
|
||||
public string GenerateAccessToken(IEnumerable<Claim> claims, DateTime expiresAt)
|
||||
{
|
||||
var keyBytes = Encoding.UTF8.GetBytes(_key);
|
||||
var creds = new SigningCredentials(new SymmetricSecurityKey(keyBytes), SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: _issuer,
|
||||
audience: _audience,
|
||||
claims: claims,
|
||||
expires: expiresAt,
|
||||
signingCredentials: creds
|
||||
);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
|
||||
public (string token, DateTime expiresAt) CreateAccessTokenForUser(int userId, string username, string role)
|
||||
{
|
||||
var expiresAt = DateTime.Now.AddMinutes(_accessMinutes);
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
||||
new Claim(ClaimTypes.Name, username),
|
||||
new Claim(ClaimTypes.Role, role)
|
||||
};
|
||||
var token = GenerateAccessToken(claims, expiresAt);
|
||||
return (token, expiresAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,164 +1,164 @@
|
||||
using AutoMapper;
|
||||
using IM_API.Application.Interfaces;
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Dtos;
|
||||
using IM_API.Dtos.Message;
|
||||
using IM_API.Exceptions;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using IM_API.VOs.Message;
|
||||
using MassTransit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StackExchange.Redis;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using static MassTransit.Monitoring.Performance.BuiltInCounters;
|
||||
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class MessageService : IMessageSevice
|
||||
{
|
||||
private readonly ImContext _context;
|
||||
private readonly ILogger<MessageService> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
//废弃,此处已使用rabbitMQ替代
|
||||
//private readonly IEventBus _eventBus;
|
||||
private readonly IPublishEndpoint _endpoint;
|
||||
private readonly ISequenceIdService _sequenceIdService;
|
||||
private readonly IUserService _userService;
|
||||
public MessageService(
|
||||
ImContext context, ILogger<MessageService> logger, IMapper mapper,
|
||||
IPublishEndpoint publishEndpoint, ISequenceIdService sequenceIdService,
|
||||
IUserService userService
|
||||
)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_mapper = mapper;
|
||||
//_eventBus = eventBus;
|
||||
_endpoint = publishEndpoint;
|
||||
_sequenceIdService = sequenceIdService;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
public async Task<List<MessageBaseVo>> GetMessagesAsync(int userId,MessageQueryDto dto)
|
||||
{
|
||||
//获取会话信息,用于获取双方聊天的唯一标识streamkey
|
||||
Conversation? conversation = await _context.Conversations.FirstOrDefaultAsync(
|
||||
x => x.Id == dto.ConversationId && x.UserId == userId
|
||||
);
|
||||
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
|
||||
|
||||
var baseQuery = _context.Messages.Where(x => x.StreamKey == conversation.StreamKey);
|
||||
List<MessageBaseVo> messages = new List<MessageBaseVo>();
|
||||
if (dto.Direction == 0) // Before: 找比锚点小的,按倒序排
|
||||
{
|
||||
if (dto.Cursor.HasValue)
|
||||
baseQuery = baseQuery.Where(m => m.SequenceId < dto.Cursor.Value);
|
||||
|
||||
var list = await baseQuery
|
||||
.OrderByDescending(m => m.SequenceId) // 最新消息在最前
|
||||
.Take(dto.Limit)
|
||||
.Select(m => _mapper.Map<MessageBaseVo>(m))
|
||||
.ToListAsync();
|
||||
|
||||
messages = list.OrderBy(s => s.SequenceId).ToList();
|
||||
}
|
||||
else // After: 找比锚点大的,按正序排(用于补洞或刷新)
|
||||
{
|
||||
// 如果 Cursor 为空且是 After,逻辑上说不通,通常直接返回空或报错
|
||||
if (!dto.Cursor.HasValue) return new List<MessageBaseVo>();
|
||||
|
||||
messages = await baseQuery
|
||||
.Where(m => m.SequenceId > dto.Cursor.Value)
|
||||
.OrderBy(m => m.SequenceId) // 按时间线正序
|
||||
.Take(dto.Limit)
|
||||
.Select(m => _mapper.Map<MessageBaseVo>(m))
|
||||
.ToListAsync();
|
||||
}
|
||||
//取发送者信息,用于前端展示
|
||||
if(messages.Count > 0)
|
||||
{
|
||||
var ids = messages
|
||||
.Select(s => s.SenderId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
var userinfoList = await _userService.GetUserInfoListAsync(ids);
|
||||
// 转为字典,提高查询效率
|
||||
var userDict = userinfoList.ToDictionary(x => x.Id, x => x);
|
||||
|
||||
foreach (var item in messages)
|
||||
{
|
||||
if(userDict.TryGetValue(item.SenderId, out var user))
|
||||
{
|
||||
item.SenderName = user.NickName;
|
||||
item.SenderAvatar = user.Avatar ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
public Task<int> GetUnreadCountAsync(int userId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<List<MessageBaseDto>> GetUnreadMessagesAsync(int userId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<bool> MarkAsReadAsync(int userId, long messageId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<bool> MarkConversationAsReadAsync(int userId, int? userBId, int? groupId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<bool> RecallMessageAsync(int userId, int messageId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task MakeMessageAsync(Message message)
|
||||
{
|
||||
_context.Messages.Add(message);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
#region 发送群消息
|
||||
public async Task<MessageBaseVo> SendGroupMessageAsync(int senderId, int groupId, MessageBaseDto dto)
|
||||
{
|
||||
//判断群存在
|
||||
var isExist = await _context.Groups.AnyAsync(x => x.Id == groupId);
|
||||
if (!isExist) throw new BaseException(CodeDefine.GROUP_NOT_FOUND);
|
||||
//判断是否是群成员
|
||||
var isMember = await _context.GroupMembers.AnyAsync(x => x.GroupId == groupId && x.UserId == senderId);
|
||||
if (!isMember) throw new BaseException(CodeDefine.NO_GROUP_PERMISSION);
|
||||
var message = _mapper.Map<Message>(dto);
|
||||
message.StreamKey = StreamKeyBuilder.Group(groupId);
|
||||
message.SequenceId = await _sequenceIdService.GetNextSquenceIdAsync(message.StreamKey);
|
||||
await _endpoint.Publish(_mapper.Map<MessageCreatedEvent>(message));
|
||||
return _mapper.Map<MessageBaseVo>(message);
|
||||
|
||||
}
|
||||
#endregion
|
||||
#region 发送私聊消息
|
||||
public async Task<MessageBaseVo> SendPrivateMessageAsync(int senderId, int receiverId, MessageBaseDto dto)
|
||||
{
|
||||
bool isExist = await _context.Friends.AnyAsync(x => x.FriendId == receiverId);
|
||||
if (!isExist) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
|
||||
var message = _mapper.Map<Message>(dto);
|
||||
message.StreamKey = StreamKeyBuilder.Private(senderId, receiverId);
|
||||
message.SequenceId = await _sequenceIdService.GetNextSquenceIdAsync(message.StreamKey);
|
||||
await _endpoint.Publish(_mapper.Map<MessageCreatedEvent>(message));
|
||||
return _mapper.Map<MessageBaseVo>(message);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
using AutoMapper;
|
||||
using IM_API.Application.Interfaces;
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Dtos;
|
||||
using IM_API.Dtos.Message;
|
||||
using IM_API.Exceptions;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using IM_API.VOs.Message;
|
||||
using MassTransit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StackExchange.Redis;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using static MassTransit.Monitoring.Performance.BuiltInCounters;
|
||||
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class MessageService : IMessageSevice
|
||||
{
|
||||
private readonly ImContext _context;
|
||||
private readonly ILogger<MessageService> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
//废弃,此处已使用rabbitMQ替代
|
||||
//private readonly IEventBus _eventBus;
|
||||
private readonly IPublishEndpoint _endpoint;
|
||||
private readonly ISequenceIdService _sequenceIdService;
|
||||
private readonly IUserService _userService;
|
||||
public MessageService(
|
||||
ImContext context, ILogger<MessageService> logger, IMapper mapper,
|
||||
IPublishEndpoint publishEndpoint, ISequenceIdService sequenceIdService,
|
||||
IUserService userService
|
||||
)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_mapper = mapper;
|
||||
//_eventBus = eventBus;
|
||||
_endpoint = publishEndpoint;
|
||||
_sequenceIdService = sequenceIdService;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
public async Task<List<MessageBaseVo>> GetMessagesAsync(int userId,MessageQueryDto dto)
|
||||
{
|
||||
//获取会话信息,用于获取双方聊天的唯一标识streamkey
|
||||
Conversation? conversation = await _context.Conversations.FirstOrDefaultAsync(
|
||||
x => x.Id == dto.ConversationId && x.UserId == userId
|
||||
);
|
||||
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
|
||||
|
||||
var baseQuery = _context.Messages.Where(x => x.StreamKey == conversation.StreamKey);
|
||||
List<MessageBaseVo> messages = new List<MessageBaseVo>();
|
||||
if (dto.Direction == 0) // Before: 找比锚点小的,按倒序排
|
||||
{
|
||||
if (dto.Cursor.HasValue)
|
||||
baseQuery = baseQuery.Where(m => m.SequenceId < dto.Cursor.Value);
|
||||
|
||||
var list = await baseQuery
|
||||
.OrderByDescending(m => m.SequenceId) // 最新消息在最前
|
||||
.Take(dto.Limit)
|
||||
.Select(m => _mapper.Map<MessageBaseVo>(m))
|
||||
.ToListAsync();
|
||||
|
||||
messages = list.OrderBy(s => s.SequenceId).ToList();
|
||||
}
|
||||
else // After: 找比锚点大的,按正序排(用于补洞或刷新)
|
||||
{
|
||||
// 如果 Cursor 为空且是 After,逻辑上说不通,通常直接返回空或报错
|
||||
if (!dto.Cursor.HasValue) return new List<MessageBaseVo>();
|
||||
|
||||
messages = await baseQuery
|
||||
.Where(m => m.SequenceId > dto.Cursor.Value)
|
||||
.OrderBy(m => m.SequenceId) // 按时间线正序
|
||||
.Take(dto.Limit)
|
||||
.Select(m => _mapper.Map<MessageBaseVo>(m))
|
||||
.ToListAsync();
|
||||
}
|
||||
//取发送者信息,用于前端展示
|
||||
if(messages.Count > 0)
|
||||
{
|
||||
var ids = messages
|
||||
.Select(s => s.SenderId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
var userinfoList = await _userService.GetUserInfoListAsync(ids);
|
||||
// 转为字典,提高查询效率
|
||||
var userDict = userinfoList.ToDictionary(x => x.Id, x => x);
|
||||
|
||||
foreach (var item in messages)
|
||||
{
|
||||
if(userDict.TryGetValue(item.SenderId, out var user))
|
||||
{
|
||||
item.SenderName = user.NickName;
|
||||
item.SenderAvatar = user.Avatar ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
public Task<int> GetUnreadCountAsync(int userId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<List<MessageBaseDto>> GetUnreadMessagesAsync(int userId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<bool> MarkAsReadAsync(int userId, long messageId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<bool> MarkConversationAsReadAsync(int userId, int? userBId, int? groupId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<bool> RecallMessageAsync(int userId, int messageId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task MakeMessageAsync(Message message)
|
||||
{
|
||||
_context.Messages.Add(message);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
#region 发送群消息
|
||||
public async Task<MessageBaseVo> SendGroupMessageAsync(int senderId, int groupId, MessageBaseDto dto)
|
||||
{
|
||||
//判断群存在
|
||||
var isExist = await _context.Groups.AnyAsync(x => x.Id == groupId);
|
||||
if (!isExist) throw new BaseException(CodeDefine.GROUP_NOT_FOUND);
|
||||
//判断是否是群成员
|
||||
var isMember = await _context.GroupMembers.AnyAsync(x => x.GroupId == groupId && x.UserId == senderId);
|
||||
if (!isMember) throw new BaseException(CodeDefine.NO_GROUP_PERMISSION);
|
||||
var message = _mapper.Map<Message>(dto);
|
||||
message.StreamKey = StreamKeyBuilder.Group(groupId);
|
||||
message.SequenceId = await _sequenceIdService.GetNextSquenceIdAsync(message.StreamKey);
|
||||
await _endpoint.Publish(_mapper.Map<MessageCreatedEvent>(message));
|
||||
return _mapper.Map<MessageBaseVo>(message);
|
||||
|
||||
}
|
||||
#endregion
|
||||
#region 发送私聊消息
|
||||
public async Task<MessageBaseVo> SendPrivateMessageAsync(int senderId, int receiverId, MessageBaseDto dto)
|
||||
{
|
||||
bool isExist = await _context.Friends.AnyAsync(x => x.FriendId == receiverId);
|
||||
if (!isExist) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
|
||||
var message = _mapper.Map<Message>(dto);
|
||||
message.StreamKey = StreamKeyBuilder.Private(senderId, receiverId);
|
||||
message.SequenceId = await _sequenceIdService.GetNextSquenceIdAsync(message.StreamKey);
|
||||
await _endpoint.Publish(_mapper.Map<MessageCreatedEvent>(message));
|
||||
return _mapper.Map<MessageBaseVo>(message);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,62 @@
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class RedisCacheService:ICacheService
|
||||
{
|
||||
private readonly IDistributedCache _cache;
|
||||
public RedisCacheService(IDistributedCache cache)
|
||||
{
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public async Task<T?> GetAsync<T>(string key)
|
||||
{
|
||||
var valueBytes= await _cache.GetAsync(key);
|
||||
if (valueBytes is null || valueBytes.Length == 0) return default;
|
||||
return JsonSerializer.Deserialize<T>(valueBytes);
|
||||
}
|
||||
|
||||
public async Task<User?> GetUserCacheAsync(string username)
|
||||
{
|
||||
var usernameKey = RedisKeys.GetUserinfoKeyByUsername(username);
|
||||
var userid = await GetAsync<string>(usernameKey);
|
||||
if (userid is null) return default;
|
||||
var key = RedisKeys.GetUserinfoKey(userid);
|
||||
return await GetAsync<User>(key);
|
||||
}
|
||||
|
||||
public async Task RemoveAsync(string key) => await _cache.RemoveAsync(key);
|
||||
|
||||
public async Task RemoveUserCacheAsync(string username)
|
||||
{
|
||||
var usernameKey = RedisKeys.GetUserinfoKeyByUsername(username);
|
||||
var userid = await GetAsync<string>(usernameKey);
|
||||
if (userid is null) return;
|
||||
var key = RedisKeys.GetUserinfoKey(userid);
|
||||
await RemoveAsync(key);
|
||||
}
|
||||
|
||||
public async Task SetAsync<T>(string key, T value, TimeSpan? expiration = null)
|
||||
{
|
||||
var options = new DistributedCacheEntryOptions
|
||||
{
|
||||
AbsoluteExpirationRelativeToNow = expiration ?? TimeSpan.FromHours(1)
|
||||
};
|
||||
var valueBytes = JsonSerializer.SerializeToUtf8Bytes(value);
|
||||
await _cache.SetAsync(key, valueBytes, options);
|
||||
}
|
||||
|
||||
public async Task SetUserCacheAsync(User user)
|
||||
{
|
||||
var idKey = RedisKeys.GetUserinfoKey(user.Id.ToString());
|
||||
await SetAsync(idKey, user);
|
||||
var usernameKey = RedisKeys.GetUserinfoKeyByUsername(user.Username);
|
||||
await SetAsync(usernameKey, user.Id.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class RedisCacheService:ICacheService
|
||||
{
|
||||
private readonly IDistributedCache _cache;
|
||||
public RedisCacheService(IDistributedCache cache)
|
||||
{
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public async Task<T?> GetAsync<T>(string key)
|
||||
{
|
||||
var valueBytes= await _cache.GetAsync(key);
|
||||
if (valueBytes is null || valueBytes.Length == 0) return default;
|
||||
return JsonSerializer.Deserialize<T>(valueBytes);
|
||||
}
|
||||
|
||||
public async Task<User?> GetUserCacheAsync(string username)
|
||||
{
|
||||
var usernameKey = RedisKeys.GetUserinfoKeyByUsername(username);
|
||||
var userid = await GetAsync<string>(usernameKey);
|
||||
if (userid is null) return default;
|
||||
var key = RedisKeys.GetUserinfoKey(userid);
|
||||
return await GetAsync<User>(key);
|
||||
}
|
||||
|
||||
public async Task RemoveAsync(string key) => await _cache.RemoveAsync(key);
|
||||
|
||||
public async Task RemoveUserCacheAsync(string username)
|
||||
{
|
||||
var usernameKey = RedisKeys.GetUserinfoKeyByUsername(username);
|
||||
var userid = await GetAsync<string>(usernameKey);
|
||||
if (userid is null) return;
|
||||
var key = RedisKeys.GetUserinfoKey(userid);
|
||||
await RemoveAsync(key);
|
||||
}
|
||||
|
||||
public async Task SetAsync<T>(string key, T value, TimeSpan? expiration = null)
|
||||
{
|
||||
var options = new DistributedCacheEntryOptions
|
||||
{
|
||||
AbsoluteExpirationRelativeToNow = expiration ?? TimeSpan.FromHours(1)
|
||||
};
|
||||
var valueBytes = JsonSerializer.SerializeToUtf8Bytes(value);
|
||||
await _cache.SetAsync(key, valueBytes, options);
|
||||
}
|
||||
|
||||
public async Task SetUserCacheAsync(User user)
|
||||
{
|
||||
var idKey = RedisKeys.GetUserinfoKey(user.Id.ToString());
|
||||
await SetAsync(idKey, user);
|
||||
var usernameKey = RedisKeys.GetUserinfoKeyByUsername(user.Username);
|
||||
await SetAsync(usernameKey, user.Id.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
using IM_API.Interface.Services;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Newtonsoft.Json;
|
||||
using StackExchange.Redis;
|
||||
using System.Numerics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class RedisRefreshTokenService : IRefreshTokenService
|
||||
{
|
||||
private readonly ILogger<RedisRefreshTokenService> _logger;
|
||||
//redis数据库
|
||||
private readonly IDatabase _db;
|
||||
private IConfiguration configuration;
|
||||
//过期时长
|
||||
private readonly TimeSpan _refreshTTL;
|
||||
public RedisRefreshTokenService(ILogger<RedisRefreshTokenService> logger, IConnectionMultiplexer multiplexer, IConfiguration configuration)
|
||||
{
|
||||
_logger = logger;
|
||||
_db = multiplexer.GetDatabase();
|
||||
this.configuration = configuration;
|
||||
//设置refresh过期时间
|
||||
var days = int.Parse(this.configuration["Jwt:RefreshTokenDays"] ?? "30");
|
||||
_refreshTTL = TimeSpan.FromDays(days);
|
||||
}
|
||||
|
||||
private static string GenerateTokenStr()
|
||||
{
|
||||
var bytes = RandomNumberGenerator.GetBytes(32);
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
|
||||
public async Task<string> CreateRefreshTokenAsync(int userId, CancellationToken ct = default)
|
||||
{
|
||||
string token = GenerateTokenStr();
|
||||
var payload = new { UserId = userId,CreateAt = DateTime.Now};
|
||||
string json = JsonConvert.SerializeObject(payload);
|
||||
//token写入redis
|
||||
await _db.StringSetAsync(token,json,_refreshTTL);
|
||||
return token;
|
||||
}
|
||||
|
||||
public async Task RevokeRefreshTokenAsync(string token, CancellationToken ct = default)
|
||||
{
|
||||
await _db.KeyDeleteAsync(token);
|
||||
}
|
||||
|
||||
public async Task<(bool ok, int userId)> ValidateRefreshTokenAsync(string token, CancellationToken ct = default)
|
||||
{
|
||||
var json = await _db.StringGetAsync(token);
|
||||
if (json.IsNullOrEmpty) return (false,-1);
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(json.ToString());
|
||||
var userId = doc.RootElement.GetProperty("UserId").GetInt32();
|
||||
return (true,userId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return (false,-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using IM_API.Interface.Services;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Newtonsoft.Json;
|
||||
using StackExchange.Redis;
|
||||
using System.Numerics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class RedisRefreshTokenService : IRefreshTokenService
|
||||
{
|
||||
private readonly ILogger<RedisRefreshTokenService> _logger;
|
||||
//redis数据库
|
||||
private readonly IDatabase _db;
|
||||
private IConfiguration configuration;
|
||||
//过期时长
|
||||
private readonly TimeSpan _refreshTTL;
|
||||
public RedisRefreshTokenService(ILogger<RedisRefreshTokenService> logger, IConnectionMultiplexer multiplexer, IConfiguration configuration)
|
||||
{
|
||||
_logger = logger;
|
||||
_db = multiplexer.GetDatabase();
|
||||
this.configuration = configuration;
|
||||
//设置refresh过期时间
|
||||
var days = int.Parse(this.configuration["Jwt:RefreshTokenDays"] ?? "30");
|
||||
_refreshTTL = TimeSpan.FromDays(days);
|
||||
}
|
||||
|
||||
private static string GenerateTokenStr()
|
||||
{
|
||||
var bytes = RandomNumberGenerator.GetBytes(32);
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
|
||||
public async Task<string> CreateRefreshTokenAsync(int userId, CancellationToken ct = default)
|
||||
{
|
||||
string token = GenerateTokenStr();
|
||||
var payload = new { UserId = userId,CreateAt = DateTime.Now};
|
||||
string json = JsonConvert.SerializeObject(payload);
|
||||
//token写入redis
|
||||
await _db.StringSetAsync(token,json,_refreshTTL);
|
||||
return token;
|
||||
}
|
||||
|
||||
public async Task RevokeRefreshTokenAsync(string token, CancellationToken ct = default)
|
||||
{
|
||||
await _db.KeyDeleteAsync(token);
|
||||
}
|
||||
|
||||
public async Task<(bool ok, int userId)> ValidateRefreshTokenAsync(string token, CancellationToken ct = default)
|
||||
{
|
||||
var json = await _db.StringGetAsync(token);
|
||||
if (json.IsNullOrEmpty) return (false,-1);
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(json.ToString());
|
||||
var userId = doc.RootElement.GetProperty("UserId").GetInt32();
|
||||
return (true,userId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return (false,-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RedLockNet;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class SequenceIdService : ISequenceIdService
|
||||
{
|
||||
private IDatabase _database;
|
||||
private IDistributedLockFactory _lockFactory;
|
||||
private ImContext _context;
|
||||
public SequenceIdService(IConnectionMultiplexer connectionMultiplexer,
|
||||
IDistributedLockFactory distributedLockFactory, ImContext imContext)
|
||||
{
|
||||
_database = connectionMultiplexer.GetDatabase();
|
||||
_lockFactory = distributedLockFactory;
|
||||
_context = imContext;
|
||||
}
|
||||
public async Task<long> GetNextSquenceIdAsync(string streamKey)
|
||||
{
|
||||
string key = RedisKeys.GetSequenceIdKey(streamKey);
|
||||
string lockKey = RedisKeys.GetSequenceIdLockKey(streamKey);
|
||||
var exists = await _database.KeyExistsAsync(key);
|
||||
if (!exists)
|
||||
{
|
||||
using (var _lock = await _lockFactory.CreateLockAsync(lockKey, TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
if (_lock.IsAcquired)
|
||||
{
|
||||
if(!await _database.KeyExistsAsync(key))
|
||||
{
|
||||
var max = await _context.Messages
|
||||
.Where(x => x.StreamKey == streamKey)
|
||||
.MaxAsync(m => (long?)m.SequenceId) ?? 0;
|
||||
await _database.StringSetAsync(key, max, TimeSpan.FromDays(7));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return await _database.StringIncrementAsync(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RedLockNet;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class SequenceIdService : ISequenceIdService
|
||||
{
|
||||
private IDatabase _database;
|
||||
private IDistributedLockFactory _lockFactory;
|
||||
private ImContext _context;
|
||||
public SequenceIdService(IConnectionMultiplexer connectionMultiplexer,
|
||||
IDistributedLockFactory distributedLockFactory, ImContext imContext)
|
||||
{
|
||||
_database = connectionMultiplexer.GetDatabase();
|
||||
_lockFactory = distributedLockFactory;
|
||||
_context = imContext;
|
||||
}
|
||||
public async Task<long> GetNextSquenceIdAsync(string streamKey)
|
||||
{
|
||||
string key = RedisKeys.GetSequenceIdKey(streamKey);
|
||||
string lockKey = RedisKeys.GetSequenceIdLockKey(streamKey);
|
||||
var exists = await _database.KeyExistsAsync(key);
|
||||
if (!exists)
|
||||
{
|
||||
using (var _lock = await _lockFactory.CreateLockAsync(lockKey, TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
if (_lock.IsAcquired)
|
||||
{
|
||||
if(!await _database.KeyExistsAsync(key))
|
||||
{
|
||||
var max = await _context.Messages
|
||||
.Where(x => x.StreamKey == streamKey)
|
||||
.MaxAsync(m => (long?)m.SequenceId) ?? 0;
|
||||
await _database.StringSetAsync(key, max, TimeSpan.FromDays(7));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return await _database.StringIncrementAsync(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,129 +1,129 @@
|
||||
using AutoMapper;
|
||||
using IM_API.Dtos.User;
|
||||
using IM_API.Exceptions;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StackExchange.Redis;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class UserService : IUserService
|
||||
{
|
||||
private readonly ImContext _context;
|
||||
private readonly ILogger<UserService> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ICacheService _cacheService;
|
||||
private readonly IDatabase _redis;
|
||||
public UserService(
|
||||
ImContext imContext,ILogger<UserService> logger,
|
||||
IMapper mapper, ICacheService cacheService, IConnectionMultiplexer connectionMultiplexer)
|
||||
{
|
||||
this._context = imContext;
|
||||
this._logger = logger;
|
||||
this._mapper = mapper;
|
||||
_cacheService = cacheService;
|
||||
_redis = connectionMultiplexer.GetDatabase();
|
||||
}
|
||||
#region 获取用户信息
|
||||
public async Task<UserInfoDto> GetUserInfoAsync(int userId)
|
||||
{
|
||||
//查询redis缓存,如果存在直接返回不走查库逻辑
|
||||
var key = RedisKeys.GetUserinfoKey(userId.ToString());
|
||||
var userinfoCache = await _cacheService.GetAsync<User>(key);
|
||||
if (userinfoCache != null) return _mapper.Map<UserInfoDto>(userinfoCache);
|
||||
//无缓存查库
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(x => x.Id == userId);
|
||||
if (user == null)
|
||||
{
|
||||
throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
}
|
||||
await _cacheService.SetUserCacheAsync(user);
|
||||
return _mapper.Map<UserInfoDto>(user);
|
||||
}
|
||||
#endregion
|
||||
#region 通过用户名获取用户信息
|
||||
public async Task<UserInfoDto> GetUserInfoByUsernameAsync(string username)
|
||||
{
|
||||
var userinfo = await _cacheService.GetUserCacheAsync(username);
|
||||
if (userinfo != null) return _mapper.Map<UserInfoDto>(userinfo);
|
||||
var user = await _context.Users.FirstOrDefaultAsync(x => x.Username == username);
|
||||
if (user == null)
|
||||
{
|
||||
throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
}
|
||||
await _cacheService.SetUserCacheAsync(user);
|
||||
return _mapper.Map<UserInfoDto>(user);
|
||||
}
|
||||
#endregion
|
||||
#region 重置用户密码
|
||||
public async Task<bool> ResetPasswordAsync(int userId, string oldPassword, string password)
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(x => x.Id == userId);
|
||||
if (user is null) throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
//验证原密码
|
||||
if (user.Password != oldPassword) throw new BaseException(CodeDefine.PASSWORD_ERROR);
|
||||
user.Password = password;
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
#region 更新用户在线状态
|
||||
public async Task<bool> UpdateOlineStatusAsync(int userId,UserOnlineStatus onlineStatus)
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(x => x.Id == userId);
|
||||
if (user is null) throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
user.OnlineStatusEnum = onlineStatus;
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
#region 更新用户信息
|
||||
public async Task<UserInfoDto> UpdateUserAsync(int userId,UpdateUserDto dto)
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(x => x.Id == userId);
|
||||
if (user is null) throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
_mapper.Map(dto,user);
|
||||
await _context.SaveChangesAsync();
|
||||
await _cacheService.SetUserCacheAsync(user);
|
||||
return _mapper.Map<UserInfoDto>(user);
|
||||
}
|
||||
#endregion
|
||||
#region 批量获取用户信息
|
||||
public async Task<List<UserInfoDto>> GetUserInfoListAsync(List<int> ids)
|
||||
{
|
||||
//读取缓存中存在的用户,存在直接添加到结果列表
|
||||
var idKeyArr = ids.Select(s => (RedisKey)RedisKeys.GetUserinfoKey(s.ToString())).ToArray();
|
||||
var values = await _redis.StringGetAsync(idKeyArr);
|
||||
List<User> results = [];
|
||||
List<int> missingIds = [];
|
||||
for(int i = 0; i < ids.Count; i++)
|
||||
{
|
||||
if (values[i].HasValue)
|
||||
{
|
||||
results.Add(JsonSerializer.Deserialize<User>(values[i]));
|
||||
}
|
||||
else
|
||||
{
|
||||
missingIds.Add(ids[i]);
|
||||
}
|
||||
}
|
||||
//如果存在没有缓存的用户则进行查库
|
||||
if (missingIds.Any())
|
||||
{
|
||||
var dbUsers = await _context.Users
|
||||
.Where(x => ids.Contains(x.Id)).ToListAsync();
|
||||
|
||||
results.AddRange(dbUsers);
|
||||
|
||||
var setTasks = dbUsers.Select(s => _cacheService.SetUserCacheAsync(s)).ToList();
|
||||
await Task.WhenAll(setTasks);
|
||||
}
|
||||
return _mapper.Map<List<UserInfoDto>>(results);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
using AutoMapper;
|
||||
using IM_API.Dtos.User;
|
||||
using IM_API.Exceptions;
|
||||
using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StackExchange.Redis;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
public class UserService : IUserService
|
||||
{
|
||||
private readonly ImContext _context;
|
||||
private readonly ILogger<UserService> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ICacheService _cacheService;
|
||||
private readonly IDatabase _redis;
|
||||
public UserService(
|
||||
ImContext imContext,ILogger<UserService> logger,
|
||||
IMapper mapper, ICacheService cacheService, IConnectionMultiplexer connectionMultiplexer)
|
||||
{
|
||||
this._context = imContext;
|
||||
this._logger = logger;
|
||||
this._mapper = mapper;
|
||||
_cacheService = cacheService;
|
||||
_redis = connectionMultiplexer.GetDatabase();
|
||||
}
|
||||
#region 获取用户信息
|
||||
public async Task<UserInfoDto> GetUserInfoAsync(int userId)
|
||||
{
|
||||
//查询redis缓存,如果存在直接返回不走查库逻辑
|
||||
var key = RedisKeys.GetUserinfoKey(userId.ToString());
|
||||
var userinfoCache = await _cacheService.GetAsync<User>(key);
|
||||
if (userinfoCache != null) return _mapper.Map<UserInfoDto>(userinfoCache);
|
||||
//无缓存查库
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(x => x.Id == userId);
|
||||
if (user == null)
|
||||
{
|
||||
throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
}
|
||||
await _cacheService.SetUserCacheAsync(user);
|
||||
return _mapper.Map<UserInfoDto>(user);
|
||||
}
|
||||
#endregion
|
||||
#region 通过用户名获取用户信息
|
||||
public async Task<UserInfoDto> GetUserInfoByUsernameAsync(string username)
|
||||
{
|
||||
var userinfo = await _cacheService.GetUserCacheAsync(username);
|
||||
if (userinfo != null) return _mapper.Map<UserInfoDto>(userinfo);
|
||||
var user = await _context.Users.FirstOrDefaultAsync(x => x.Username == username);
|
||||
if (user == null)
|
||||
{
|
||||
throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
}
|
||||
await _cacheService.SetUserCacheAsync(user);
|
||||
return _mapper.Map<UserInfoDto>(user);
|
||||
}
|
||||
#endregion
|
||||
#region 重置用户密码
|
||||
public async Task<bool> ResetPasswordAsync(int userId, string oldPassword, string password)
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(x => x.Id == userId);
|
||||
if (user is null) throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
//验证原密码
|
||||
if (user.Password != oldPassword) throw new BaseException(CodeDefine.PASSWORD_ERROR);
|
||||
user.Password = password;
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
#region 更新用户在线状态
|
||||
public async Task<bool> UpdateOlineStatusAsync(int userId,UserOnlineStatus onlineStatus)
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(x => x.Id == userId);
|
||||
if (user is null) throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
user.OnlineStatusEnum = onlineStatus;
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
#region 更新用户信息
|
||||
public async Task<UserInfoDto> UpdateUserAsync(int userId,UpdateUserDto dto)
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(x => x.Id == userId);
|
||||
if (user is null) throw new BaseException(CodeDefine.USER_NOT_FOUND);
|
||||
_mapper.Map(dto,user);
|
||||
await _context.SaveChangesAsync();
|
||||
await _cacheService.SetUserCacheAsync(user);
|
||||
return _mapper.Map<UserInfoDto>(user);
|
||||
}
|
||||
#endregion
|
||||
#region 批量获取用户信息
|
||||
public async Task<List<UserInfoDto>> GetUserInfoListAsync(List<int> ids)
|
||||
{
|
||||
//读取缓存中存在的用户,存在直接添加到结果列表
|
||||
var idKeyArr = ids.Select(s => (RedisKey)RedisKeys.GetUserinfoKey(s.ToString())).ToArray();
|
||||
var values = await _redis.StringGetAsync(idKeyArr);
|
||||
List<User> results = [];
|
||||
List<int> missingIds = [];
|
||||
for(int i = 0; i < ids.Count; i++)
|
||||
{
|
||||
if (values[i].HasValue)
|
||||
{
|
||||
results.Add(JsonSerializer.Deserialize<User>(values[i]));
|
||||
}
|
||||
else
|
||||
{
|
||||
missingIds.Add(ids[i]);
|
||||
}
|
||||
}
|
||||
//如果存在没有缓存的用户则进行查库
|
||||
if (missingIds.Any())
|
||||
{
|
||||
var dbUsers = await _context.Users
|
||||
.Where(x => ids.Contains(x.Id)).ToListAsync();
|
||||
|
||||
results.AddRange(dbUsers);
|
||||
|
||||
var setTasks = dbUsers.Select(s => _cacheService.SetUserCacheAsync(s)).ToList();
|
||||
await Task.WhenAll(setTasks);
|
||||
}
|
||||
return _mapper.Map<List<UserInfoDto>>(results);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user