SignalR
SignalR
1、ASP.NET Core SignalR 是一个开放源代码库,可用于简化向应用添加实时 Web 功能。 实时 Web 功能使服务器端代码能够将内容推送到客户端。--服务端发送通知发送到指定客户端
2、SignalR是一个基于ASP.NET平台构建,利用JavaScript或者WebSockets(当下利用的它),实现在客户端和服务端异步通信的框架。
3、WebSockets出现以后,SignalR也支持WebSockets通信
4、网站开发中最长见到的一个功能就是在线聊天室,聊天室要解决最大的问题就是 消息的推送。当N个在线用户 同时加入一个聊天室时,1个用户发送消息,服务端就要把这个消息转发给特定的人。
之前的技术都是通过Javascript来不停地发送请求来轮训 服务端的新的消息。这种定期发送Ajax请求给服务器的方式,在用户很大的情况下给服务器带来很大的压力。
WebSockets这个技术的出现,很好地解决了这个问题,恰恰支持可以主动推送消息,SignalR 支持WebSockets、而SignalR应该也会广泛在ASP.NET 网站中出现
WebSocket与http的区别,以及它的原理_摩羯ez的博客-CSDN博客_websocket和http的区别
理清 WebSocket 和 HTTP 的关系 - 知乎 (zhihu.com)
实现:参考 .NET6API-Vue使用SignalR文档教程
一、.NET Core (6.0) API代码实现(无图片处全部参考文档教程)
1、NuGet包下载(Microsoft.AspNetCore.SignalR)+Program文件中添加SignalR的注入和配置
//添加SignalR注入配置 builder.Services.AddSignalR();
2、创建SignalRMessageViewModel类存放实现SignalR所需数据字段
public class SignalRMessageViewModel { /// <summary> /// 发起人 /// </summary> public string? send_user { get; set; } /// <summary> /// 接受人 /// </summary> public string get_user { get; set; } /// <summary> /// 标题 /// </summary> public string title { get; set; } /// <summary> /// 内容 /// </summary> public string content { get; set; } /// <summary> /// 发送时间 /// </summary> public DateTime? send_date { get; set; } /// <summary> /// 创建时间 /// </summary> public DateTime? created_date { get; set; } }
3、控制器编写消息推送Hub类 --继承Hub类
后面完善--指定发送的用户才需要使用那些有关LoginManagerService这些
⑤步骤没用删除了就可


public class SystemMessageHub : Hub { //.netcore 定时执行 https://blog.csdn.net/sunshineGGB/article/details/121765514 public readonly LoginManagerService _LoginManagerService; public SystemMessageHub(LoginManagerService adminUserService) { _LoginManagerService = adminUserService; } /// <summary> /// 建立SignalR的客户端id与token的关系 /// </summary> public static Dictionary<string, UserData> UserIdAndCid = new Dictionary<string, UserData>(); /// <summary> /// 建立连接初始化方法 /// </summary> /// <param name="token"></param> /// <returns></returns> public async Task Init(string token) { var login = _LoginManagerService.GetLoginUserInRedisByToken(token); UserData userData = new UserData() { ConnectionId = Context.ConnectionId, Token = token, UserName = login.user_name, UserFullName = login.user_full_name }; UserIdAndCid.Add(Context.ConnectionId, userData); } /// <summary> /// 记录当前用户的连接ID,方便后面给客户端用户推送站内信通知消息。 /// OnConnectedAsync当与集线器建立新连接时调用。 /// </summary> /// <returns></returns> public override async Task OnConnectedAsync() { //var userId = _currentUserService.GetUserId(); //await _userService.SaveWebSocketIdAsync(userId, Context.ConnectionId); //Console.WriteLine($"新的连接:{Context.ConnectionId},userId={userId}"); ////处理当前用户的未读消息 //var unreadMessage = await _messageService.GetMyMessageUnReadNumberAsync(userId); //var message = new //{ // UnReadNumber = unreadMessage, // MessageList = new List<MessageDTO>() //}; //await Clients.Client(Context.ConnectionId).SendAsync(MesssageCenter.NewMessageNotify, message); await base.OnConnectedAsync(); } /// <summary> /// 主要作用是,当用户的WebSocket断开连接后,清空他的连接ID。 /// OnDisconnectedAsync当与集线器的连接终止时调用。 /// </summary> /// <param name="exception"></param> /// <returns></returns> public override async Task OnDisconnectedAsync(Exception? exception) { //var userId = _currentUserService.GetUserId(); //Console.WriteLine($"断开连接:{Context.ConnectionId},userId={userId}"); //await _userService.SaveWebSocketIdAsync(userId, string.Empty); if (UserIdAndCid.Keys.Contains(Context.ConnectionId)) { //UserData userData = UserIdAndCid[Context.ConnectionId]; //if (userData != null) //{ // userData.Timer.Stop(); //停止定时器 // userData.Timer.Dispose(); //释放定时器 //} UserIdAndCid.Remove(Context.ConnectionId); } await base.OnDisconnectedAsync(exception); } /// <summary> /// 向客户端所有人发送消息 /// </summary> /// <returns></returns> public async Task SendAllMessage(SignalRMessageViewModel dto) { if (dto.get_user == "all") { Clients.All.SendAsync("GetAllMess", dto); //调用客户端的方法 } } //public async Task SendMess(string connectionId,string mess) //{ //} //public void StartTime(object? sender, ElapsedEventArgs e,string connectionId, Hub hub) //{ // UserData userData = UserIdAndCid[connectionId]; // if (userData == null) // { // return; // } //} } public class UserData { public string ConnectionId { get; set; } public string Token { get; set; } public string UserName { get; set; } public string UserFullName { get; set; } public DateTime LoginDate { get; set; } }
4、在Program中加入Signalr的hub的访问路径,用于让前端进行访问
//注册signalR的 SystemMessageHub中间件, js访问路径"/mess app.MapHub<SystemMessageHub>("/mess");
5、为了使用signal 这里修改为指定url才可以跨域访问, 把指定url(withOrigins)放置在配置文件中

6、原先的放置路径文件 appsettings.json 下默认只有 appsettings.Development.json 一个
6-1 --现在更新下--再其下再创建一个appsettings.Production.json文件

6-2 原先在appsettings.json下写的路径--现在统一先转移到appsettings.Development.json(表示开发环境下-类似于vue当时添加配置文件理解的那样--分为两个)

6-2-1 ②处写http://localhost:8080原因就是参考vue运行时这个头--这里端口号是多少那么此时的后端这里就写多少

7、创建控制器api接口

public class MessManagerController : BaseMessConterController { private readonly IHubContext<SystemMessageHub> systemMessageHub; /// <summary> /// 注入signalr 的消息推送的hub /// </summary> /// <param name="systemMessageHub"></param> public MessManagerController(IHubContext<SystemMessageHub> systemMessageHub) { this.systemMessageHub = systemMessageHub; } /// <summary> /// 发送消息的接口 /// </summary> /// <param name="message"></param> /// <returns></returns> [HttpPost] public async Task<ResultModel<bool>> SendMess(SignalRMessageViewModel message) { //如果接口人是all 代表给所有人都发送 if (message.get_user == "all") { // 给所有客户端发送消息 message 表示给客户端传递的参数 systemMessageHub.Clients.All.SendAsync("GetAllMess", message); } return MyOk<bool>(true); } }
二、客户端(Vue)代码实现(无图片处全部参考文档教程)
1、vue添加SignalR引用
npm install @aspnet/signalr

2、在Index.vue里面写对应的连接和注册获取信息代码
2-1:引入Signalr组件

import * as signalR from "@aspnet/signalr"; //引入signalr组件
2-2:创立连接方法


signalRConnection() { //建立SignalR连接的方法
let that = this;
let token = cookesTools.getToken(); //获取token
//process.env.VUE_APP_BASE_Host是配置文件里写的公共的http……这个头
//拼接建立signal连接的url mess是在api端的program中 注册的路径 app.MapHub<SystemMessageHub>("/mess");
let url = process.env.VUE_APP_BASE_Host + "mess";
//建立signal的连接
this.singnalData.conn = new signalR.HubConnectionBuilder().withUrl(url).build();
this.$singnalData = this.singnalData;
//注册客户端显示消息的方法,用于给服务端调用 在MessManagerController的 SendMess 方法下有体现
this.singnalData.conn.on("GetAllMess", (data) => {
//使用element ui 的Notification 通知 弹出消息
this.$notify({
title: data.title,
message: data.content,
duration: 0,
});
});
//启动连接 并调用signalR的 服务端的注册登录方法 Init
this.singnalData.conn.start().then(function () {
that.singnalData.conn.invoke("Init", token).catch(function (err) {
return console.error(err.toString());
});
});
},
容易忘记:需要在Program中注册路径--下面红框的第四行那里,否则有错
2-3:在该页一初始化时就调用创建signal连接
该Index是作为基页的,其他的页路由都配置他的children那里--所以在这个也一初始化开始就建立下signal连接

this.signalRConnection(); //建立signal连接
2-4: 创建一个vue发送消息的视图

<template>
<div>
<el-form :inline="true" :model="searchModel" class="demo-form-inline">
<el-form-item label="发送人">
<el-form-item label="发送人">
<el-select
v-model="searchModel.selectUsers"
filterable
remote
reserve-keyword
placeholder="请输入角色名称"
:remote-method="searchUser"
:loading="editModel.loading"
@focus="searchUsersFocus"
>
<el-option
v-for="item in users"
:key="item.user_name"
:label="item.user_full_name"
:value="item.user_name"
>
</el-option>
</el-select>
</el-form-item>
</el-form-item>
<el-form-item label="接收人"> </el-form-item>
<el-button @click="searchMessage">搜索</el-button>
<el-button @click="openSendAdd">发送消息</el-button>
</el-form>
<el-table ref="filterTable" :data="pageModel.data" style="width: 100%">
<el-table-column prop="send_user" label="发送人"> </el-table-column>
<el-table-column prop="get_user" label="接收人"> </el-table-column>
<el-table-column prop="title" label="标题"> </el-table-column>
<el-table-column
prop="send_date"
label="发送时间"
width="150"
align="center"
:formatter="this.$dateTools.dateFormat"
>
</el-table-column>
</el-table>
<el-pagination
@size-change="searchMessage"
@current-change="searchMessage"
:current-page.sync="searchModel.pageIndex"
:page-sizes="[2, 4, 6, 8]"
:page-size.sync="searchModel.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pageModel.total"
>
</el-pagination>
<!--用户添加弹出层-->
<!--:close-on-click-modal 设置弹出层其他区域点击 不关闭-->
<el-dialog
title="发送消息"
:visible.sync="editModel.isShowEdit"
:close-on-click-modal="false"
>
<el-form ref="form" :model="editModel.sendMessInfo" label-width="100px">
<el-form-item label="标题" prop="title">
<el-input v-model="editModel.sendMessInfo.title"></el-input>
</el-form-item>
<el-form-item label="内容" prop="content">
<el-input v-model="editModel.sendMessInfo.content"></el-input>
</el-form-item>
<el-form-item label="接收人" prop="users">
<!--可搜索下拉框-->
<el-select
v-model="editModel.sendMessInfo.get_user"
filterable
remote
reserve-keyword
placeholder="请输入用户名"
:remote-method="searchUser"
:loading="editModel.loading"
@focus="searchUsersFocus"
>
<el-option
v-for="item in users"
:key="item.user_name"
:label="item.user_full_name"
:value="item.user_name"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="sendMess">发送消息</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
</el-dialog>
</div>
</template>
<script>
import { sendMess } from "./signalRMessageAPI"; //引入api的js
// import { searchAdminUserApi } from "../systembase/adminUser/adminUserManagerApi.js"
export default {
data() {
var that = this;
return {
pageModel: {},
users: [{ user_name: "all", user_full_name: "全部" }],
searchUserInfo: {
pageIndex: 1,
pageSize: 100,
searchItem: {
user_full_name: "",
},
},
searchModel: {
searchItem: {
user_name: null,
user_full_name: null,
user_status: null,
begin_creator_date: null,
end_creator_date: null,
},
pageIndex: 1,
pageSize: 6,
},
editModel: {
//添加或者修改操作的model
sendMessInfo: {
title: "",
content: "",
get_user: "",
},
loading: false, //显示搜索角色的加载动画
isShowEdit: false, //是否显示弹出层
rules: {
title: [{ required: true, message: "请输入标题" }],
content: [{ required: true, message: "请输入用户姓名" }],
get_user: [{ required: true, message: "请选择接收人" }],
},
},
};
},
created() {
this.searchMessage();
},
methods: {
//搜索消息
searchMessage() {},
//当获得焦点时搜索
searchUsersFocus() {
this.searchUser("");
},
//搜索用户
searchUser(sValue) {
// if (sValue) {
// this.editModel.loading = true;
// this.searchUser.searchItem.user_full_name =
// sValue == " " ? "" : sValue;
// searchAdminUserApi(this.searchUser).then((r) => {
// this.editModel.loading = false;
// this.editModel.roles = r.data.data;
// });
// }
},
openSendAdd() {
this.editModel.isShowEdit = true;
this.editModel.sendMessInfo = {
title: "",
content: "",
get_user: "",
};
},
sendMess() {
this.$refs["form"].validate((valid) => {
if (valid) {
sendMess(this.editModel.sendMessInfo).then((r) => {
if (r.code == 1) {
this.$message("消息发送成功");
this.editModel.isShowEdit = false;
this.searchMessage();
}
});
}
});
},
},
};
</script>
2-5:调用请求api控制器

import myaxios from '@/mytools/myaxios' //调用api接口发送消息接口 export function sendMess(messInfo) { return myaxios({ url: 'MessManager/SendMess', method: 'post', data: messInfo }) }
2-6:开始试验结果
---以上当前作出效果是给全部网页推送信息--没有实现给指定人发送信息
--执行步骤:
1、vue页打开‘发送信息’页,点击‘发送信息’按钮,弹出层编辑发送内容

2、API接口处接受到了输入内容数据,并借助与注入的自己写的SystemMessageHub服务类

3、实现效果--》在谷歌浏览器和电脑自带浏览器(本机两个浏览器)各自登录项目--在其中一个点开发送消息(下拉选全部)--另一个也会有一个消息弹窗

本文来自博客园,作者:じ逐梦,转载请注明原文链接:https://www.cnblogs.com/ZhuMeng-Chao/p/16453814.html

浙公网安备 33010602011771号