使用SSE推送信息给前端
SSE(Server-Sent Events)是一种用于实现服务器主动向客户端推送数据的技术,也被称为“事件流”(Event Stream)。它基于 HTTP 协议,利用了其长连接特性,在客户端与服务器之间建立一条持久化连接,并通过这条连接实现服务器向客户端的实时数据推送。SSE 和 WebSocket 都有各自的优缺点,适用于不同的场景和需求。如果只需要服务器向客户端单向推送数据,并且应用在前端
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");
...
}
}
如果向指定客户端发消息
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");
...
}
}
发消息
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<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; } = "";
}
扫码登录成功后,推送用户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";
}
}
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
<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>
/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.前端先创建sse连接,然后通过userStore.fetchLoginQrcode()拉取网站A的二维码,取到二维码后,显示给用户;
2.用户扫码成功,触发CustomWxMsgHandler中的OnCanEvent事件,在该事件中注册并登录用户,返回用户token给消息中间件;
3.消息中间件再把token消息发给应用站点,应用站点再通过sse,推送token给前端页面
4.最后,前端页面保存token,并进行逻辑处理

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。
更多推荐
所有评论(0)