前端:
1、优化消息排序逻辑 2、新增加载历史消息 3、修复已知问题 后端: 1、优化消息排序逻辑 2、增加用户信息缓存机制 3、修改日期类型为DateTimeOffset改善时区信息丢失问题 3、修复了已知问题 数据库: 1、新增SequenceId字段用于消息排序 2、新增ClientMsgId字段用于客户端消息回执
This commit is contained in:
@@ -14,15 +14,19 @@ namespace IM_API.Services
|
||||
private readonly ImContext _context;
|
||||
private readonly ILogger<AuthService> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
public AuthService(ImContext context, ILogger<AuthService> logger, 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);
|
||||
@@ -30,6 +34,7 @@ namespace IM_API.Services
|
||||
{
|
||||
throw new BaseException(CodeDefine.PASSWORD_ERROR);
|
||||
}
|
||||
await _cache.SetUserCacheAsync(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ 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
|
||||
@@ -27,26 +28,26 @@ namespace IM_API.Services
|
||||
|
||||
#endregion
|
||||
#region 获取用户会话列表
|
||||
public async Task<List<ConversationDto>> GetConversationsAsync(int userId)
|
||||
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 == (int)ChatType.PRIVATE
|
||||
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 == (int)ChatType.GROUP
|
||||
where c.UserId == userId && c.ChatType == ChatType.GROUP
|
||||
select new { c, g.Avatar, g.Name })
|
||||
.ToListAsync();
|
||||
|
||||
var privateDtos = privateList.Select(x =>
|
||||
{
|
||||
var dto = _mapper.Map<ConversationDto>(x.c);
|
||||
var dto = _mapper.Map<ConversationVo>(x.c);
|
||||
dto.TargetAvatar = x.Avatar;
|
||||
dto.TargetName = x.RemarkName;
|
||||
return dto;
|
||||
@@ -54,7 +55,7 @@ namespace IM_API.Services
|
||||
|
||||
var groupDtos = groupList.Select(x =>
|
||||
{
|
||||
var dto = _mapper.Map<ConversationDto>(x.c);
|
||||
var dto = _mapper.Map<ConversationVo>(x.c);
|
||||
dto.TargetAvatar = x.Avatar;
|
||||
dto.TargetName = x.Name;
|
||||
return dto;
|
||||
@@ -89,25 +90,23 @@ namespace IM_API.Services
|
||||
#endregion
|
||||
|
||||
#region 获取单个会话信息
|
||||
public async Task<ConversationDto> GetConversationByIdAsync(int userId, int conversationId)
|
||||
public async Task<ConversationVo> GetConversationByIdAsync(int userId, int conversationId)
|
||||
{
|
||||
var conversation = await _context.Conversations
|
||||
.Include(x => x.LastReadMessage)
|
||||
.FirstOrDefaultAsync(
|
||||
x => x.UserId == userId && x.Id == conversationId
|
||||
);
|
||||
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
|
||||
var dto = _mapper.Map<ConversationDto>(conversation);
|
||||
//dto.LastReadMessage = _mapper.Map<MessageBaseDto>(conversation);
|
||||
if(conversation.ChatType == (int)ChatType.PRIVATE)
|
||||
var dto = _mapper.Map<ConversationVo>(conversation);
|
||||
if(conversation.ChatType == ChatType.PRIVATE)
|
||||
{
|
||||
var friendInfo = await _context.Friends.FirstOrDefaultAsync(
|
||||
x => x.UserId == userId && x.FriendId == conversation.TargetId
|
||||
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 == (int)ChatType.GROUP)
|
||||
if(conversation.ChatType == ChatType.GROUP)
|
||||
{
|
||||
var groupInfo = await _context.Groups.FirstOrDefaultAsync(
|
||||
x => x.Id == conversation.TargetId
|
||||
@@ -125,12 +124,18 @@ namespace IM_API.Services
|
||||
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.Id)
|
||||
.OrderByDescending(x => x.SequenceId)
|
||||
.FirstOrDefaultAsync();
|
||||
conversation.LastReadMessage = message;
|
||||
conversation.UnreadCount = 0;
|
||||
_context.Conversations.Update(conversation);
|
||||
await _context.SaveChangesAsync();
|
||||
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;
|
||||
|
||||
}
|
||||
@@ -143,10 +148,10 @@ namespace IM_API.Services
|
||||
StreamKeyBuilder.Private(userAId, userBId) : StreamKeyBuilder.Group(userBId);
|
||||
var conversation = new Conversation()
|
||||
{
|
||||
ChatType = (int)chatType,
|
||||
ChatType = chatType,
|
||||
LastMessage = "",
|
||||
LastMessageTime = DateTime.UtcNow,
|
||||
LastReadMessageId = -1,
|
||||
LastMessageTime = DateTime.Now,
|
||||
LastReadSequenceId = null,
|
||||
StreamKey = streamKey,
|
||||
TargetId = userBId,
|
||||
UnreadCount = 0,
|
||||
@@ -156,5 +161,19 @@ namespace IM_API.Services
|
||||
_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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,8 +132,8 @@ namespace IM_API.Services
|
||||
await _endpoint.Publish(new FriendAddEvent()
|
||||
{
|
||||
AggregateId = friendRequest.Id.ToString(),
|
||||
OccurredAt = DateTime.UtcNow,
|
||||
Created = DateTime.UtcNow,
|
||||
OccurredAt = DateTime.Now,
|
||||
Created = DateTime.Now,
|
||||
EventId = Guid.NewGuid(),
|
||||
OperatorId = friendRequest.ResponseUser,
|
||||
RequestInfo = _mapper.Map<FriendRequestDto>(friendRequest),
|
||||
@@ -183,7 +183,7 @@ namespace IM_API.Services
|
||||
await _endpoint.Publish(new RequestFriendEvent()
|
||||
{
|
||||
AggregateId = friendRequst.Id.ToString(),
|
||||
OccurredAt = friendRequst.Created,
|
||||
OccurredAt = friendRequst.Created.UtcDateTime,
|
||||
Description = friendRequst.Description,
|
||||
EventId = Guid.NewGuid(),
|
||||
FromUserId = friendRequst.RequestUser,
|
||||
@@ -205,7 +205,7 @@ namespace IM_API.Services
|
||||
Friend friendA = new Friend()
|
||||
{
|
||||
Avatar = userbInfo.Avatar,
|
||||
Created = DateTime.UtcNow,
|
||||
Created = DateTime.Now,
|
||||
FriendId = userbInfo.Id,
|
||||
RemarkName = remarkName ?? userbInfo.NickName,
|
||||
StatusEnum = FriendStatus.Added,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Dtos.Group;
|
||||
using IM_API.Exceptions;
|
||||
@@ -27,7 +28,7 @@ namespace IM_API.Services
|
||||
|
||||
private async Task<List<GroupInvite>> GetGroupInvites(int userId, int groupId, List<int> ids)
|
||||
{
|
||||
DateTime dateTime = DateTime.UtcNow;
|
||||
DateTime dateTime = DateTime.Now;
|
||||
//验证被邀请用户是否为好友
|
||||
var validFriendIds = await _context.Friends
|
||||
.Where(f => f.UserId == userId && ids.Contains(f.FriendId))
|
||||
@@ -44,13 +45,14 @@ namespace IM_API.Services
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task<GroupInfoDto> CreateGroup(int userId, GroupCreateDto groupCreateDto, List<int> userIds)
|
||||
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.UtcNow;
|
||||
DateTime dateTime = DateTime.Now;
|
||||
Group group = _mapper.Map<Group>(groupCreateDto);
|
||||
group.GroupMaster = userId;
|
||||
_context.Groups.Add(group);
|
||||
@@ -90,21 +92,38 @@ namespace IM_API.Services
|
||||
}
|
||||
}
|
||||
|
||||
public Task DeleteGroup(int userId, int groupId)
|
||||
public Task DeleteGroupAsync(int userId, int groupId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task InviteUsers(int userId, int groupId, List<int> userIds)
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
public Task JoinGroup(int userId, int groupId)
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace IM_API.Services
|
||||
|
||||
public (string token, DateTime expiresAt) CreateAccessTokenForUser(int userId, string username, string role)
|
||||
{
|
||||
var expiresAt = DateTime.UtcNow.AddMinutes(_accessMinutes);
|
||||
var expiresAt = DateTime.Now.AddMinutes(_accessMinutes);
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
||||
|
||||
@@ -2,12 +2,18 @@
|
||||
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.RegularExpressions;
|
||||
using static MassTransit.Monitoring.Performance.BuiltInCounters;
|
||||
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
@@ -19,9 +25,10 @@ namespace IM_API.Services
|
||||
//废弃,此处已使用rabbitMQ替代
|
||||
//private readonly IEventBus _eventBus;
|
||||
private readonly IPublishEndpoint _endpoint;
|
||||
private readonly ISequenceIdService _sequenceIdService;
|
||||
public MessageService(
|
||||
ImContext context, ILogger<MessageService> logger, IMapper mapper, IEventBus eventBus,
|
||||
IPublishEndpoint publishEndpoint
|
||||
ImContext context, ILogger<MessageService> logger, IMapper mapper,
|
||||
IPublishEndpoint publishEndpoint, ISequenceIdService sequenceIdService
|
||||
)
|
||||
{
|
||||
_context = context;
|
||||
@@ -29,40 +36,43 @@ namespace IM_API.Services
|
||||
_mapper = mapper;
|
||||
//_eventBus = eventBus;
|
||||
_endpoint = publishEndpoint;
|
||||
_sequenceIdService = sequenceIdService;
|
||||
}
|
||||
|
||||
public async Task<List<MessageBaseDto>> GetMessagesAsync(int userId, int conversationId, int? msgId, int? pageSize, bool desc)
|
||||
public async Task<List<MessageBaseVo>> GetMessagesAsync(int userId,MessageQueryDto dto)
|
||||
{
|
||||
//获取会话信息,用于获取双方聊天的唯一标识streamkey
|
||||
Conversation? conversation = await _context.Conversations.FirstOrDefaultAsync(
|
||||
x => x.Id == conversationId && x.UserId == userId
|
||||
x => x.Id == dto.ConversationId && x.UserId == userId
|
||||
);
|
||||
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
|
||||
var query = _context.Messages.AsQueryable();
|
||||
if(msgId != null)
|
||||
|
||||
var baseQuery = _context.Messages.Where(x => x.StreamKey == conversation.StreamKey);
|
||||
if (dto.Direction == 0) // Before: 找比锚点小的,按倒序排
|
||||
{
|
||||
query = query.Where(
|
||||
x => x.StreamKey == conversation.StreamKey && x.Id < msgId.Value
|
||||
)
|
||||
.OrderByDescending(x => x.Id);
|
||||
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();
|
||||
|
||||
return list.OrderBy(s => s.SequenceId).ToList();
|
||||
}
|
||||
else
|
||||
else // After: 找比锚点大的,按正序排(用于补洞或刷新)
|
||||
{
|
||||
query = query.Where(
|
||||
x => x.StreamKey == conversation.StreamKey && x.Id > conversation.LastReadMessageId
|
||||
);
|
||||
// 如果 Cursor 为空且是 After,逻辑上说不通,通常直接返回空或报错
|
||||
if (!dto.Cursor.HasValue) return new List<MessageBaseVo>();
|
||||
|
||||
return await baseQuery
|
||||
.Where(m => m.SequenceId > dto.Cursor.Value)
|
||||
.OrderBy(m => m.SequenceId) // 按时间线正序
|
||||
.Take(dto.Limit)
|
||||
.Select(m => _mapper.Map<MessageBaseVo>(m))
|
||||
.ToListAsync();
|
||||
}
|
||||
if(pageSize != null)
|
||||
{
|
||||
query = query.Take(pageSize.Value);
|
||||
}
|
||||
var msgList = await query
|
||||
.ToListAsync();
|
||||
msgList = msgList
|
||||
.OrderBy(x => x.Created)
|
||||
.ThenBy(t => t.Id)
|
||||
.ToList();
|
||||
return _mapper.Map<List<MessageBaseDto>>(msgList);
|
||||
}
|
||||
|
||||
public Task<int> GetUnreadCountAsync(int userId)
|
||||
@@ -89,8 +99,14 @@ namespace IM_API.Services
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task MakeMessageAsync(Message message)
|
||||
{
|
||||
_context.Messages.Add(message);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
#region 发送群消息
|
||||
public async Task<MessageBaseDto> SendGroupMessageAsync(int senderId, int groupId, MessageBaseDto dto)
|
||||
public async Task<MessageBaseVo> SendGroupMessageAsync(int senderId, int groupId, MessageBaseDto dto)
|
||||
{
|
||||
//判断群存在
|
||||
var isExist = await _context.Groups.AnyAsync(x => x.Id == groupId);
|
||||
@@ -99,30 +115,23 @@ namespace IM_API.Services
|
||||
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.Sender = senderId;
|
||||
message.StreamKey = StreamKeyBuilder.Group(
|
||||
|
||||
groupId);
|
||||
_context.Messages.Add(message);
|
||||
await _context.SaveChangesAsync();
|
||||
message.StreamKey = StreamKeyBuilder.Group(groupId);
|
||||
message.SequenceId = await _sequenceIdService.GetNextSquenceIdAsync(message.StreamKey);
|
||||
await _endpoint.Publish(_mapper.Map<MessageCreatedEvent>(message));
|
||||
return _mapper.Map<MessageBaseDto>(message);
|
||||
return _mapper.Map<MessageBaseVo>(message);
|
||||
|
||||
}
|
||||
#endregion
|
||||
#region 发送私聊消息
|
||||
public async Task<MessageBaseDto> SendPrivateMessageAsync(int senderId, int receiverId, MessageBaseDto dto)
|
||||
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.Sender = senderId;
|
||||
//生成消息流唯一标识符
|
||||
message.StreamKey = StreamKeyBuilder.Private(dto.SenderId, dto.ReceiverId);
|
||||
_context.Messages.Add(message);
|
||||
await _context.SaveChangesAsync();
|
||||
message.StreamKey = StreamKeyBuilder.Private(senderId, receiverId);
|
||||
message.SequenceId = await _sequenceIdService.GetNextSquenceIdAsync(message.StreamKey);
|
||||
await _endpoint.Publish(_mapper.Map<MessageCreatedEvent>(message));
|
||||
return _mapper.Map<MessageBaseDto>(message);
|
||||
return _mapper.Map<MessageBaseVo>(message);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -0,0 +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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using IM_API.Interface.Services;
|
||||
using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
@@ -13,31 +14,43 @@ namespace IM_API.Services
|
||||
private readonly ImContext _context;
|
||||
private readonly ILogger<UserService> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
public UserService(ImContext imContext,ILogger<UserService> logger,IMapper mapper)
|
||||
private readonly ICacheService _cacheService;
|
||||
public UserService(ImContext imContext,ILogger<UserService> logger,IMapper mapper, ICacheService cacheService)
|
||||
{
|
||||
this._context = imContext;
|
||||
this._logger = logger;
|
||||
this._mapper = mapper;
|
||||
_cacheService = cacheService;
|
||||
}
|
||||
#region 获取用户信息
|
||||
public async Task<UserInfoDto> GetUserInfoAsync(int userId)
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(x => x.Id == 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
|
||||
@@ -70,6 +83,7 @@ namespace IM_API.Services
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user