Ant Design Pro V5 配置连接signalR,实现消息推送

  前言:之前做一个业务接口的时候,需要处理的时间比较长,超过两份钟iis就自动断了连接,一直寻找iis超时的设置方式,改了几个配置后还是到2分钟就报超时,熬到很晚结果耻辱下播。后来思来想去,发现努力的方向错了,为何iis设置超时时间为2分钟,那肯定是有意为之,超过2分钟的连接便不是一个好的连接。处理时间过长的程序,到程序里应该用异步的处理方式,用户调用接口后直接告诉用户,程序在后台运行请稍等,异步执行处理程序,处理完成后消息推送给用户,这才是合理的做法。消息推送就是后端能调用前端的方法。一搜消息推送,就能看到websocket的介绍,一种全双工的连接通信方式。手上在玩的项目,前端用的ant design pro v5,后端.net6,而.net6用的实现这种全双工的通信方式,使用的是signalR。下面就介绍一下前后端如何配置使用signalR。

后端:1.创建 SignalR 中心

 1 using Microsoft.AspNetCore.Authorization;
 2 using Microsoft.AspNetCore.SignalR;
 3 
 4 namespace Wood.API.Hubs
 5 {
 6     [Authorize]
 7     public class MessageHub : Hub
 8     {
 9         public async Task SendMessage(string user, string message)
10         {
11             await Clients.All.SendAsync("ReceiveMessage", user, message);
12         }
13     }
14 }

 

  2.Program中配置 SignalR,在对应的位置插入如下代码。

 1 //singalR调用特定用户来发送消息,需要此设置,默认userid是ClaimTypes.NameIdentifier
 2 builder.Services.TryAddSingleton(typeof(IUserIdProvider), typeof(DefaultUserIdProvider));
 3 
 4 builder.Services.AddSignalR();
 5 
 6 //这边设置了专门的认证服务器来验证登录情况,singalR认证主要是自定义一下OnMessageReceived事件,不支持header所以用地址栏参数获取
 7 var Authority = Appsettings.App(new string[] { "AuthorityConfiguration", "Authority" });
 8     var ApiName = Appsettings.App(new string[] { "AuthorityConfiguration", "ApiName" });
 9     var RequireHttpsMetadata = Appsettings.App(new string[] { "AuthorityConfiguration", "RequireHttpsMetadata" });
10 
11     builder.Services.AddIdentity<User, Role>(options => configuration.GetSection(nameof(IdentityOptions)).Bind(options))
12                 .AddEntityFrameworkStores<WoodIdentityDbContext>()
13                 .AddDefaultTokenProviders();
14 
15     builder.Services.AddAuthentication(options =>
16     {
17         options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
18         options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
19         options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
20         options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
21         options.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
22     })
23         .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
24         {
25             options.Authority = Authority;
26             options.RequireHttpsMetadata = Convert.ToBoolean(RequireHttpsMetadata);
27             options.Audience = ApiName;
28             options.Events = new JwtBearerEvents
29             {
30                 OnMessageReceived = context =>
31                 {
32                     var accessToken = context.Request.Query["access_token"];
33                     var path = context.HttpContext.Request.Path;
34                     if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/Hubs/MessageHub"))
35                     {
36                         context.Token = accessToken;
37                     }
38                     return Task.CompletedTask;
39                 }
40             };
41         });
42 
43 var app = builder.Build();
44 
45 app.MapHub<MessageHub>("/Hubs/MessageHub");
46 app.Run();

 

3.在集线器外部发送消息,比如在controller里调用集线器Hub里面的方法

 1 [ApiController]
 2 [Route("api/[controller]/[action]")]
 3 [Authorize]
 4 public class CableController : Controller
 5 {
 6        private readonly IHubContext<MessageHub> _messageHub;
 7        private string _userId = "";
 8        public CableController(IHubContext<MessageHub> messageHub)
 9       {
10              _messageHub = messageHub;
11       }
12         [HttpPost]
13         public ActionResult generateData(){
14             _userId = this.User.FindFirst(ClaimTypes.NameIdentifier).Value;
15             generateDataAsync(_userId);
16             return Ok(new { code = 200, success = true, msg = "开始运行程序,请稍候..." });
17         }
18         private async Task<bool> generateDataAsync(string userid)
19         {
20             bool t = await Task.Run(() =>
21             {
22                 //...运行程序
string messageData = "运行成功";
23 _messageHub.Clients.User(userid).SendAsync("ReceiveMessage", messageData); 24 return true; 25 }); 26 return t; 27 } 28 }

前端:1.安装singalR库

执行命令 npm install @microsoft/signalr

 

2.在全局初始数据中初始化连接。网上说在layouts里,可是最新的ant design pro v5里没有layouts, 那就放在app.js的getInitialState里,因为官方文档介绍那边存放的是全局初始数据,不是很笃定是否合理,前端做的不多。

 1 import * as signalR from '@microsoft/signalr';
 2 
 3 export async function getInitialState(): Promise<{
 4   settings?: Partial<LayoutSettings>;
 5   idetityUser?: Oidc.User;
 6   currentUser?: API.CurrentUser;
 7   connection?: any;
 8   generateBtn?: boolean;
 9   loading?: boolean;
10   fetchIdentityUserInfo?: () => Promise<Oidc.User | undefined>;
11   fetchUserInfo?: (options?: { [key: string]: any }) => Promise<API.CurrentUser | undefined>;
12   initConnection?: (options?: { [key: string]: any }) => any;
13   getGenerateBtn?: () => boolean | undefined;
14 }> {
15   const Mgrs = new Mgr();
16   const fetchIdentityUserInfo = async () => {
17     const idetityUser = await Mgrs.getUser();
18     return idetityUser;
19   };
20   const fetchUserInfo = async (options?: { [key: string]: any }) => {
21     const response = await queryCurrentUser(options);
22     const currentUser = response?.data;
23     return currentUser;
24   };
25   const getGenerateBtn = () => {
26     const generateBtn = false;
27     return generateBtn;
28   };
29   const initConnection = (option?: { [key: string]: any }) => {
30     const protocol = new signalR.JsonHubProtocol();
31     const transport = signalR.HttpTransportType.WebSockets;
32     const options = {
33       skipNegotiation: true,
34       transport: transport,
35       accessTokenFactory: () => option?.token,
36     };
37     const connection = new signalR.HubConnectionBuilder()
38       .withUrl('/Hubs/MessageHub', options)
39       .withHubProtocol(protocol)
40       .withAutomaticReconnect()
41       .build();
42 
43     return connection;
44   };
45   // 如果不是登录页面,执行
46   if (history.location.pathname !== loginPath) {
47     const idetityUser = await fetchIdentityUserInfo();
48     const currentUser = await fetchUserInfo();
49 
50     const generateBtn = getGenerateBtn();
51     const connection = initConnection({ token: idetityUser.access_token });
52     // 开始连接 调用后台 BeginSendData 方法 成功后双方交互数据
53     connection.start().then(() => {
54       console.log('开始连接');
55     });
56     return {
57       idetityUser,
58       currentUser,
59       connection,
60       generateBtn,
61       fetchUserInfo,
62       fetchIdentityUserInfo,
63       initConnection,
64       getGenerateBtn,
65       settings: defaultSettings,
66     };
67   }
68   return {
69     fetchIdentityUserInfo,
70     fetchUserInfo,
71     initConnection,
72     settings: defaultSettings,
73   };
74 }

3.在layout中监听服务端发送过来的消息,因为是全局的并且可以setInitialState,改变全局变量。

 1 export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }: any) => {
 2   // 后台默认触发
 3   initialState.connection.on('ReceiveMessage', (data: any) => {
 4     //setBtnState(false);
 5     setInitialState((s: any) => ({ ...s, generateBtn: false }));
 6     const currentMSgId = localStorage.getItem('currentMSgId');
 7     if (data.id && data.id != currentMSgId) {
 8       if (currentMSgId) {
 9         notification.close(currentMSgId);
10       }
11 
12       localStorage.setItem('currentMSgId', data.id);
13       notification.success({
14         message: data.title,
15         description: data.desc,
16         placement: 'bottomRight',
17         duration: 0,
18         key: data.id,
19       });
20     }
21   });
22 
23   return {
24 ...
25 }
26 }

4.代理配置 通信方式不同于http,websocket的代理配置有些许差异

4.1 本地运行

 

 

4.2 部署在iis运行,需要配置反向代理

iis 反向代理 API以及websocket

 

附上大微软的官方文档:https://learn.microsoft.com/zh-cn/aspnet/core/tutorials/signalr?view=aspnetcore-6.0&tabs=visual-studio

 

posted @ 2022-11-17 14:40  木子樵  阅读(771)  评论(0)    收藏  举报