当前位置:   article > 正文

使用SSE推送信息给前端_前端sse

前端sse

SSE概念

什么是SSE

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

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");
       ...
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

如果向指定客户端发消息

internal interface INotificationsServerSentEventsService : IServerSentEventsService
{ }

internal class NotificationsServerSentEventsService : ServerSentEventsService, INotificationsServerSentEventsService
{
    public NotificationsServerSentEventsService(IOptions<ServerSentEventsServiceOptions<NotificationsServerSentEventsService>> options)
        : base(options.ToBaseServerSentEventsServiceOptions())
    { }
}

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        ...

        services.AddServerSentEvents();
        services.AddServerSentEvents<INotificationsServerSentEventsService, NotificationsServerSentEventsService>();

        ...
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
    {
        ...

        app.MapServerSentEvents("/default-sse-endpoint");
        app.MapServerSentEvents<NotificationsServerSentEventsService>("/notifications-sse-endpoint");

        ...
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

发消息

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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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;
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

客户端

连接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));
      })
    },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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

通信消息格式

{
    "type":"xx",
    "data":object
}
  • 1
  • 2
  • 3
  • 4

SSE实例项目

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

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

1.站点A开发配置

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

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

...
app.UseWeChat();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

 [EnableCors]
 [HttpPost("/oauth/login/qrcode")]
public async Task<JsonResult> 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; } = "";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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

 public class CustomWxMsgHandler : WeChatEventHandler
 {
     //扫描二维码
     public override async Task<string> OnCanEvent(ScanEventMessage scanEventMessage)
    {
        log.LogInformation($"scanEventMessage.EventKey={scanEventMessage.EventKey}");
        var sceneInfo = JsonSerializer.Deserialize<WxCustomSceneMsg>(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<Claim> 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";

    }
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

2.业务网站开发配置

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

services.AddSse();  

app.UseSse();
  • 1
  • 2
  • 3

订阅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));
    }
    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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));
      })
    },
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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

<template>
  <div class="main">
    <qrcode-vue :value="props.qrcodeUrl" :size="200" level="H" />
  </div>
</template>

<script setup lang="ts">
import { defineProps } from 'vue';
import QrcodeVue from 'qrcode.vue';

const props = defineProps({
  qrcodeUrl: {
    type: String,
    default: '',
  },
});
</script>

<style scoped>
.main {
  text-align: center;
}

.description {
  font-size: 13px;
  color: #bdbdbd;
}
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

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

<t-dialog
      header="打开微信扫码登录"
      :visible="visibleQrLogin"
      :footer="null"
      width="300"
      @close="visibleQrLogin = false"
    >
      <wechatLogin :qrcode-url="loginQrcode"></wechatLogin>
    </t-dialog>

<script setup lang="ts">
    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();
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

整个流程

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

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/775513
推荐阅读
相关标签
  

闽ICP备14008679号