跳转到内容

即时通讯

后端的 SignalR 侧:两条 Hub、怎么往前端推、在线状态怎么算,以及一条必须遵守的载荷约定。

前端接法见 前端手册 · 实时通信

两条 Hub

Hub路径用途
BasicAppNotificationHub/hubs/notification通知推送、强制下线、偏好多端同步、任务进度
BasicAppChatHub/hubs/chat在线聊天(单聊 / 群聊 / 部门会话)

两条 Hub 都继承框架的 XiHanHub、打 [AuthorizeHub]必须携带有效 JWT 才能连接),在 Saas 模块的 OnApplicationInitialization 里映射。

Hub 路由前缀由配置 XiHan:Web:Api:Auth:SignalRHubPathPrefix 决定,默认 /hubs

往前端推:IRealtimeNotificationService<THub>

业务侧不直接操作 Hub,而是注入按 Hub 泛型化的推送服务:

csharp
public class NotificationAppService(
    IRealtimeNotificationService<BasicAppNotificationHub> realtime) : SaasApplicationService
{
    public async Task PublishAsync(NotificationCreateDto input, CancellationToken ct = default)
    {
        // …落库…
        // 按 userId 点发给指定用户
        await realtime.SendToUsersAsync(targetUserIds, "ReceiveNotification", payload, ct);
    }
}

泛型参数决定推到哪条 Hub,编译期就不会推错。

当前在用的地方:

场景应用服务说明
通知推送NotificationAppService新公告即时到达
强制下线UserAppService删除用户、踢下线时 ForceLogout
偏好多端同步UserSettingAppService保存列设置/搜索设置后推 UserSettingChanged
聊天消息聊天相关服务落库后按会话成员 userId 点发

聊天的实时方法名

方法名集中在 ChatRealtimeMethods 常量类,不要内联字符串

常量触发时机
ReceiveChatMessage收到新消息
ChatMessageEdited消息被编辑
ChatMessageRecalled消息被撤回
ChatConversationChanged会话变更(成员、名称等)
ChatReactionChanged表情回应变更
ChatReadPositionChanged已读位置变更
ChatTyping对端输入中——Hub 组内广播,不落库
ChatAssistantDeltaAI 助手回复增量
ChatAssistantCompletedAI 助手回复结束

聊天 Hub 的客户端可调方法

方法说明
JoinConversation(conversationId)加入会话组
LeaveConversation(conversationId)离开会话组
Typing(conversationId)广播「正在输入」

参数是 string——这不是随意选的,见下面的载荷约定。

消息落库后点发,而不是组内广播

聊天消息先落库,再经 IRealtimeNotificationService<BasicAppChatHub> 按成员 userId 点发。组(group)只用于 Typing 这种不落库的瞬时信号。

这样做的好处:离线成员下次拉历史能看到,不依赖当时是否在线。

在线状态怎么算

text
在线用户列表 = 活跃会话(数据库 SysUserSession)
              + 实时连接标注(框架 IConnectionManager)

BasicAppNotificationHub 在连接 / 断开时除了框架级的连接登记外,同步刷新对应 SysUserSession 的最后活动时间——弥补 HTTP 侧无心跳的缺口。

会话刷新失败不阻断连接

刷新失败只记日志,绝不影响连接建立或断开。实时能力不应该因为一次数据库抖动就不可用。

「在线用户」页(/identity/online-user)读的就是这两者的合并结果。

载荷必须手动投影

Hub 侧没有 MVC 的 JSON 转换器

long → string、枚举 → 成员名这些转换是 MVC JSON 管道XiHanWebCoreMvcOptions)的配置,不作用于 SignalR

后果:直接推送含雪花 ID 的对象,前端拿到的是会溢出精度的 Number。

两条约定:

  1. 服务端推送前手动投影成前端契约——ID 转字符串、枚举转成员名;
  2. Hub 方法参数用 string 接收 ID,服务端自行解析。

写新推送时先问一句「这个 payload 里有 long 或枚举吗」,有就先投影。

部署注意

反向代理要放行 WebSocket

Nginx 等反向代理必须转发 Upgrade / Connection 头,否则 WebSocket 握手失败。SignalR 会回退到 SSE / LongPolling,功能能用但实时性和资源占用都变差——症状是「能收到消息但延迟高」,容易被误判成业务问题

多实例部署时,SignalR 默认是单机内存的连接管理。要跨实例推送需接 backplane(Redis),否则用户连在 A 实例、消息从 B 实例发出就收不到。

相关页面

Released under The MIT License