Merge pull request '222' (#62) from feature-nxdev into main
Reviewed-on: #62
This commit is contained in:
commit
95165be2a9
28
.gitea/ISSUE_TEMPLATE/bug.yaml
Normal file
28
.gitea/ISSUE_TEMPLATE/bug.yaml
Normal file
@ -0,0 +1,28 @@
|
||||
name: "IM 缺陷报告"
|
||||
description: "记录聊天消息、SignalR 或缓存相关的问题"
|
||||
title: "[BUG]简要描述问题"
|
||||
labels: ["bug", "high-priority"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "请详细填写 Bug 信息,这将有助于快速定位后端 Redis 或 SignalR 的问题。"
|
||||
- type: input
|
||||
id: stream_key
|
||||
attributes:
|
||||
label: "相关 StreamKey / 群组 ID"
|
||||
placeholder: "例如:group:123"
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: "复现步骤"
|
||||
value: |
|
||||
1. 登录用户 A 和用户 B
|
||||
2. 用户 A 向群组发送消息
|
||||
3. 用户 B 界面没有反应
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: "控制台错误日志 (Console Log)"
|
||||
placeholder: "在此粘贴浏览器或后端的报错信息..."
|
||||
@ -17,33 +17,47 @@ namespace IM_API.Application.EventHandlers.MessageCreatedHandler
|
||||
{
|
||||
private readonly IConversationService _conversationService;
|
||||
private readonly ILogger<ConversationEventHandler> _logger;
|
||||
private readonly ImContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IUserService _userSerivce;
|
||||
public ConversationEventHandler(
|
||||
IConversationService conversationService,
|
||||
ILogger<ConversationEventHandler> logger,
|
||||
ImContext imContext,
|
||||
IMapper mapper
|
||||
IUserService userService
|
||||
)
|
||||
{
|
||||
_conversationService = conversationService;
|
||||
_logger = logger;
|
||||
_context = imContext;
|
||||
_mapper = mapper;
|
||||
_userSerivce = userService;
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<MessageCreatedEvent> context)
|
||||
{
|
||||
var @event = context.Message;
|
||||
await _conversationService.UpdateConversationAfterSentAsync(new Dtos.Conversation.UpdateConversationDto
|
||||
if (@event.ChatType == ChatType.GROUP)
|
||||
{
|
||||
LastMessage = @event.MessageContent,
|
||||
LastSequenceId = @event.SequenceId,
|
||||
ReceiptId = @event.MsgRecipientId,
|
||||
SenderId = @event.MsgSenderId,
|
||||
StreamKey = @event.StreamKey,
|
||||
DateTime = @event.MessageCreated
|
||||
});
|
||||
var userinfo = await _userSerivce.GetUserInfoAsync(@event.MsgSenderId);
|
||||
await _conversationService.UpdateConversationAfterSentAsync(new Dtos.Conversation.UpdateConversationDto
|
||||
{
|
||||
LastMessage = $"{userinfo.NickName}:{@event.MessageContent}",
|
||||
LastSequenceId = @event.SequenceId,
|
||||
ReceiptId = @event.MsgRecipientId,
|
||||
SenderId = @event.MsgSenderId,
|
||||
StreamKey = @event.StreamKey,
|
||||
DateTime = @event.MessageCreated
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
await _conversationService.UpdateConversationAfterSentAsync(new Dtos.Conversation.UpdateConversationDto
|
||||
{
|
||||
LastMessage = @event.MessageContent,
|
||||
LastSequenceId = @event.SequenceId,
|
||||
ReceiptId = @event.MsgRecipientId,
|
||||
SenderId = @event.MsgSenderId,
|
||||
StreamKey = @event.StreamKey,
|
||||
DateTime = @event.MessageCreated
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -25,29 +25,17 @@ namespace IM_API.Application.EventHandlers.MessageCreatedHandler
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
var @event = context.Message;
|
||||
Console.WriteLine($"[SignalR]handlerCreated!");
|
||||
try
|
||||
{
|
||||
// 先转成实体,如果这一步都报错,说明之前的 sbyte -> enum 还没改好
|
||||
var entity = _mapper.Map<Message>(@event);
|
||||
|
||||
// 再从实体转 VO,这是最稳妥的路径,因为这两者的映射你肯定配过了
|
||||
var messageBaseVo = _mapper.Map<MessageBaseVo>(entity);
|
||||
|
||||
// 2. 打印日志确认逻辑执行到这里了
|
||||
Console.WriteLine($"[SignalR] 准备向所有人广播消息: {messageBaseVo.Content}");
|
||||
|
||||
// 3. 执行广播
|
||||
await _hub.Clients.User(@event.MsgRecipientId.ToString()).SendAsync("ReceiveMessage", new HubResponse<MessageBaseVo>("Event", messageBaseVo));
|
||||
|
||||
Console.WriteLine("[SignalR] 广播指令已发出");
|
||||
Console.ResetColor();
|
||||
await _hub.Clients.Group(@event.StreamKey).SendAsync("ReceiveMessage", new HubResponse<MessageBaseVo>("Event", messageBaseVo));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[SignalR] 发送失败: {ex.Message}");
|
||||
Console.ResetColor();
|
||||
throw; // 抛出异常触发 MassTransit 重试
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ using IM_API.Tools;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
|
||||
@ -45,7 +46,7 @@ namespace IM_API.Controllers
|
||||
{
|
||||
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
int userId = int.Parse(userIdStr);
|
||||
var userinfo = await _userService.UpdateUserAsync(userId,dto);
|
||||
var userinfo = await _userService.UpdateUserAsync(userId, dto);
|
||||
var res = new BaseResponse<UserInfoDto>(userinfo);
|
||||
return Ok(res);
|
||||
|
||||
@ -84,7 +85,7 @@ namespace IM_API.Controllers
|
||||
{
|
||||
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
int userId = int.Parse(userIdStr);
|
||||
await _userService.ResetPasswordAsync(userId,dto.OldPassword,dto.Password);
|
||||
await _userService.ResetPasswordAsync(userId, dto.OldPassword, dto.Password);
|
||||
return Ok(new BaseResponse<object?>());
|
||||
}
|
||||
/// <summary>
|
||||
@ -100,5 +101,14 @@ namespace IM_API.Controllers
|
||||
await _userService.UpdateOlineStatusAsync(userId, dto.OnlineStatus);
|
||||
return Ok(new BaseResponse<object?>());
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(BaseResponse<List<UserInfoDto>>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetUserList([FromBody][Required]List<int> ids)
|
||||
{
|
||||
var users = await _userService.GetUserInfoListAsync(ids);
|
||||
var res = new BaseResponse<List<UserInfoDto>>(users);
|
||||
return Ok(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,9 +35,9 @@ namespace IM_API.Hubs
|
||||
//将用户加入已加入聊天的会话组
|
||||
string userIdStr = Context.User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var keys = await _conversationService.GetUserAllStreamKeyAsync(int.Parse(userIdStr));
|
||||
foreach(var key in keys)
|
||||
foreach (var key in keys)
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId,key);
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, key);
|
||||
}
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
@ -35,5 +35,11 @@ namespace IM_API.Interface.Services
|
||||
/// <param name="onlineStatus"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> UpdateOlineStatusAsync(int userId, UserOnlineStatus onlineStatus);
|
||||
/// <summary>
|
||||
/// 批量获取用户信息
|
||||
/// </summary>
|
||||
/// <param name="ids">用户id列表</param>
|
||||
/// <returns></returns>
|
||||
Task<List<UserInfoDto>> GetUserInfoListAsync(List<int> ids);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StackExchange.Redis;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace IM_API.Services
|
||||
{
|
||||
@ -15,12 +16,16 @@ namespace IM_API.Services
|
||||
private readonly ILogger<UserService> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ICacheService _cacheService;
|
||||
public UserService(ImContext imContext,ILogger<UserService> logger,IMapper mapper, 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)
|
||||
@ -87,5 +92,38 @@ namespace IM_API.Services
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#VITE_API_BASE_URL = http://localhost:5202/api
|
||||
#VITE_SIGNALR_BASE_URL = http://localhost:5202/chat/
|
||||
VITE_API_BASE_URL = https://im.test.nxsir.cn/api
|
||||
VITE_SIGNALR_BASE_URL = https://im.test.nxsir.cn/chat/
|
||||
VITE_API_BASE_URL = http://localhost:5202/api
|
||||
VITE_SIGNALR_BASE_URL = http://localhost:5202/chat/
|
||||
#VITE_API_BASE_URL = https://im.test.nxsir.cn/api
|
||||
#VITE_SIGNALR_BASE_URL = https://im.test.nxsir.cn/chat/
|
||||
@ -5,12 +5,13 @@ import { useChatStore } from "@/stores/chat";
|
||||
import { messageHandler } from "@/handler/messageHandler";
|
||||
import { generateSessionId } from "../sessionIdTools";
|
||||
import { useConversationStore } from "@/stores/conversation";
|
||||
import { MESSAGE_TYPE } from "@/constants/MessageType";
|
||||
|
||||
export const SignalRMessageHandler = (data) => {
|
||||
const msg = data.data;
|
||||
const chatStore = useChatStore()
|
||||
const browserNotification = useBrowserNotification();
|
||||
const sessionId = generateSessionId(msg.senderId, msg.receiverId);
|
||||
const sessionId = generateSessionId(msg.senderId, msg.receiverId, msg.chatType == MESSAGE_TYPE.GROUP);
|
||||
messageHandler(msg);
|
||||
chatStore.pushAndSortMessagesAsync([msg], sessionId);
|
||||
const conversation = useConversationStore().conversations.find(x => x.targetId == msg.senderId);
|
||||
|
||||
@ -178,7 +178,7 @@ input.value = ''; // 清空输入框
|
||||
updateMsg.isError = true;
|
||||
}finally{
|
||||
updateMsg.isLoading = false;
|
||||
chatStore.pushAndSortMessagesAsync([updateMsg], generateSessionId(msg.senderId, msg.receiverId), true);
|
||||
chatStore.pushAndSortMessagesAsync([updateMsg], generateSessionId(msg.senderId, msg.receiverId, msg.chatType == MESSAGE_TYPE.GROUP), true);
|
||||
|
||||
msg.isLoading = false;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user