在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;
}
}
這樣使程式碼更具有可擴充套件性及可讀性,業務與業務之間有更好的隔離。