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();
SignalR注入配置
注册的路径 app.MapHub<SystemMessageHub>("/mess");

 

 

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; }
    }
SignalRMessageViewModel

 

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; }
    }
SystemMessageHub

 

4、Program中加入Signalr的hub的访问路径,用于让前端进行访问

//注册signalR的 SystemMessageHub中间件,  js访问路径"/mess
app.MapHub<SystemMessageHub>("/mess");
Program注册signalR的 SystemMessageHub中间件

 

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);
        }
    }
MessManagerController

 

二、客户端(Vue)代码实现(无图片处全部参考文档教程)

1、vue添加SignalR引用

 npm install @aspnet/signalr

 

 

 

 

2、Index.vue里面写对应的连接和注册获取信息代码

2-1:引入Signalr组件

import * as signalR from "@aspnet/signalr";   //引入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());
        });
      });
    },
建立SignalR连接的方法

 容易忘记:需要在Program中注册路径--下面红框的第四行那里,否则有错

 

 

 

2-3:在该页一初始化时就调用创建signal连接

该Index是作为基页的,其他的页路由都配置他的children那里--所以在这个也一初始化开始就建立下signal连接

this.signalRConnection(); //建立signal连接
调用建立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>
vue

 

2-5:调用请求api控制器 

 

import myaxios from '@/mytools/myaxios'
//调用api接口发送消息接口
export function sendMess(messInfo) {
  return myaxios({
    url: 'MessManager/SendMess',
    method: 'post',
    data: messInfo
  })
}
vue js文件

 

2-6:开始试验结果

---以上当前作出效果是给全部网页推送信息--没有实现给指定人发送信息

--执行步骤:

1、vue页打开‘发送信息’页,点击‘发送信息’按钮,弹出层编辑发送内容

 

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

 

 

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

 

 

 

 

 

posted @ 2022-07-07 10:30  じ逐梦  阅读(388)  评论(0)    收藏  举报