Zengjing

by Zengjing 2021-07-16

利用SignalR在Vue.js中实现即时通讯

SignalR for ASP.NET Core是一个开源库,可简化向应用添加即时通讯。支持多端与中心端即时通讯,实现消息相互推送。

配置 SignalR 中心

ASP.NET Core框架已经内置SignalR,我们可以直接注入使用。

services.AddSignalR();

注入之后重载 Hub 接口,实现上线、下线和历史消息推送。

public class MessageHub : Hub<IMessageClient>
{
    private readonly ILogger<MessageHub> logger;
    private readonly IHttpContextAccessor httpContextAccessor;

    /// <summary>
    /// MessageHub
    /// </summary>
    public MessageHub(ILogger<MessageHub> logger,
        IHttpContextAccessor httpContextAccessor)
    {
        this.logger = logger;
        this.httpContextAccessor = httpContextAccessor;
    }

    /// <summary>		
    /// 上线	
    /// </summary>
    public override Task OnConnectedAsync()
    {
        string connId = Context.ConnectionId;
        logger.LogInformation($"SignalR:客户端[{connId}]开始连接");

        try
        {
            string token = httpContextAccessor.HttpContext.Request.Query["access_token"];
            var result = JWTExtension.ValidateJwtToken(token);
            if (result.Status)
            {
                dynamic payload = JsonConvert.DeserializeObject(result.Data);
                string userId = payload.UserId;
                string userName = payload.UserName;

                //上线并推送历史消息
                using (var efDbContext = (EfDbContext)ServiceLocator.Instance.CreateScope().ServiceProvider.GetService(typeof(EfDbContext)))
                {
                    SignalRClientEntity client = efDbContext.SignalRClient.FirstOrDefault(x => x.UserId == userId);
                    if (client != null)
                    {
                        efDbContext.Remove(client);
                    }

                    efDbContext.Add(new SignalRClientEntity
                    {
                        Id = ZnResponse.GuId(),
                        UserId = userId,
                        UserName = userName,
                        ConnectionId = connId,
                        ConnectedDate = DateTime.Now
                    });

                    efDbContext.SaveChanges();

                    List<MessageDto> messages = efDbContext.SysMessage.Where(x => x.CategoryId == (int)MessageCategory.Broadcast || x.ReceiveUserId == userId)
                        .OrderByDescending(x => x.CreatedDate)
                        .ToList();

                    Clients.Client(connId).ReceiveMessage(new ResultModel<List<MessageDto>>(messages.Count > 0, messages));
                    logger.LogInformation($"SignalR:客户端[{connId}]历史消息推送成功");
                }
            }
        }
        catch
        {
            logger.LogInformation($"SignalR:客户端[{connId}]历史消息推送失败");
        }

        return base.OnConnectedAsync();
    }

    /// <summary>
    /// 下线		
    /// </summary>
    public override Task OnDisconnectedAsync(Exception exception)
    {
        string connId = Context.ConnectionId;
        try
        {
            using (var efDbContext = (EfDbContext)ServiceLocator.Instance.CreateScope().ServiceProvider.GetService(typeof(EfDbContext)))
            {
                SignalRClientEntity client = efDbContext.SignalRClient.FirstOrDefault(x => x.ConnectionId == connId);
                if (client != null)
                {
                    efDbContext.Remove(client);
                    efDbContext.SaveChanges();
                }
                logger.LogInformation($"SignalR:客户端[{connId}]删除成功");
            }
        }
        catch
        {
            logger.LogInformation($"SignalR:客户端[{connId}]删除失败");
        }
        logger.LogInformation($"SignalR: 客户端[{connId}]关闭连接");
        return base.OnDisconnectedAsync(exception);
    }
    
}
配置 Vue 客户端

SignalR支持多种客户端连接。本文以 Vue 为例示范。

引用SignalR官方NPM包
npm install @microsoft/signalr -s
封装一下支持多组件调用
import * as signalR from "@microsoft/signalr";
import { getToken } from "@/utils/token";

const token = getToken();
const url = window.config.signalRUrl;

const connection = new signalR.HubConnectionBuilder()
    .withUrl(url, { accessTokenFactory: () => token })
    .configureLogging(signalR.LogLevel.Error)
    .withAutomaticReconnect([0, 3000, 5000, 10000, 15000, 30000])
    .build();

connection
    .start()
    .then(() => {
        console.log("SignalR is ready");
    })
    .catch((err) => {
        return console.error(err.toString());
    });

export default {
    receiveMessage(callback) {
        connection.on("ReceiveMessage", (result) => {
            callback(result);
        });
    },

    sendMessage(message, callback) {
        connection
            .invoke("SendMessage", message)
            .then((result) => {
                callback(result);
            })
            .catch((err) => {
                return console.error(err.toString());
            });
    },
};
在 Vue 监听收发
  • 接收消息
signalR.receiveMessage((result) => {
    this.messageList = this.messageList.concat(result.data);
});
  • 发送消息
says(event) {
    signalR.sendMessage(this.message, (result) => {});
    event.preventDefault();
}

收获点赞: 0

评论

...

by Wilson 2023/1/13 10:21:35

can i send emoji in the chat?

...

by 天草 2023/2/15 8:58:03

需要转码处理(charCodeAt/fromCharCode)

...