You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:.NET6+Angular13下SignalR单用户私信实现故障排查

解决方案:.NET 6 SignalR + Angular 13 实现单用户私信

核心问题定位

你当前的问题集中在两点:

  1. CustomUserIdProvider 无法获取用户唯一ID,导致Clients.User()无法定位接收方
  2. 混淆了SignalR连接ID(connectionId)和业务用户ID的概念,发送消息时传参逻辑错误

步骤1:修复用户认证与CustomUserIdProvider问题

1.1 修正服务端中间件顺序

.NET 6 中认证中间件的顺序必须严格遵循:UseRouting → UseAuthentication → UseAuthorization → UseEndpoints,否则SignalR连接无法正确解析用户身份。修改Program.cs:

var app = builder.Build();

// 其他中间件(如UseHttpsRedirection)
app.UseHttpsRedirection();

// 必须先启用路由
app.UseRouting();

// 认证、授权必须在路由之后,端点之前
app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapHub<ChatHub>("/api/chat");
    endpoints.MapControllers();
});

1.2 配置JWT Bearer适配SignalR连接

SignalR的WebSocket/SSE连接不会自动在请求头携带Authorization,需要从查询参数提取token。在Program.cs的JWT配置中添加事件处理:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
        };

        // 处理SignalR连接的token提取
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                var accessToken = context.Request.Query["access_token"];
                var path = context.HttpContext.Request.Path;
                // 匹配你的SignalR Hub路由
                if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/api/chat"))
                {
                    context.Token = accessToken;
                }
                return Task.CompletedTask;
            }
        };
    });

1.3 修正CustomUserIdProvider的Claim匹配

确保CustomUserIdProvider中使用的Claim键与你的JWT Token中的用户ID声明一致(比如你的控制器用_userClaims.GetUserId()对应的Claim键可能是"id"而非默认的ClaimTypes.NameIdentifier):

public class CustomUserIdProvider : IUserIdProvider
{
    public string GetUserId(HubConnectionContext connection)
    {
        // 替换为你Token中存储用户ID的Claim键,比如"id"或"user_id"
        var uniqueId = connection.User?.FindFirst("id")?.Value 
            ?? connection.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        return uniqueId;
    }
}

步骤2:修正消息发送逻辑

2.1 调整Angular客户端sendMessage方法

不需要传递connectionId,直接传递包含接收方用户ID的message对象即可:

sendMessage(message: IChatMessage): void {
    this.hubConnection.invoke('SendMessage', message)
        .then((response) => console.log('Server response:', response))
        .catch(error => console.error('Error sending message through SignalR:', error));
}

2.2 重构服务端SendMessage方法

使用用户唯一ID定位接收方,同时填充发送方信息:

public async Task SendMessage(ChatMessage message)
{
    try
    {
        // 获取当前发送方用户ID
        var senderId = Context.User.FindFirst("id")?.Value; // 与CustomUserIdProvider的Claim键保持一致
        if (string.IsNullOrEmpty(senderId))
            throw new UnauthorizedAccessException("无法识别发送方用户");
        if (string.IsNullOrEmpty(message.to))
            throw new ArgumentNullException(nameof(message.to), "接收方用户ID不能为空");

        // 填充消息元数据
        message.from = senderId;
        message.created = DateTime.UtcNow;

        // 可选:保存消息到数据库
        // _chatMessageRepository.Add(message);
        // await _dbContext.SaveChangesAsync();

        // 通过用户ID发送私信
        await Clients.User(message.to).SendAsync("ReceiveMessage", message);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error in SendMessage: {ex.Message}");
        throw;
    }
}

2.3 新增Hub连接日志(可选)

在ChatHub中添加连接日志,验证用户身份是否正确:

public override async Task OnConnectedAsync()
{
    var userId = Context.User.FindFirst("id")?.Value;
    Console.WriteLine($"用户 {userId} 已连接,ConnectionId: {Context.ConnectionId}");
    await base.OnConnectedAsync();
}

验证逻辑

  1. 启动服务后,观察控制台日志,确认用户连接时能正确输出userId
  2. 发送消息时,服务端通过Clients.User(message.to)将消息推送给目标用户
  3. Angular客户端通过receivedMessage$订阅接收消息,正常显示私信内容

内容的提问来源于stack exchange,提问作者Michał Sawicki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 10:10:59