精华内容
下载资源
问答
  • (给DotNet加星标,提升.Net技能)转自:飞天猪皮怪cnblogs.com/aqgy12138/p/13719851.html系列SignalR+VueSignalR+Vue 服务端向客户端发送信息SignalR+Vue+Log4net 实时日志推送待定......源码地址:...

    (给DotNet加星标,提升.Net技能)

    转自:飞天猪皮怪cnblogs.com/aqgy12138/p/13719851.html

    系列

    SignalR+Vue

    SignalR+Vue 服务端向客户端发送信息

    SignalR+Vue+Log4net 实时日志推送

    待定......

    源码地址:

    https://github.com/QQ2287991080/SignalRServerAndVueClientDemo

    效果

    老规矩先看最后效果

    6d97da0b37c292ff2e1e0915c5e8ea16.gif

    步骤

    1、配置log4net日志

    实现日志推送,首先需要配置log4net日志,然后定义一个全局异常捕获器,用于捕获错误写入到日志文件。

    faafe5ff71ee2517e07425358079b90b.png

    先把nuget包安装一下。

    然后需要配置log4net的xml信息,右键web项目“添加”->“新建项”

    7c44bbb70a853e4b403e3b1a0b73aa06.png

    找到Web配置文件->“命名”->"点击添加"

    6deef7ccc5a4789990a5ca54bc9414d4.png

    然后把xml配置放入到config文件中,配置如下:

    xml version="1.0" encoding="utf-8" ?><configuration><log4net><appender name="DebugAppender" type="log4net.Appender.DebugAppender" ><layout type="log4net.Layout.PatternLayout"><conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />layout>appender><appender name="RollingFile" type="log4net.Appender.RollingFileAppender"><file value="../../../logs/system.log" /><appendToFile value="true" /><rollingStyle value="Composite" /><staticLogFileName value="true" /><maxSizeRollBackups value="10" /><maximumFileSize value="1GB" /><layout type="log4net.Layout.PatternLayout"><conversionPattern value="%n时间:%date{yyyy-MM-dd HH:mm:ss},%n线程Id:%thread,%n日志级别:%-5level,%n描述:%message|%newline"/>layout>appender><root><level value="All"/><appender-ref ref="DebugAppender" /><appender-ref ref="RollingFile"  />root>log4net>configuration>

    想要更多配置的可以前往官网:http://logging.apache.org/log4net/release/config-examples.html  

    如果对生成多个文件夹有兴趣的可以看我另外:Asp.Net Core Log4Net 配置分多个文件记录日志(不同日志级别)

    接下来就需要在Startup中配置log4net.

    public Startup(IConfiguration configuration){
    Configuration = configuration;
    Logger = LogManager.CreateRepository(Assembly.GetEntryAssembly(), typeof(log4net.Repository.Hierarchy.Hierarchy));
    XmlConfigurator.Configure(Logger, new FileInfo("log4net.config"));// _logger = LogManager.GetLogger(Logger.Name, typeof(Startup));
    }public static ILoggerRepository Logger { get; set; }

    按照我最开始说的,在配置好日志之后需要配置一个全局错误捕获器,直接上代码。

    public class SysExceptionFilter : IAsyncExceptionFilter
    {readonly IHubContext _hub;//使用log4
    ILog _log = LogManager.GetLogger(Startup.Logger.Name, typeof(SysExceptionFilter));public SysExceptionFilter(IHubContext hub){
    _hub = hub;
    }public async Task OnExceptionAsync(ExceptionContext context){//错误var ex = context.Exception;//错误信息string message = ex.Message;//请求方法的路由string url = context.HttpContext?.Request.Path;//写入日志文件描述 注意这个地方尽量不要用中文冒号,否则读取日志文件的时候会造成信息确实,当然你可以定义自己的规则string logMessage = $"错误信息=>【{message}】,【请求地址=>{url}】";//写入日志
    _log.Error(logMessage);//读取日志var data = ReadHelper.Read();//发送给客户端await _hub.Clients.All.SendAsync("ReceiveLog", data);//返回一个正确的200http码,避免前端错误
    context.Result = new JsonResult(new { ErrCode = 0, ErrMsg = message, Data = true });
    }
    }

    代码中的读取日志会在第二节中讲到。

    在Startup服务中注册这个过滤器。

    public void ConfigureServices(IServiceCollection services){
    ......
    services.AddMvc(option =>
    {//添加错误捕获
    option.Filters.Add(typeof(SysExceptionFilter));//option.EnableEndpointRouting = false;
    });
    ......
    }

    按照我这个配置将会在程序目录生成一个logs文件夹,以及一个system.log文件。

    1dc2e4d7e78a7c10ae063f81473fd9a1.png

    2、读取日志文件

    在配置日志文件中已经将日志配置了,再看看生成日志文件内容。

    13e80bd614e78bcb83fac3d40f5d4bf4.png

    跟我在log4net.config中配置的是一样的。

    <layout type="log4net.Layout.PatternLayout">

    <conversionPattern value="%n时间:%date{yyyy-MM-dd HH:mm:ss},%n线程Id:%thread,%n日志级别:%-5level,%n描述:%message|%newline"/>
    layout>

    然后需要读取日志文件的,把日志文件的内容转换成前端能够识别的数据。

    public class ReadHelper
    {
    ///
    /// https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.readerwriterlockslim?view=netframework-4.8
    /// 这里主要控制控制多个线程读取日志文件
    ///
    static ReaderWriterLockSlim _slimLock = new ReaderWriterLockSlim();
    public static ListRead(string filePath=""){
    //日志对象集合
    List datas = new List();
    filePath = Directory.GetCurrentDirectory() + "\\logs\\system.log";//判断日志文件是否存在if (!File.Exists(filePath))
    {return datas;
    }
    _slimLock.EnterReadLock();try
    {//获取日志文件流var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);//读取内容var reader = new StreamReader(fs);var content = reader.ReadToEnd();
    reader.Close();
    fs.Close();/*
    *处理内容,换行符替换掉,然后在log4net配置文件中在每一写入日志结尾的地方加上 |
    *这样做的好处是便于在读取日志文件的时候处理日志数据返回给客户端
    *由于是在每一行结束的地方加上| 所有根据Split分割之后最后一个数据必然是空的
    *所有Where去除一下。
    */
    var contentList = content.Replace("\r\n", "").Split('|').Where(w => !string.IsNullOrEmpty(w));foreach (var item in contentList)
    {//根据逗号分割单个日志数据的内容var info = item.Split(',');//实例化日志对象
    SysExceptionData data = new SysExceptionData();
    data.CreateTime = Convert.ToDateTime(info[0].Split(':')[1]);
    data.Level = info[2].Split(':')[1];
    data.Summary = info[3].Split(':')[1];
    datas.Add(data);
    }
    }finally
    {//退出
    _slimLock.ExitReadLock();
    }return datas.OrderByDescending(bo=>bo.CreateTime).ToList();
    }
    }public class SysExceptionData
    {/// /// 时间/// public DateTime CreateTime { get; set; }/// /// 日志级别/// public string Level { get; set; }/// /// 日志描述/// public string Summary { get; set; }
    }

    这里需要说一下的是为什么要用ReaderWriterLockSlim,其实在写这篇博客之前我刚好看书学到这个东西。

    来一段原文描述:

    通常一个类型实例的并发读操作是线程安全的,而并发更新操作则不是。诸如文件这样的资源也具有相同的特点。

    虽然可以简单的使用一个排它锁来保护对实例的任何形式的访问。

    但是如果其读操作很多但是更新操作很少,则使用单一的锁限制并发性就不大合理了。

    这种情况出现在业务应用服务器上,它会将常用的数据缓存在静态字段中进行快速检索。

    ReaderWriterLockSlim是专门为这种情形设计的,它可以最大限度的保证锁的可用性。

    ReaderWriterLockSlim在.net3.5引入的它替代了笨重的ReaderWriterLock类。

    虽然两者功能相识,但是后者的执行速度比前置慢数倍。

    ReaderWriteLockSlim和ReaderWriterLock都拥有两种基本锁,读和写。

    写锁是全局排它锁

    读锁可以兼容其他的锁

    因此,一个持有写锁的线程将阻塞其他任何试图获取读锁或写锁的京城。但是如果没有任何线程持有写锁的话,那么任意数量的线程都可以获得读锁。

    ReaderWriterLockSlim和lock一样也有类似TryEnter之类的方法,来判断是否超时,如果超时就抛出错误(lock返回false)

    这是关于ReaderWriterLockSlim官网最新的描述:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.readerwriterlockslim?view=netframework-4.8

    对了,我看的是孔雀鸟--《c# 7.0核心技术指南》c#想进阶强烈推荐这本书。

    同时这部分代码也有参考老张的Blog.Core的源码,感谢!

    接下来调试一下看看读取日志文件处理后的数据,我在TestController加了故意抛出错误的接口。

    9259b87658e6d944d7e5473d39a271ae.png

    直接在浏览器输入 :http://localhost:13989/api/test/getLog

    836b1163cdac20cbd8680e6c42793e4b.png

    成功进入断点

    7b15db945bd37fcfcc305985fd57ec4e.png

    shift+f9监听data看看数据

    c4feda38f34bfc0e24559186e17de92b.png

    拿到这个数据,在客户端就直接可以用来展示,那么读取日志文件这部分就说完了,然后再说如何发送日志给客户端。

    3、实时发送日志数据

    在日志过滤器中有这样一段代码,玩过signalr的人都知道SendAsync的第一个字符串其实是集线器中方法(Hub)的名称,但是我们也是可以自定义它的名称的。

    //发送给客户端
    await _hub.Clients.All.SendAsync("ReceiveLog", data);

    signalr强类型中心:

    https://docs.microsoft.com/zh-cn/aspnet/core/signalr/hubs?view=aspnetcore-3.1#change-the-name-of-a-hub-method

    之前用的Hub不是强类型中心,这次一并给他改造了。

    /// 
    /// https://docs.microsoft.com/zh-cn/aspnet/core/signalr/hubs?view=aspnetcore-3.1
    /// 强类型中心
    ///
    public interface IChatClient
    {
    Task ReceiveMessage(string user, string message);
    Task ReceiveMessage(object message);
    Task ReceiveCaller(object message);
    Task ReceiveLog(object data);
    }

    重构源码之前的方法。

    public class ChatHub : Hub
    {/// /// 给所有客户端发送消息/// /// 用户/// 消息/// public async Task SendMessage(string user, string message){
    await Clients.All.ReceiveMessage(user, message);
    }/// /// 向调用客户端发送消息/// /// /// public async Task SendMessageCaller(string message){
    await Clients.Caller.ReceiveCaller( message);
    }/// /// 客户端连接服务端/// /// public override Task OnConnectedAsync(){
    var id = Context.ConnectionId;//_logger.Info($"客户端ConnectionId=>【{id}】已连接服务器!");return base.OnConnectedAsync();
    }/// /// 客户端断开连接/// /// /// public override Task OnDisconnectedAsync(Exception exception){
    var id = Context.ConnectionId;//_logger.Info($"客户端ConnectionId=>【{id}】已断开服务器连接!");return base.OnDisconnectedAsync(exception);
    }public async Task ReceiveLog(object data){
    data = ReadHelper.Read();
    await Clients.All.ReceiveLog(data);
    }
    }

    ps:这个改动不会影响它在控制器注入,或者其它注入地方的使用。

    其实服务端的配置差不多好了,现在需要想的是在客户端,首次进入页面的时候是应该手动给他调用一次发送日志,否则进入页面是没有数据的。

    然后我在TestController中加上一个接口手动触发

    [HttpGet]public  async TaskGetLogMessage(){var data = ReadHelper.Read();await _hubContext.Clients.All.SendAsync("ReceiveLog", data);return new JsonResult(0);
    }

    ?,接下来需要把注意力集中到客户端上了,

    之前的两篇博客我是没有安装element-ui的,这一次我为了展示数据省事,就打算直接用element-table展示数据好了。

    element官网:https://element.eleme.cn/#/zh-CN/component/installation

    npm i element-ui -S

    在mian.js添加配置

    import ElementUI from 'element-ui'
    import 'element-ui/lib/theme-chalk/index.css'

    vue 这里我不敢乱讲,这个我也不是很会,所以直接放代码了,我把客户端直接的代码进行了一下改造,加了个菜单,然后之前的内容都放在不同的菜单。

    <template>
    <div class="home">
    <h1>服务端错误日志返回h1>
    <button @click="sendErr">执行一个错误button>
    <div class="table">
    <el-table :data="tableData" border style="width: 100%">
    <el-table-column type="index" label="序号" width="100">el-table-column>
    <el-table-column prop="createTime" label="日期" width="180">el-table-column>
    <el-table-column prop="level" label="级别" width="100">el-table-column>
    <el-table-column prop="summary" label="描述" width="300">el-table-column>
    el-table>
    div>
    div>
    template>
    <script>
    // @ is an alias to /src
    import HelloWorld from "@/components/HelloWorld.vue";
    import * as signalR from "@aspnet/signalr";
    export default {
    name: "Home",
    components: {
    HelloWorld,
    },
    data() {
    return {
    message: "", //消息
    connection: "", //signalr连接
    messages: [], //返回消息
    tableData: [],
    };
    },
    methods: {
    //发出一个错误
    sendErr: function () {
    this.$http.get("http://localhost:13989/api/test/getLog").then((resp) => {
    //console.log(resp);
    });
    },
    //获取系统日志
    getLog: function () {
    this.$http
    .get("http://localhost:13989/api/test/GetLogMessage")
    .then((res) => {
    console.log(res);
    });
    },
    getdatalist: function () {
    this.$http
    .get("http://localhost:13989/api/test/GetLogMessage")
    .then((res) => {
    // console.log(res);
    //this.tableData = res.data;
    })
    .catch((err) => {
    console.log(err);
    });
    },
    },
    computed: {},
    mounted: function () {
    let thisVue = this;
    this.connection = new signalR.HubConnectionBuilder()
    .withUrl("http://localhost:13989/chathub", {
    skipNegotiation: true,
    transport: signalR.HttpTransportType.WebSockets,
    })
    .configureLogging(signalR.LogLevel.Information)
    .build();
    this.connection.start();
    //连接日志发送事件
    this.connection.on("ReceiveLog", function (message) {
    console.log("listening receivelog");
    thisVue.tableData = message;
    });
    //初始化表格数据
    thisVue.getdatalist();
    },
    };
    script>
    <style scoped>
    .table {
    margin: 20px;
    }
    style>

    启动看看效果。

    这是日志接口展示的客户端页面

    fb037cd471ac6c22f94194a8d5e2ef54.png

    之前博客的内容在聊天中。

    f5e6f9407309518d16b5215bd1c71489.png

    来个gif看看效果

    6f3e636f6b46e064d322b23188635f81.gif

    结语

    今天的分享到这里就结束了,内心觉得写一篇博客真不容易,从这个想法的萌芽到写demo去实现大概花了一周,不断地去看资料,研究源码。

    俗话说,人不逼自己一下,不知道有多少潜力。

    最后希望博客能够帮助到需要的人,后续还想研究下signalr 配置jwt,redis,sqlserver等。

    Dome源码地址:https://github.com/QQ2287991080/SignalRServerAndVueClientDemo

    学习使我快乐!!!

    - EOF -

    7629c86134018148ebcc8ca4eb9440ee.png

    推荐阅读  点击标题可跳转新版C#高效率编程指南.NET Core守护进程配置(Supervisor)C#中居然也有切片语法糖,太厉害了 

    看完本文有收获?请转发分享给更多人

    关注「DotNet」加星标,提升.Net技能 

    47541ca29b37b78a3416171641214644.png

    好文章,我在看❤️

    展开全文
  • 本文主要为大家介绍Vue结合SignalR实现前后端实时消息同步,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能帮助到大家。最近业务中需要实现服务器端与客户端的实时通信功能,对Signalr做了一点总结和...

    本文主要为大家介绍Vue结合SignalR实现前后端实时消息同步,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能帮助到大家。

    最近业务中需要实现服务器端与客户端的实时通信功能,对Signalr做了一点总结和整理。

    SignalR 作为 ASP.NET 的一个库,能够简单方便地为应用提供实时的服务器端与客户端双向通信功能。

    SignalR 在客户端方面有两种API:Connections 和 Hubs。

    在特殊情况下,比如发送消息的格式是特定不变时,使用Connections API。

    大多数情况下使用Hubs,因为它是 Connections API 更高级的一种实现,允许客户端与服务端相互直接调用方法。一个实际应用的具体场景,比如服务端获取到新订单时,调用客户端的打印方法,客户端打印完成后,调用服务端的订单状态更新方法。

    下面介绍 Hubs 在前端的 API

    generated proxy

    当使用generated proxy的时候,在语法层面上可以更加简单地调用服务端方法,就像在服务端直接调用。

    如下面是服务端的代码,表示新增一条聊天信息到列表

    public class DemoChatHub : Hub

    {

    public void NewChatMessage(string name, string message)

    {

    Clients.All.addMessageToList(name, message);

    }

    }

    客户端调用的时候:

    var demoChatHubProxy = $.connection.DemoChatHub;

    demoChatHubProxy.client.addMessageToList = function (name, message) {

    console.log(name + ' ' + message);

    };

    $.connection.hub.start().done(function () {

    $('#newChatMessage').click(function () {

    demoChatHubProxy.server.newChatMessage($('#displayname').val(), $('#message').val());

    });

    });

    不使用 generated proxy 时,客户端调用的时候则是

    var connection = $.hubConnection();

    var demoChatHubProxy = connection.createHubProxy('demoChatHub');

    demoChatHubProxy.on('addMessageToList', function(name, message) {

    console.log(name + ' ' + message);

    });

    connection.start().done(function() {

    $('#newChatMessage').click(function () {

    demoChatHubProxy.invoke('newChatMessage', $('#displayname').val(), $('#message').val());

    });

    });

    但是在Vue项目里面,如果前后端分离,不会这样引用:

    而且在客户端方法中如果要使用多个事件处理器时,不能使用generated proxy。

    因此后面的例子不采取generated proxy的方式。

    1.如何建立连接

    var connection = $.hubConnection('localhost:23123');//如果前后端为同一个端口,可不填参数。如果前后端分离,这里参数为服务器端的URL

    var demoChatHubProxy = connection.createHubProxy('demoChatHub');

    demoChatHubProxy.on('addMessageToList', function(userName, message) {

    console.log(userName + ' ' + message);

    });

    connection.start()

    .done(function(){ console.log('Now connected, connection ID=' + connection.id); })

    .fail(function(){ console.log('Could not connect'); });

    需要注意的是,开始连接之前(调用 start 方法之前),最好注册至少一个事件处理方法,如果没有注册的话,Hubs的 OnConnected 方法将不会被调用,那么客户端的方法就不能被服务端调用(这容易埋坑,所以要提前注册方法)。

    2.客户端如何调用服务器端方法使用 invoke,注意调用服务器端的方法名首字母可以不大写,如果方法名称要限制必须大写,需要后端做配置。

    demoChatHubProxy.invoke('newChatMessage', {name:'a',message:'b'});

    3. 服务器端调用客户端方法

    首先客户端要注册方法才能让服务器端调用,使用 on 方法注册。

    demoChatHubProxy.on('addMessageToList', function(userName, message) {

    console.log(userName + ' ' + message);

    });

    4 在Vue项目中使用SignalR

    首先安装 SignalR 的package,需要注意的是 SignalR 依赖 jQuery。

    npm i signalr,jquery

    为了方便,在webpack.base.conf.js中注册全局的jQuery

    plugins: [new webpack.ProvidePlugin({

    $: 'jquery',

    jQuery: 'jquery',

    'window.jQuery': 'jquery',

    'root.jQuery': 'jquery'

    })

    ]

    然后在main.js中引入 SignalR

    import 'signalr'

    这时候就可以在Vue项目中使用SignalR了,后端的相关配置暂时略过。

    新建一个signalr.js

    import { Message } from 'element-ui';

    const HUBNAME = 'DefaultHub';

    /*客户端调用服务器端方法*/

    //更新订单打印次数

    const updateOrderPrint = {

    name:'updateOrderPrint',

    method:function(data){

    console.log(data)

    }

    }

    /*服务器调用客户端方法*/

    // 打印新订单

    const printNewOrder = {

    name:'printNewOrder',

    method:function(data){

    console.log(data)

    }

    }

    const get = {

    name:'Get',

    method:function(data){

    console.log(data)

    }

    }

    //服务器端的方法

    const serverMethodSets = [updateOrderPrint];

    //客户端的方法

    const clientMethodSets = [printNewOrder,get]; //将需要注册的方法放进集合

    // 建立连接

    export function startConnection() {

    let hub = $.hubConnection(process.env.HUB_API)

    let proxy = createHubProxy(hub) //需要先注册方法再连接

    hub.start().done((connection) =>{

    console.log('Now connected, connection ID=' + connection.id)

    }).fail(()=>{

    Message('连接失败' + error);

    console.log('Could not connect');

    })

    hub.error(function (error) {

    Message('SignalR error: ' + error);

    console.log('SignalR error: ' + error)

    })

    hub.connectionSlow(function () {

    console.log('We are currently experiencing difficulties with the connection.')

    });

    hub.disconnected(function () {

    console.log('disconnected')

    });

    return proxy

    }

    // 手动创建proxy

    export function createHubProxy(hub){

    let proxy = hub.createHubProxy(HUBNAME)

    // 注册客户端方法

    clientMethodSets.map((item)=>{

    proxy.on(item.name,item.method)

    })

    return proxy

    }

    这样,在组件引入signalr.js后调用startConnection方法即可建立连接。

    相关推荐:

    展开全文
  • (给DotNet加星标,提升.Net技能)转自:飞天猪皮怪cnblogs.com/aqgy12138/p/13719851.html系列SignalR+VueSignalR+Vue 服务端向客户端发送信息SignalR+Vue+Log4net 实时日志推送待定......源码地址:...

    (给DotNet加星标,提升.Net技能)

    转自:飞天猪皮怪cnblogs.com/aqgy12138/p/13719851.html

    系列

    SignalR+Vue

    SignalR+Vue 服务端向客户端发送信息

    SignalR+Vue+Log4net 实时日志推送

    待定......

    源码地址:

    https://github.com/QQ2287991080/SignalRServerAndVueClientDemo

    效果

    老规矩先看最后效果

    f0fe7ecdb817f265cde9e2dee54e2c61.gif

    步骤

    1、配置log4net日志

    实现日志推送,首先需要配置log4net日志,然后定义一个全局异常捕获器,用于捕获错误写入到日志文件。

    a4bfea609b2be4acbfa9408008faaf66.png

    先把nuget包安装一下。

    然后需要配置log4net的xml信息,右键web项目“添加”->“新建项”

    86c330b9dde00a86c0f8f290cf32125a.png

    找到Web配置文件->“命名”->"点击添加"

    235763edb8a8cc6572ce1a9e94405825.png

    然后把xml配置放入到config文件中,配置如下:

    xml version="1.0" encoding="utf-8" ?><configuration><log4net><appender name="DebugAppender" type="log4net.Appender.DebugAppender" ><layout type="log4net.Layout.PatternLayout"><conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />layout>appender><appender name="RollingFile" type="log4net.Appender.RollingFileAppender"><file value="../../../logs/system.log" /><appendToFile value="true" /><rollingStyle value="Composite" /><staticLogFileName value="true" /><maxSizeRollBackups value="10" /><maximumFileSize value="1GB" /><layout type="log4net.Layout.PatternLayout"><conversionPattern value="%n时间:%date{yyyy-MM-dd HH:mm:ss},%n线程Id:%thread,%n日志级别:%-5level,%n描述:%message|%newline"/>layout>appender><root><level value="All"/><appender-ref ref="DebugAppender" /><appender-ref ref="RollingFile"  />root>log4net>configuration>

    想要更多配置的可以前往官网:http://logging.apache.org/log4net/release/config-examples.html  

    如果对生成多个文件夹有兴趣的可以看我另外:Asp.Net Core Log4Net 配置分多个文件记录日志(不同日志级别)

    接下来就需要在Startup中配置log4net.

    public Startup(IConfiguration configuration){
    Configuration = configuration;
    Logger = LogManager.CreateRepository(Assembly.GetEntryAssembly(), typeof(log4net.Repository.Hierarchy.Hierarchy));
    XmlConfigurator.Configure(Logger, new FileInfo("log4net.config"));// _logger = LogManager.GetLogger(Logger.Name, typeof(Startup));
    }public static ILoggerRepository Logger { get; set; }

    按照我最开始说的,在配置好日志之后需要配置一个全局错误捕获器,直接上代码。

    public class SysExceptionFilter : IAsyncExceptionFilter
    {readonly IHubContext _hub;//使用log4
    ILog _log = LogManager.GetLogger(Startup.Logger.Name, typeof(SysExceptionFilter));public SysExceptionFilter(IHubContext hub){
    _hub = hub;
    }public async Task OnExceptionAsync(ExceptionContext context){//错误var ex = context.Exception;//错误信息string message = ex.Message;//请求方法的路由string url = context.HttpContext?.Request.Path;//写入日志文件描述 注意这个地方尽量不要用中文冒号,否则读取日志文件的时候会造成信息确实,当然你可以定义自己的规则string logMessage = $"错误信息=>【{message}】,【请求地址=>{url}】";//写入日志
    _log.Error(logMessage);//读取日志var data = ReadHelper.Read();//发送给客户端await _hub.Clients.All.SendAsync("ReceiveLog", data);//返回一个正确的200http码,避免前端错误
    context.Result = new JsonResult(new { ErrCode = 0, ErrMsg = message, Data = true });
    }
    }

    代码中的读取日志会在第二节中讲到。

    在Startup服务中注册这个过滤器。

    public void ConfigureServices(IServiceCollection services){
    ......
    services.AddMvc(option =>
    {//添加错误捕获
    option.Filters.Add(typeof(SysExceptionFilter));//option.EnableEndpointRouting = false;
    });
    ......
    }

    按照我这个配置将会在程序目录生成一个logs文件夹,以及一个system.log文件。

    6336cebd6816e81214b4e602bc601e31.png

    2、读取日志文件

    在配置日志文件中已经将日志配置了,再看看生成日志文件内容。

    d2a0e42e6afa5b8104cbaff9d29fc4d2.png

    跟我在log4net.config中配置的是一样的。

    <layout type="log4net.Layout.PatternLayout">

    <conversionPattern value="%n时间:%date{yyyy-MM-dd HH:mm:ss},%n线程Id:%thread,%n日志级别:%-5level,%n描述:%message|%newline"/>
    layout>

    然后需要读取日志文件的,把日志文件的内容转换成前端能够识别的数据。

    public class ReadHelper
    {
    ///
    /// https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.readerwriterlockslim?view=netframework-4.8
    /// 这里主要控制控制多个线程读取日志文件
    ///
    static ReaderWriterLockSlim _slimLock = new ReaderWriterLockSlim();
    public static ListRead(string filePath=""){
    //日志对象集合
    List datas = new List();
    filePath = Directory.GetCurrentDirectory() + "\\logs\\system.log";//判断日志文件是否存在if (!File.Exists(filePath))
    {return datas;
    }
    _slimLock.EnterReadLock();try
    {//获取日志文件流var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);//读取内容var reader = new StreamReader(fs);var content = reader.ReadToEnd();
    reader.Close();
    fs.Close();/*
    *处理内容,换行符替换掉,然后在log4net配置文件中在每一写入日志结尾的地方加上 |
    *这样做的好处是便于在读取日志文件的时候处理日志数据返回给客户端
    *由于是在每一行结束的地方加上| 所有根据Split分割之后最后一个数据必然是空的
    *所有Where去除一下。
    */
    var contentList = content.Replace("\r\n", "").Split('|').Where(w => !string.IsNullOrEmpty(w));foreach (var item in contentList)
    {//根据逗号分割单个日志数据的内容var info = item.Split(',');//实例化日志对象
    SysExceptionData data = new SysExceptionData();
    data.CreateTime = Convert.ToDateTime(info[0].Split(':')[1]);
    data.Level = info[2].Split(':')[1];
    data.Summary = info[3].Split(':')[1];
    datas.Add(data);
    }
    }finally
    {//退出
    _slimLock.ExitReadLock();
    }return datas.OrderByDescending(bo=>bo.CreateTime).ToList();
    }
    }public class SysExceptionData
    {/// /// 时间/// public DateTime CreateTime { get; set; }/// /// 日志级别/// public string Level { get; set; }/// /// 日志描述/// public string Summary { get; set; }
    }

    这里需要说一下的是为什么要用ReaderWriterLockSlim,其实在写这篇博客之前我刚好看书学到这个东西。

    来一段原文描述:

    通常一个类型实例的并发读操作是线程安全的,而并发更新操作则不是。诸如文件这样的资源也具有相同的特点。

    虽然可以简单的使用一个排它锁来保护对实例的任何形式的访问。

    但是如果其读操作很多但是更新操作很少,则使用单一的锁限制并发性就不大合理了。

    这种情况出现在业务应用服务器上,它会将常用的数据缓存在静态字段中进行快速检索。

    ReaderWriterLockSlim是专门为这种情形设计的,它可以最大限度的保证锁的可用性。

    ReaderWriterLockSlim在.net3.5引入的它替代了笨重的ReaderWriterLock类。

    虽然两者功能相识,但是后者的执行速度比前置慢数倍。

    ReaderWriteLockSlim和ReaderWriterLock都拥有两种基本锁,读和写。

    写锁是全局排它锁

    读锁可以兼容其他的锁

    因此,一个持有写锁的线程将阻塞其他任何试图获取读锁或写锁的京城。但是如果没有任何线程持有写锁的话,那么任意数量的线程都可以获得读锁。

    ReaderWriterLockSlim和lock一样也有类似TryEnter之类的方法,来判断是否超时,如果超时就抛出错误(lock返回false)

    这是关于ReaderWriterLockSlim官网最新的描述:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.readerwriterlockslim?view=netframework-4.8

    对了,我看的是孔雀鸟--《c# 7.0核心技术指南》c#想进阶强烈推荐这本书。

    同时这部分代码也有参考老张的Blog.Core的源码,感谢!

    接下来调试一下看看读取日志文件处理后的数据,我在TestController加了故意抛出错误的接口。

    908c3feb74b746fd1dc1fc8500e21ef2.png

    直接在浏览器输入 :http://localhost:13989/api/test/getLog

    7a9987bb58602428c74484452ef99d6d.png

    成功进入断点

    6c5eb59858f78a0d16d96943d62be6bf.png

    shift+f9监听data看看数据

    5bd3f934a4c4e73376e077ce0232b3df.png

    拿到这个数据,在客户端就直接可以用来展示,那么读取日志文件这部分就说完了,然后再说如何发送日志给客户端。

    3、实时发送日志数据

    在日志过滤器中有这样一段代码,玩过signalr的人都知道SendAsync的第一个字符串其实是集线器中方法(Hub)的名称,但是我们也是可以自定义它的名称的。

    //发送给客户端
    await _hub.Clients.All.SendAsync("ReceiveLog", data);

    signalr强类型中心:

    https://docs.microsoft.com/zh-cn/aspnet/core/signalr/hubs?view=aspnetcore-3.1#change-the-name-of-a-hub-method

    之前用的Hub不是强类型中心,这次一并给他改造了。

    /// 
    /// https://docs.microsoft.com/zh-cn/aspnet/core/signalr/hubs?view=aspnetcore-3.1
    /// 强类型中心
    ///
    public interface IChatClient
    {
    Task ReceiveMessage(string user, string message);
    Task ReceiveMessage(object message);
    Task ReceiveCaller(object message);
    Task ReceiveLog(object data);
    }

    重构源码之前的方法。

    public class ChatHub : Hub
    {/// /// 给所有客户端发送消息/// /// 用户/// 消息/// public async Task SendMessage(string user, string message){
    await Clients.All.ReceiveMessage(user, message);
    }/// /// 向调用客户端发送消息/// /// /// public async Task SendMessageCaller(string message){
    await Clients.Caller.ReceiveCaller( message);
    }/// /// 客户端连接服务端/// /// public override Task OnConnectedAsync(){
    var id = Context.ConnectionId;//_logger.Info($"客户端ConnectionId=>【{id}】已连接服务器!");return base.OnConnectedAsync();
    }/// /// 客户端断开连接/// /// /// public override Task OnDisconnectedAsync(Exception exception){
    var id = Context.ConnectionId;//_logger.Info($"客户端ConnectionId=>【{id}】已断开服务器连接!");return base.OnDisconnectedAsync(exception);
    }public async Task ReceiveLog(object data){
    data = ReadHelper.Read();
    await Clients.All.ReceiveLog(data);
    }
    }

    ps:这个改动不会影响它在控制器注入,或者其它注入地方的使用。

    其实服务端的配置差不多好了,现在需要想的是在客户端,首次进入页面的时候是应该手动给他调用一次发送日志,否则进入页面是没有数据的。

    然后我在TestController中加上一个接口手动触发

    [HttpGet]public  async TaskGetLogMessage(){var data = ReadHelper.Read();await _hubContext.Clients.All.SendAsync("ReceiveLog", data);return new JsonResult(0);
    }

    ?,接下来需要把注意力集中到客户端上了,

    之前的两篇博客我是没有安装element-ui的,这一次我为了展示数据省事,就打算直接用element-table展示数据好了。

    element官网:https://element.eleme.cn/#/zh-CN/component/installation

    npm i element-ui -S

    在mian.js添加配置

    import ElementUI from 'element-ui'
    import 'element-ui/lib/theme-chalk/index.css'

    vue 这里我不敢乱讲,这个我也不是很会,所以直接放代码了,我把客户端直接的代码进行了一下改造,加了个菜单,然后之前的内容都放在不同的菜单。

    <template>
    <div class="home">
    <h1>服务端错误日志返回h1>
    <button @click="sendErr">执行一个错误button>
    <div class="table">
    <el-table :data="tableData" border style="width: 100%">
    <el-table-column type="index" label="序号" width="100">el-table-column>
    <el-table-column prop="createTime" label="日期" width="180">el-table-column>
    <el-table-column prop="level" label="级别" width="100">el-table-column>
    <el-table-column prop="summary" label="描述" width="300">el-table-column>
    el-table>
    div>
    div>
    template>
    <script>
    // @ is an alias to /src
    import HelloWorld from "@/components/HelloWorld.vue";
    import * as signalR from "@aspnet/signalr";
    export default {
    name: "Home",
    components: {
    HelloWorld,
    },
    data() {
    return {
    message: "", //消息
    connection: "", //signalr连接
    messages: [], //返回消息
    tableData: [],
    };
    },
    methods: {
    //发出一个错误
    sendErr: function () {
    this.$http.get("http://localhost:13989/api/test/getLog").then((resp) => {
    //console.log(resp);
    });
    },
    //获取系统日志
    getLog: function () {
    this.$http
    .get("http://localhost:13989/api/test/GetLogMessage")
    .then((res) => {
    console.log(res);
    });
    },
    getdatalist: function () {
    this.$http
    .get("http://localhost:13989/api/test/GetLogMessage")
    .then((res) => {
    // console.log(res);
    //this.tableData = res.data;
    })
    .catch((err) => {
    console.log(err);
    });
    },
    },
    computed: {},
    mounted: function () {
    let thisVue = this;
    this.connection = new signalR.HubConnectionBuilder()
    .withUrl("http://localhost:13989/chathub", {
    skipNegotiation: true,
    transport: signalR.HttpTransportType.WebSockets,
    })
    .configureLogging(signalR.LogLevel.Information)
    .build();
    this.connection.start();
    //连接日志发送事件
    this.connection.on("ReceiveLog", function (message) {
    console.log("listening receivelog");
    thisVue.tableData = message;
    });
    //初始化表格数据
    thisVue.getdatalist();
    },
    };
    script>
    <style scoped>
    .table {
    margin: 20px;
    }
    style>

    启动看看效果。

    这是日志接口展示的客户端页面

    e78538fc937fec70b276bc6594a4ce9e.png

    之前博客的内容在聊天中。

    35b35ec21e3ee6fbc5d11571d384e30c.png

    来个gif看看效果

    0ec795ee40538190333662463912c4dc.gif

    结语

    今天的分享到这里就结束了,内心觉得写一篇博客真不容易,从这个想法的萌芽到写demo去实现大概花了一周,不断地去看资料,研究源码。

    俗话说,人不逼自己一下,不知道有多少潜力。

    最后希望博客能够帮助到需要的人,后续还想研究下signalr 配置jwt,redis,sqlserver等。

    Dome源码地址:https://github.com/QQ2287991080/SignalRServerAndVueClientDemo

    学习使我快乐!!!

    - EOF -

    6b24e7cdb1bcd465ab5c10bc82fc4fb7.png

    推荐阅读  点击标题可跳转新版C#高效率编程指南.NET Core守护进程配置(Supervisor)C#中居然也有切片语法糖,太厉害了 

    看完本文有收获?请转发分享给更多人

    关注「DotNet」加星标,提升.Net技能 

    84dcecb92906c168b2665b347806345a.png

    好文章,我在看❤️

    展开全文
  • vue使用signalr

    千次阅读 2020-04-10 13:54:16
    1.先安装依赖: ...2.因为signalr依赖jquery,我们先配置全局jquery,这里使用的项目框架是vue-cli3+ts,所以先配置vue.config.js module.exports = { configureWebpack: { plugins: [ new webpa...

    1.先安装依赖:

    npm i signalr jquery --save

    2.因为signalr依赖jquery,我们先配置全局jquery,这里使用的项目框架是vue-cli3+ts,所以先配置vue.config.js

    module.exports = {
        configureWebpack: {
            plugins: [
                new webpack.ProvidePlugin({
                    $: 'jquery',
                    jQuery: 'jquery',
                    'windows.jQuery': 'jquery',
                }),
            ],
        },
    }

    3.在main.ts中引入signalr:

    // 先引入$,防止报$找不到的错误
    import $ from "jquery";
    // 引入signalr
    import "signalr"

    这里需要注意:如果你的引入方式是这样的,然后在使用中报了“$.hubConnection is not a function”的错误,请使用以下方式引入jquery:

    首先先安装:

    npm install --save expose-loader

    然后在main.ts中引入

    import "expose-loader?jQuery!jquery";
    import $ from "jquery";
    // 如果不行的话就用这个引入:import "../node_modules/signalr/jquery.signalR.min.js";
    import "signalr";

    4.在需要使用signalr的组件中使用:

    先创建连接:

    // 我这里是前后端分离的,连接地址是后台地址,注意:连接地址后面不要加/signalr,因为它会自动加上的
    this.connection = ($ as any).hubConnection("连接地址");
    // 创建代理
    this.chatHubProxy = this.connection.createHubProxy("代理名称");
    
    // 监听后台的test事件,即后台执行test时,会执行以下内容
    this.chatHubProxy.on("test", (data: any) => {
        console.log(data);
    });
    
        // 开始连接 jsonp是开启跨域
        this.connection
                .start({ jsonp: true })
                .done(() => {
                    // 连接成功
                    console.log("Now connected, connection ID=" + this.connection.id);
                })
                .fail((err: any) => {
                    console.log(err);
                });
    
        // 调用后台方法,参数形式可以和后台协商
        this.chatHubProxy.invoke("后台方法名","参数");

    说一下我遇到的坑吧:

    1.引入signalr报错,总是报$.hubConnection is not a function,找了两天,才正确引入。

    2.以为signalr你websocket一样,有自己的send、receive、close事件,最后才发现,要实现交互,只需要使用on监听后台事件,使用invoke调用后台事件就可以了

    展开全文
  • vue中使用signalR

    2021-03-11 20:45:48
    npm install @microsoft/signalr 使用 const signalR = require("@microsoft/signalr"); const connection = new signalR.HubConnectionBuilder() .withUrl("http://localhost:5000/chathub", {}) ....
  • asp.netCore+vue +SignalR

    2020-12-08 16:07:21
    基于asp.netCore+vue框架实现SignalR实时推送消息功能。协议是webSoket协议
  • vue-signalr-test.zip

    2020-12-07 10:39:29
    此代码是基于vue写的signalr实时通讯前后端分离中前端部分测试代码,点击按钮后端可以传递当前时间给前端进行显示
  • 主要为大家详细介绍了Vue结合SignalR实现前后端实时消息同步,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • vue中使用signalR总结(前端代码)

    万次阅读 2019-03-02 19:16:42
    一、SignalR 是什么? ASP.NET SignalR 是一个面向 ASP.NET 开发人员的库,可简化将实时 web 功能添加到应用程序的过程。实时 web 功能是让服务器代码将内容推送到连接的客户端立即可用,而不是让服务器等待客户端...
  • 最近业务中需要实现服务器端与客户端的实时通信功能,对Signalr做了一点总结和整理。SignalR 作为 ASP.NET 的一个库,能够简单方便地为应用提供实时的服务器端与客户端双向通信功能。SignalR 在客户端方面有两种API...
  • 2、在signalR.js中封装signalR方法 import bus from "./bus.js" const signalR = require("@aspnet/signalr"); let connection = null; export let connectServer = (url, createUserDto) => { connection =...
  • 最近业务中需要实现服务器端与客户端的实时通信功能,对Signalr做了一点总结和整理。SignalR 作为 ASP.NET 的一个库,能够简单方便地为应用提供实时的服务器端与客户端双向通信功能。SignalR 在客户端方面有两种API...
  • Aspnet-aspnetcore-Vue-starter-signalR.zip,asp.netcore 2.0 vue 2(es6)spa启动程序包,包含路由、vuex和signalrasp.netcore 2.1 vue 2信号程序启动程序,asp.net是一个开源的web框架,用于使用.net构建现代web...
  • 最近业务中需要实现服务器端与客户端的实时通信功能,对Signalr做了一点总结和整理。 SignalR 作为 ASP.NET 的一个库,能够简单方便地为应用提供实时的服务器端与客户端双向通信功能。 SignalR 在客户端方面有两种...
  • 最近在vue中使用SignalR做消息推送的时候遇到了一些坑,也许是我对SignalR还不够深入的研究,或者理解的还不够到位,还望各位大神别喷我啊。。。我也第一次使用SignalR 1.遇到的SignalR来回断开,然后重新实例连接的...
  • 由于signalr2.2.0 依赖于jQuery,虽然在vuejs 略显臃肿, 但是对于目前刚接触 vuejs 和想实现 前后分离的我来说 这已经很好了。目前先实现功能, 然后如果有时间或者期望大牛将signalr 改成不依赖jQuery的signalr....
  • 学习新知识signalr

    2019-04-24 09:09:28
    signalr swagger vue
  • 封装signalr代码,便于扩展3. main.js全局引用4.页面使用C#后端代码下载 1.下载依赖包 npm i @aspnet/signalr 或 cnpm i 2.封装signalr代码,便于扩展 signalR.js import * as signalR from "@aspnet/signalr"; //...
  • 首先,介绍一下SignalR ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信。什么是实时通信的Web呢?就是让客户端(Web页面)和服务器端可以互相通知消息及调用方法,当然这是实时...
  • 文章目录在vue中插入Echarts基于ASP.NET Core3.1 signalR实现前后端分离后端代码:前端配置1.用图形化方式创建的脚手架项目中,在依赖中添加添加`@microsoft/signalr`2. 用以下代码创建hub.js3. 配置`BarChart.vue` ...
  • 我看到有人把一个用户当着一个组,用来单对单私聊,效果是可以的,但是我个人觉得这样不对,我的方法是初始化时,把用户id跟signalr的connectionid存放到一个字典中,私聊时,传入私聊对象的用户Id,然后根据字典获取...

空空如也

空空如也

1 2 3 4
收藏数 69
精华内容 27
关键字:

signalrvue

vue 订阅