使用SSE推送信息给前端

07-11 1065阅读

SSE概念

什么是SSE

SSE(Server-Sent Events)是一种用于实现服务器主动向客户端推送数据的技术,也被称为“事件流”(Event Stream)。它基于 HTTP 协议,利用了其长连接特性,在客户端与服务器之间建立一条持久化连接,并通过这条连接实现服务器向客户端的实时数据推送。

使用SSE推送信息给前端
(图片来源网络,侵删)

SSE 和 WebSocket 都有各自的优缺点,适用于不同的场景和需求。如果只需要服务器向客户端单向推送数据,并且应用在前端的浏览器环境中,则 SSE 是一个更加轻量级、易于实现和维护的选择。而如果需要双向传输数据、支持自定义协议、或者在更加复杂的网络环境中应用,则 WebSocket 可能更加适合。

SSE适用于场景

ChatGPT聊天机器人,股票价格更新,新闻实时推送,实时监控等需要服务器推送消息给客户端的场景,用在数据更新频繁,低延迟,单向通信的应用非常合适。

SSE技术实现

服务端

完成sse连接,向指定客户端发消息

asp.net core项目中,引用Lib.AspNetCore.ServerSentEvents

简单配置一下即可使用

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddServerSentEvents();
        ...
    }
    public void Configure(IApplicationBuilder app)
    {
        ...
        app.MapServerSentEvents("/sse");
       ...
    }
}

如果向指定客户端发消息

internal interface INotificationsServerSentEventsService : IServerSentEventsService
{ }
internal class NotificationsServerSentEventsService : ServerSentEventsService, INotificationsServerSentEventsService
{
    public NotificationsServerSentEventsService(IOptions options)
        : base(options.ToBaseServerSentEventsServiceOptions())
    { }
}
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddServerSentEvents();
        services.AddServerSentEvents();
        ...
    }
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
    {
        ...
        app.MapServerSentEvents("/default-sse-endpoint");
        app.MapServerSentEvents("/notifications-sse-endpoint");
        ...
    }
}

发消息

public class NotificationsController : Controller
{
    private readonly INotificationsServerSentEventsService _serverSentEventsService;
   IHttpContextAccessor httpContextAccessor;
    public NotificationsController(INotificationsServerSentEventsService serverSentEventsService,IHttpContextAccessor httpContextAccessor)
    {
        _serverSentEventsService = serverSentEventsService;
        this.httpContextAccessor = httpContextAccessor;
    }
    public async Task SendTest()
    {
        //向所有客户端发消息
        _serverSentEventsService.SendEventAsync("");
        //向指定客户端发消息
        var clientId = serverSentEventsClientIdProvider.AcquireClientId(httpContextAccessor.HttpContext);
await SendEventAsync(msg, x => x.Id == clientId);
    }
}

nginx配置

location /
		{
           
		   proxy_pass http://127.0.0.1:55005; 
		    
            proxy_http_version 1.1;
		   proxy_set_header Host $host;
             proxy_set_header Upgrade $http_upgrade;
             proxy_set_header Connection $http_connection;
		   proxy_connect_timeout 4s;
		   proxy_read_timeout 600s; #心跳值 单位秒
		   proxy_send_timeout 12s;
		
        proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header REMOTE-HOST $remote_addr;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
           proxy_buffer_size 1024k;
           proxy_buffers 16 1024k;
           proxy_busy_buffers_size 2048k;
           proxy_temp_file_write_size 2048k;
           proxy_buffering off;
           proxy_cache off;
           gzip off;
           chunked_transfer_encoding off;
		}

客户端

连接sse服务器,接收服务端消息

ts版本

sseConnect(){
      const url=window.location.origin+'/sse'  //'https://localhost:6601/sse'
      const sse=new EventSource(url);
      sse.addEventListener("open",(e)=>{
        //sse open
        $eventBus.emit('SseOpenEvent')
      })
      sse.addEventListener("message",({data})=>{
          //收到消息
          console.log('message',data)
          const d=JSON.parse(data);
          $eventBus.emit("receiveMsgEvent",data)
      })
      sse.addEventListener('error',(err:any)=>{
          //报错
        console.log('err '+JSON.stringify(err));
      })
    },

在收到消息后,发送receiveMsgEvent 处理收到消息后的事件逻辑

通信消息格式

{
    "type":"xx",
    "data":object
}

SSE实例项目

网站上实现微信二维码登录

思路:准备一个网站A,负责与微信通信,处理微信业务;其它网站,连接sse,得到sseId后,调用并显示A站点微信登录二维码,用户扫码后,通过sse推送消息给前端页面

1.站点A开发配置

引用RsCode.Wechat ,并添加微信API服务,具体用法可以查看文档

 builder.Services.AddWeChat(options => {
     Configuration.GetSection("Tencent:WeChat").Bind(options);
 });
...
app.UseWeChat();

添加微信带场景二维码逻辑

 [EnableCors]
 [HttpPost("/oauth/login/qrcode")]
public async Task CreateLoginQrcodeAsync([FromBody] LoginQrcodeRequestDto dto)
{
    string appId = "wx7c829604a62b02e8";
    var ret = await oauthService.CreateLoginQrcodeAsync(appId, dto);
    return Json(ret);
}
public class LoginQrcodeRequestDto
{ 
    [JsonPropertyName("sseId")]
    public string SseId { get; set; } = ""; 
    [JsonPropertyName("domain")]
    public string Domain { get; set; } = "";
}

扫码登录成功后,推送用户token信息,自定义接收微信服务器消息CustomWxMsgHandler.cs

 public class CustomWxMsgHandler : WeChatEventHandler
 {
     //扫描二维码
     public override async Task OnCanEvent(ScanEventMessage scanEventMessage)
    {
        log.LogInformation($"scanEventMessage.EventKey={scanEventMessage.EventKey}");
        var sceneInfo = JsonSerializer.Deserialize(scanEventMessage.EventKey);
        string scene = sceneInfo.SceneStr;
        //扫码后,如果有场景值,获取新用户完成登录
        if(string.IsNullOrWhiteSpace(scene))
        {
            return "success";
        }
        string ghId = scanEventMessage.ToUserName;
        string appId = WeChatOptions.FirstOrDefault(c => c.Id == ghId).AppId;
        string openId = scanEventMessage.FromUserName;
         //查询并创建用户
        var user=await userDomainService.GetOrCreateUserAysnc(new OAuthUserValueObject
        {
             AppId = appId,
             OpenId = openId, 
        }); 
        if (user != null)
        {
            if (scene == "wxlogin")
            {
                List claims = user.GetClaims();
                var tokenInfo = jwt.CreateAccessToken(claims);
                var clientId = sceneInfo.SignalrConnectId; 
                if(!string.IsNullOrWhiteSpace(sceneInfo.Domain))
                {
                    var infoMsg = new
                    {
                        domain = sceneInfo.Domain,
                        clientId=sceneInfo.SseId,
                        type = "login.success",
                        data = tokenInfo
                    };
                    log.LogInformation("发送wxLoginSuccess");
                    await capPublisher.PublishAsync("wxLoginSuccess", infoMsg);
                }
            }
            //向用户发消息
            TextMessage msg = new TextMessage(openId,ghId,"您己成功登录");
            wechat.UseAppId(appId);
            return await wechat.SendMessageAsync(msg);
        }
        return "success";
    }
 }

2.业务网站开发配置

引用SSE,Lib.AspNetCore.ServerSentEvents,添加sse服务

services.AddSse();  
app.UseSse();

订阅CAP消息,在收到登录成功后,将登录结果推送给前端

//微信登录成功
[CapSubscribe("wxLoginSuccess")]
public async Task WxLoginSuccessAsync(WxLoginSuccessDto dto)
{
    if(dto.Domain.Contains("pan.rs888.net"))
    {
        var s = JsonSerializer.Serialize(dto); 
        await sse.SendEventAsync(s, x => x.Id == Guid.Parse(dto.ClientId));
    }
    
}

3.前端页面配置

以vue项目为例,/src/store/model/websocket.s中,添加

actions: {
    sseConnect(){
      const url=window.location.origin+'/sse'  
      const sse=new EventSource(url);
      sse.addEventListener("open",(e)=>{
        console.log('sse open')
        $eventBus.emit('SseOpenEvent')
      })
      sse.addEventListener("message",({data})=>{
          console.log('message',data)
          const d=JSON.parse(data);
          $eventBus.emit("receiveMsgEvent",data)
      })
      sse.addEventListener('error',(err:any)=>{
        console.log('err '+JSON.stringify(err));
      })
    },
}

编写登录二维码组件 /src/components/login/wecchatLogin.vue

  
    
  


import { defineProps } from 'vue';
import QrcodeVue from 'qrcode.vue';
const props = defineProps({
  qrcodeUrl: {
    type: String,
    default: '',
  },
});


.main {
  text-align: center;
}
.description {
  font-size: 13px;
  color: #bdbdbd;
}

/src/layouts/index.vue中调用登录二维码

      
    

    import {  ref } from 'vue';
    import { useWebsocketStore, useUserStore } from '@/store';
    import $eventBus from '@/utils/eventBus';
    import wechatLogin from '@/components/Login/wechatLogin.vue';
    const websocketStore = useWebsocketStore();
    
    //收到推送消息后的逻辑, 收到token后保存
 $eventBus.on('receiveMsgEvent', (data: string) => {
  const d = JSON.parse(data);
  if (d.type === 'login.success') {
    userStore.login(d.data.access_token);
    visibleQrLogin.value = false;
  }
  if (d.type === 'login.fail') {
    MessagePlugin.error('登录失败');
    visibleQrLogin.value = false;
  }
});
$eventBus.on('OpenLoginEvent', () => {
  visibleQrLogin.value = true;
});
const loginQrcode = ref('');
$eventBus.on('SseOpenEvent', (d: any) => {
  if (userStore.token != null && userStore.token.length > 12) {
    return;
  }
  fetchQrcode();
  // 拉取二维码
  setTimeout(() => {
    fetchQrcode();
  }, 60000 * 5);
});
// 二维码登录
const visibleQrLogin = ref(false);
const fetchQrcode = () => {
  userStore.fetchLoginQrcode().then((ret: any) => {
    loginQrcode.value = ret.url;
  });
};
    
    //sse连接
    websocket.sseConnect();

整个流程

1.前端先创建sse连接,然后通过userStore.fetchLoginQrcode()拉取网站A的二维码,取到二维码后,显示给用户;

2.用户扫码成功,触发CustomWxMsgHandler中的OnCanEvent事件,在该事件中注册并登录用户,返回用户token给消息中间件;

3.消息中间件再把token消息发给应用站点,应用站点再通过sse,推送token给前端页面

4.最后,前端页面保存token,并进行逻辑处理

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]