在asp

ssz0312發表於2024-05-23

在asp.net中完成服務間實時通訊

寫在開頭:

最近遇到這樣一個需求:在企業微信(webform)中完成出庫入庫操作,web後臺管理(asp.net core + vue2)介面實時更新庫存

最開始,我想的是前端透過短輪詢方案,這樣並不需要對後端進行任何的改動,簡單粗暴。

可想一想,還有更好的解決方案嗎,前端綁一個監聽事件,觸發完成頁面更新操作,可是這樣的好像對使用者也不太友好,同時,如果有很多地方出現這種需求,那會對伺服器帶來額外的負載。

將庫存變化看作是一個事件,然後企業微信端通知到web後端,再由web後端通知給前端,改變相關值即可,不需進行查詢。

所以採用rabbitmq完成事件反饋,透過signalr完成實時通訊,也就是下圖這樣

具體實現

1.企業微信端傳送事件:

 //
var factory = new ConnectionFactory()
 {
     HostName = "伺服器ip",
     UserName = "admin",
     Password = "123456",
     VirtualHost = "/"
 };
 _connection = factory.CreateConnection();
 _channel = _connection.CreateModel();
 _channel.ExchangeDeclare("SparepartsChangeEvent",
                      ExchangeType.Direct,
                       true, false, null);
 var sendedEvent = new SparepartsChangeEvent(SparepartsType.OutStock, code, count, Factory);
 var json = JsonConvert.SerializeObject(sendedEvent);
 var body = Encoding.UTF8.GetBytes(json);
 _channel.BasicPublish(exchange: "SparepartsChangeEvent",
                       routingKey: routekey,
                       basicProperties: null,
                       body: body);

2.web後端建立集線器:

public class SparepartsHub:Hub
{
    public async Task SendSparepartsChangeMessage(string user,string  code,int count,int type)
    {
        await Clients.All.SendAsync("ReceiveSparepartsChangeMessage", user, code, count,type);
    }
}

3.事件處理:

public class SparepartsChangeEventHandler
{
    private readonly IHubContext<SparepartsHub> _hubContext;
    private readonly IConfiguration _configuration;
    private string routeKey = "DP06";
    public SparepartsChangeEventHandler(IHubContext<SparepartsHub> hubContext,IConfiguration configuration)
    {
        _hubContext = hubContext;
        _configuration = configuration;
    }
    public async Task SendMsg()
    {
        var factory = new ConnectionFactory()
        {
            HostName = "伺服器ip",
            UserName = "admin",
            Password = "123456",//此處建議放入配置檔案
            VirtualHost = "/"
        };
            routeKey = _configuration["RouteKey:SparepartsChange"];//將routekey放入配置檔案
        if(routeKey == null)
        {
            await Task.CompletedTask;
            return;
        }
        using (var connection = factory.CreateConnection())
        using (var channel = connection.CreateModel())
        {
            channel.QueueDeclare(queue: "SparepartsChangeEventHandler",
                                 durable: false,
                                 exclusive: false,
                                 autoDelete: false,
                                 arguments: null);
            channel.QueueBind(queue: "SparepartsChangeEventHandler", exchange: "SparepartsChangeEvent", routingKey: routeKey);
            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += async (model, ea) =>
            {
                var body = ea.Body.ToArray();
                SparepartsChangeEvent scEvent = JsonConvert.DeserializeObject<SparepartsChangeEvent>(Encoding.UTF8.GetString(body));
                await Console.Out.WriteLineAsync(JsonConvert.SerializeObject(_hubContext));

                if (_hubContext.Clients != null)
                {
                    await _hubContext.Clients.All.SendAsync("ReceiveSparepartsChangeMessage", scEvent.Factory, scEvent.Code, scEvent.Count,scEvent.Type);
                }

            };
            channel.BasicConsume(queue: "SparepartsChangeEventHandler",
                                 autoAck: true,
                                 consumer: consumer);
            Console.ReadLine();

        }
    }
}

4.starup類配置:

services.AddSingleton<SparepartsHub>();
services.AddSingleton<SparepartsChangeEventHandler>();//依賴注入

var sparepartsChangeEventHandler = app.ApplicationServices.GetRequiredService<SparepartsChangeEventHandler>();
Task.Run(() => sparepartsChangeEventHandler.SendMsg());
//獲取服務呼叫方法,需要另外開一個執行緒

如果前端signalr提示跨域問題,在此處配置:

 app.UseEndpoints(endpoints =>
 {
     endpoints.MapHub<SparepartsHub>("/sparepartsHub").RequireCors(t => t.WithOrigins(new string[] { "http://前端ip:埠號" }).AllowAnyMethod().AllowAnyHeader().AllowCredentials()); ;
     endpoints.MapControllers();
 });

5.前端:

init() {
      const connection = new signalR.HubConnectionBuilder()
        .withUrl("http://後端ip:埠號/SparepartsHub", {})
        .configureLogging(signalR.LogLevel.Error)
        .build();
      connection.on("ReceiveSparepartsChangeMessage", (factory,code,count,type) => {
        console.log(factory+'-'+code+'-'+count+'-'+type)
        console.log(code)
        let connectIndex=this.dataSource.findIndex(a=>a.bjbh==code)
        console.log(connectIndex)
        if(connectIndex==-1)
          return;
        if(type==0){
          this.dataSource[connectIndex].nowStock += count;
        }else{
          this.dataSource[connectIndex].nowStock -= count;
        }
        console.log()
      });
      connection.start(this.dataSource)
    .then(() => {
        connection.invoke("SendSparepartsChangeMessage", "Alice", "Hello from Vue!",1,0);
    })
    .catch(error => {
        console.error(error);
    });
    },
  },
  created(){
    this.init()
  }

寫在最後

我希望rabbitmq釋出事件後,web後端根據事件可以自動呼叫某個EventHandler方法,就像這樣:

public class SparepartsChangeEventHandler
{
    private readonly ILogger<SparepartsChangeEvent> _logger;
    public SparepartsChangeEventHandler(ILogger<SparepartsChangeEventHandler> logger)
    {
        _logger = logger;
    }
    public Task Receive()
    {
        _logger.LogInformation($"Received SparepartsChangeEvent 事件");
        return Task.CompletedTask;
    }
}

這樣使程式碼更具有可擴充套件性及可讀性,業務與業務之間有更好的隔離。

相關文章