.NET Core Web + Vue 项目集成消息推送工具SignalR
本文来自于我关于SignalR的系列文章,欢迎阅读与讨论~
SignalR 是一个开源的 .NET 库,用于简化向应用程序中添加实时 Web 功能。 它允许服务器端代码立即向连接的客户端推送内容,而不是等待客户端请求。

以下是.Net core web + Vue 项目中集成消息推送工具SignalR的方案:
1. .NET Core Web API 后端配置
创建 SignalR Hub
csharp
// Hubs/ChatHub.cs
using Microsoft.AspNetCore.SignalR;
public class ChatHub : Hub
{
// 客户端连接时调用
public override async Task OnConnectedAsync()
{
await Clients.Caller.SendAsync("ReceiveMessage", "System", "连接成功!");
await base.OnConnectedAsync();
}
// 发送消息到所有客户端
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
// 发送消息到特定组
public async Task SendToGroup(string groupName, string user, string message)
{
await Clients.Group(groupName).SendAsync("ReceiveGroupMessage", user, message);
}
// 加入组
public async Task JoinGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
await Clients.Caller.SendAsync("ReceiveMessage", "System", $"已加入组: {groupName}");
}
// 离开组
public async Task LeaveGroup(string groupName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
await Clients.Caller.SendAsync("ReceiveMessage", "System", $"已离开组: {groupName}");
}
}
配置 SignalR 服务
csharp
// Program.cs
using Microsoft.AspNetCore.SignalR;
var builder = WebApplication.CreateBuilder(args);
// 添加 SignalR 服务
builder.Services.AddSignalR();
// 配置 CORS
builder.Services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", policy =>
{
policy.WithOrigins("http://localhost:8080") // Vue 开发服务器地址
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
builder.Services.AddControllers();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("CorsPolicy");
app.UseRouting();
app.UseAuthorization();
app.MapControllers();
// 配置 SignalR Hub 路由
app.MapHub<ChatHub>("/chatHub");
app.Run();
创建消息推送控制器(可选)
csharp
// Controllers/NotificationController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
[ApiController]
[Route("api/[controller]")]
public class NotificationController : ControllerBase
{
private readonly IHubContext<ChatHub> _hubContext;
public NotificationController(IHubContext<ChatHub> hubContext)
{
_hubContext = hubContext;
}
[HttpPost("broadcast")]
public async Task<IActionResult> BroadcastMessage([FromBody] BroadcastMessageRequest request)
{
await _hubContext.Clients.All.SendAsync("ReceiveMessage", request.User, request.Message);
return Ok(new { success = true });
}
[HttpPost("send-to-group")]
public async Task<IActionResult> SendToGroup([FromBody] GroupMessageRequest request)
{
await _hubContext.Clients.Group(request.GroupName)
.SendAsync("ReceiveGroupMessage", request.User, request.Message);
return Ok(new { success = true });
}
}
public class BroadcastMessageRequest
{
public string User { get; set; }
public string Message { get; set; }
}
public class GroupMessageRequest
{
public string GroupName { get; set; }
public string User { get; set; }
public string Message { get; set; }
}
2. Vue 前端配置
安装 SignalR 客户端
bash
npm install @microsoft/signalr
创建 SignalR 服务
javascript
// src/services/signalRService.js
import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr'
class SignalRService {
constructor() {
this.connection = null
this.isConnected = false
}
// 初始化连接
async initConnection() {
this.connection = new HubConnectionBuilder()
.withUrl('http://localhost:5000/chatHub', {
withCredentials: true // 如果需要认证
})
.configureLogging(LogLevel.Information)
.withAutomaticReconnect([0, 2000, 5000, 10000, 30000]) // 重连策略
.build()
// 注册消息处理程序
this.registerHandlers()
try {
await this.connection.start()
this.isConnected = true
console.log('SignalR 连接成功')
return true
} catch (err) {
console.error('SignalR 连接失败:', err)
this.isConnected = false
return false
}
}
// 注册消息处理器
registerHandlers() {
if (!this.connection) return
// 接收普通消息
this.connection.on('ReceiveMessage', (user, message) => {
console.log(`收到消息 from ${user}: ${message}`)
// 触发自定义事件,让组件可以监听
window.dispatchEvent(new CustomEvent('signalr-message', {
detail: { user, message, type: 'message' }
}))
})
// 接收组消息
this.connection.on('ReceiveGroupMessage', (user, message) => {
console.log(`收到组消息 from ${user}: ${message}`)
window.dispatchEvent(new CustomEvent('signalr-group-message', {
detail: { user, message, type: 'group-message' }
}))
})
// 连接状态变化
this.connection.onreconnecting((error) => {
console.log('SignalR 重新连接中...', error)
this.isConnected = false
})
this.connection.onreconnected((connectionId) => {
console.log('SignalR 重新连接成功:', connectionId)
this.isConnected = true
})
this.connection.onclose((error) => {
console.log('SignalR 连接关闭', error)
this.isConnected = false
})
}
// 发送消息
async sendMessage(user, message) {
if (!this.isConnected || !this.connection) {
console.error('SignalR 未连接')
return false
}
try {
await this.connection.invoke('SendMessage', user, message)
return true
} catch (err) {
console.error('发送消息失败:', err)
return false
}
}
// 加入组
async joinGroup(groupName) {
if (!this.isConnected || !this.connection) {
console.error('SignalR 未连接')
return false
}
try {
await this.connection.invoke('JoinGroup', groupName)
return true
} catch (err) {
console.error('加入组失败:', err)
return false
}
}
// 离开组
async leaveGroup(groupName) {
if (!this.isConnected || !this.connection) {
console.error('SignalR 未连接')
return false
}
try {
await this.connection.invoke('LeaveGroup', groupName)
return true
} catch (err) {
console.error('离开组失败:', err)
return false
}
}
// 断开连接
async disconnect() {
if (this.connection) {
await this.connection.stop()
this.isConnected = false
}
}
}
export default new SignalRService()
创建 Vue 组件
vue
<!-- src/components/ChatComponent.vue -->
<template>
<div class="chat-container">
<div class="connection-status">
<span :class="['status-dot', isConnected ? 'connected' : 'disconnected']"></span>
{{ connectionStatus }}
</div>
<div class="chat-messages" ref="messagesContainer">
<div
v-for="(msg, index) in messages"
:key="index"
:class="['message', msg.type]"
>
<strong>{{ msg.user }}:</strong> {{ msg.message }}
<small class="timestamp">{{ msg.timestamp }}</small>
</div>
</div>
<div class="chat-input">
<input
v-model="currentMessage"
@keyup.enter="sendMessage"
placeholder="输入消息..."
:disabled="!isConnected"
/>
<button @click="sendMessage" :disabled="!isConnected || !currentMessage.trim()">
发送
</button>
</div>
<div class="group-controls">
<input v-model="groupName" placeholder="组名称" />
<button @click="joinGroup" :disabled="!isConnected">加入组</button>
<button @click="leaveGroup" :disabled="!isConnected">离开组</button>
</div>
</div>
</template>
<script>
import signalRService from '@/services/signalRService'
export default {
name: 'ChatComponent',
data() {
return {
isConnected: false,
messages: [],
currentMessage: '',
groupName: 'default-group',
userName: 'Vue用户' + Math.floor(Math.random() * 1000)
}
},
computed: {
connectionStatus() {
return this.isConnected ? '已连接' : '未连接'
}
},
async mounted() {
// 初始化 SignalR 连接
this.isConnected = await signalRService.initConnection()
// 监听消息事件
window.addEventListener('signalr-message', this.handleMessage)
window.addEventListener('signalr-group-message', this.handleGroupMessage)
},
beforeUnmount() {
// 清理事件监听
window.removeEventListener('signalr-message', this.handleMessage)
window.removeEventListener('signalr-group-message', this.handleGroupMessage)
// 断开连接
signalRService.disconnect()
},
methods: {
handleMessage(event) {
this.addMessage({
user: event.detail.user,
message: event.detail.message,
type: 'received',
timestamp: new Date().toLocaleTimeString()
})
},
handleGroupMessage(event) {
this.addMessage({
user: `${event.detail.user} [组消息]`,
message: event.detail.message,
type: 'group',
timestamp: new Date().toLocaleTimeString()
})
},
addMessage(message) {
this.messages.push(message)
this.$nextTick(() => {
this.scrollToBottom()
})
},
scrollToBottom() {
const container = this.$refs.messagesContainer
if (container) {
container.scrollTop = container.scrollHeight
}
},
async sendMessage() {
if (!this.currentMessage.trim()) return
const success = await signalRService.sendMessage(this.userName, this.currentMessage)
if (success) {
this.addMessage({
user: this.userName,
message: this.currentMessage,
type: 'sent',
timestamp: new Date().toLocaleTimeString()
})
this.currentMessage = ''
}
},
async joinGroup() {
if (!this.groupName.trim()) return
await signalRService.joinGroup(this.groupName)
},
async leaveGroup() {
if (!this.groupName.trim()) return
await signalRService.leaveGroup(this.groupName)
}
}
}
</script>
<style scoped>
.chat-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.connection-status {
margin-bottom: 10px;
font-size: 14px;
}
.status-dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 5px;
}
.status-dot.connected {
background-color: #4caf50;
}
.status-dot.disconnected {
background-color: #f44336;
}
.chat-messages {
height: 300px;
overflow-y: auto;
border: 1px solid #eee;
padding: 10px;
margin-bottom: 10px;
}
.message {
margin-bottom: 8px;
padding: 5px;
border-radius: 4px;
}
.message.sent {
background-color: #e3f2fd;
text-align: right;
}
.message.received {
background-color: #f5f5f5;
}
.message.group {
background-color: #fff3e0;
border-left: 3px solid #ff9800;
}
.timestamp {
color: #666;
font-size: 0.8em;
margin-left: 10px;
}
.chat-input {
display: flex;
margin-bottom: 10px;
}
.chat-input input {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
margin-right: 5px;
}
.group-controls {
display: flex;
}
.group-controls input {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
margin-right: 5px;
}
button {
padding: 8px 16px;
background-color: #2196f3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 5px;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
button:hover:not(:disabled) {
background-color: #1976d2;
}
</style>
3. 在 Vue 应用中使用
javascript
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
vue
<!-- src/App.vue -->
<template>
<div id="app">
<h1>SignalR 实时聊天示例</h1>
<ChatComponent />
</div>
</template>
<script>
import ChatComponent from './components/ChatComponent.vue'
export default {
name: 'App',
components: {
ChatComponent
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin-top: 20px;
}
</style>
4. 部署注意事项
生产环境配置
csharp
// 生产环境的 CORS 配置
builder.Services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", policy =>
{
policy.WithOrigins("https://yourdomain.com") // 生产环境域名
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
Vue 生产环境配置
javascript
// 根据环境变量配置 SignalR 地址
const signalRUrl = process.env.NODE_ENV === 'production'
? 'https://your-api-domain.com/chatHub'
: 'http://localhost:5000/chatHub'
this.connection = new HubConnectionBuilder()
.withUrl(signalRUrl, {
withCredentials: true
})
// ... 其他配置
5. 高级功能扩展
身份验证集成
csharp
// 需要认证的 Hub
[Authorize]
public class AuthChatHub : Hub
{
public string GetUserId()
{
return Context.User?.Identity?.Name;
}
}
消息持久化
csharp
public class ChatHub : Hub
{
private readonly IChatService _chatService;
public ChatHub(IChatService chatService)
{
_chatService = chatService;
}
public async Task SendMessage(string user, string message)
{
// 保存到数据库
await _chatService.SaveMessage(user, message);
// 发送给所有客户端
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
这样你就完成了一个完整的 .NET Core + Vue + SignalR 实时消息推送系统。这个示例包含了基本的聊天功能、组消息、连接状态管理等功能,可以根据实际需求进行扩展。