解决用户同时登录轮询获取用户信息错乱,使用WebSocket和Server-Sent Events (SSE)

为什么更推荐WebSocket

Server-Sent Events (SSE) 是一种服务器向客户端推送数据的单向通信协议,适合某些场景,在解决用户同时登录和实时获取用户信息的问题上,WebSocket 是更好的选择


1. SSE 的局限性

单向通信
  • SSE 是单向的,只能从服务器向客户端推送数据,客户端无法通过 SSE 向服务器发送数据。
  • 如果需要双向通信(如用户登录后需要发送确认消息),SSE 无法满足需求。
连接限制
  • 浏览器对 SSE 的连接数有限制(通常每个域名最多 6 个并发连接)。
  • 如果用户同时打开多个页面,可能会占用大量连接,导致新的连接无法建立。
协议支持
  • SSE 基于 HTTP 协议不支持二进制数据传输只能传输文本数据
  • 如果需要传输二进制数据(如图片、文件),SSE 无法实现。
兼容性
  • 虽然现代浏览器都支持 SSE,但在某些特殊环境(如旧版浏览器或移动端)可能存在兼容性问题

2. WebSocket 的优势

双向通信
  • WebSocket 是全双工的,支持服务器和客户端之间的双向通信。
  • 适合需要客户端和服务器交互的场景(如用户登录后需要发送确认消息)。
高性能
  • WebSocket 的连接是持久的,只有在有数据更新时才会传输数据,减少不必要的请求。
  • 适合高频更新的场景(如实时通知、聊天应用)。
支持二进制数据
  • WebSocket 支持二进制数据传输,适合传输图片、文件等数据。
多用户并发
  • WebSocket 可以为每个用户维护独立的连接,避免多个用户之间的数据冲突。
  • 适合多用户并发的场景。

3. 场景对比

特性WebSocketSSE
通信方式双向通信单向通信(服务器 → 客户端)
性能高性能,适合高频更新适合低频更新
数据传输支持文本和二进制数据仅支持文本数据
连接限制无连接限制每个域名最多 6 个并发连接
兼容性现代浏览器均支持部分旧版浏览器不支持
适用场景实时通知、聊天应用、多用户并发低频通知、状态更新

4. 为什么选择 WebSocket?

用户同时登录时需要实时获取用户信息,且可能需要双向通信(如用户登录后需要发送确认消息)。因此,WebSocket 是更好的选择,因为它:

  • 支持双向通信,满足复杂交互需求。
  • 性能高,适合高频更新。
  • 支持多用户并发,避免数据冲突。

5. SSE 的适用场景

SSE 适合以下场景:

  • 低频通知:如新闻更新、股票价格变动。
  • 状态更新:如任务进度、系统状态。
  • 单向通信:只需要服务器向客户端推送数据。

如果你的场景是单向的、低频的,且不需要客户端向服务器发送数据,SSE 是一个简单的选择。


6. 总结

  • 推荐使用 WebSocket,因为它支持双向通信性能高适合多用户并发
  • SSE 适合低频、单向的场景,但在用户同时登录和实时获取用户信息的问题上,WebSocket 是更好的选择。

实现步骤

解决用户同时登录时轮询获取用户信息出错的问题,推荐使用 WebSocket。以下是详细的原因和实现方法:


1. 后端实现 WebSocket 服务器

安装依赖
npm install ws
代码实现
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 }); // WebSocket 服务器端口// 存储所有连接的客户端
const clients = new Map();wss.on('connection', (ws, req) => {console.log('客户端已连接');// 从请求中获取用户标识(如 openid)const openid = new URL(req.url, 'http://localhost').searchParams.get('openid');if (openid) {clients.set(openid, ws); // 将用户标识和 WebSocket 连接关联}// 监听客户端消息ws.on('message', (message) => {console.log('收到客户端消息:', message);});// 监听连接关闭ws.on('close', () => {console.log('客户端已断开连接');if (openid) {clients.delete(openid); // 移除用户标识}});
});// 推送登录状态
function pushLoginStatus(openid, userInfo) {const ws = clients.get(openid);if (ws) {ws.send(JSON.stringify({type: 'login_success',data: {openid,userInfo}}));}
}module.exports = { pushLoginStatus };

2. 在用户登录成功后推送消息

在用户登录成功的逻辑中,调用 pushLoginStatus 推送消息。

const { pushLoginStatus } = require('./websocket'); // 引入 WebSocket 模块app.get('/auth/callback', async (req, res) => {const { code } = req.query;if (!code) {return res.status(400).send("Code 参数获取失败");}try {// 1. 用 code 换取 access_token 和 openidconst { data: tokenData } = await axios.get(`https://api.weixin.qq.com/sns/oauth2/access_token`, {params: { appid, secret: appsecret, code, grant_type: "authorization_code" }});if (!tokenData.access_token || !tokenData.openid) return res.status(400).send("获取 access_token 失败");if (tokenData.scope !== 'snsapi_userinfo') return res.status(400).send("用户未授权");const { access_token, openid } = tokenData;// 2. 获取用户信息const { data: userInfo } = await axios.get(`https://api.weixin.qq.com/sns/userinfo`, {params: { access_token, openid, lang: "zh_CN" }});const { nickname, sex, province, city, headimgurl } = userInfo;// 3. 查询数据库,判断用户是否存在db.query(`SELECT * FROM users WHERE openid = ?`, [openid], (err, results) => {if (err) return res.status(500).send("数据库查询失败");if (results.length > 0) {// 老用户,更新 is_new 为 0db.query(`UPDATE users SET is_new = 0 WHERE openid = ?`,[openid],(updateErr, updateResults) => {if (updateErr) {console.error("更新 is_new 失败:", updateErr);return res.status(500).send("更新 is_new 失败");}// 推送登录状态pushLoginStatus(openid, results[0]);return res.status(200).send({status: 'success',message: "用户已存在",is_new: 0,data: results[0]});});} else {// 新用户,存入数据库db.query(`INSERT INTO users (openid, nickname, sex, province, city, headimgurl, is_new)VALUES (?, ?, ?, ?, ?, ?, 1)`,[openid, nickname, sex, province, city, headimgurl],(insertErr, insertResults) => {if (insertErr) {console.error("数据库操作失败:", insertErr);return res.status(500).send("数据库操作失败");}// 推送登录状态pushLoginStatus(openid, { openid, nickname, sex, province, city, headimgurl });return res.status(200).send({status: 'success',message: "新用户信息保存成功",is_new: 1,data: { openid, nickname, sex, province, city, headimgurl }});});}});} catch (err) {console.error("服务器错误:", err);res.status(500).send({status: 'error',message: "服务器错误",error: err.message || err.toString()});}
});

3. 前端实现 WebSocket 客户端

代码实现
export default {data() {return {isLoggedIn: false,userInfo: {},ws: null};},created() {this.connectWebSocket();},methods: {// 连接 WebSocketconnectWebSocket() {const openid = localStorage.getItem('userOpenID');if (!openid) return;this.ws = new WebSocket(`ws://localhost:8080?openid=${openid}`);this.ws.onopen = () => {console.log('已连接到 WebSocket 服务器');};this.ws.onmessage = (event) => {const data = JSON.parse(event.data);if (data.type === 'login_success') {localStorage.setItem('userOpenID', data.data.openid);this.isLoggedIn = true;this.userInfo = data.data.userInfo;this.$store.dispatch('saveUserInfo', this.userInfo);}};this.ws.onclose = () => {console.log('WebSocket 连接已关闭');};}},beforeDestroy() {// 组件销毁时关闭 WebSocket 连接if (this.ws) {this.ws.close();}}
};

4. 总结

  • 后端

    • 使用 WebSocket 服务器推送用户登录状态。
    • 在用户登录成功后,通过 pushLoginStatus 推送消息。
  • 前端

    • 连接 WebSocket 服务器,并监听登录状态的消息。
    • 在收到消息后,更新用户信息和登录状态。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/74322.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

发票查验/发票验真如何用Java实现接口调用

一、什么是发票查验?发票验真接口? 输入发票基本信息发票代码、发票号码、开票日期、校验码后6位、不含税金额、含税金额,核验发票真伪。 该接口也适用于机动车、二手车销售发票、航空运输电子客票、铁路电子客票等。 二、如何用Java实现接口…

html5-qrcode前端打开摄像头扫描二维码功能

实现的效果如图所示,全屏打开并且扫描到二维码后弹窗提醒,主要就是使用html5-qrcode这个依赖库,html5-qrcode开源地址:GitHub - mebjas/html5-qrcode: A cross platform HTML5 QR code reader. See end to end implementation at:…

cpp-友元

理解 C 中的友元(Friend) 在 C 语言中,封装(Encapsulation) 是面向对象编程的重要特性之一。它允许类将数据隐藏在私有(private)或受保护(protected)成员中,…

JavaWeb基础-HTTP协议、请求协议、响应协议

一. HTTP协议 1. HTTP协议:Hyper Text Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间数据传输的规则 2. HTTP协议特点: ① 基于TCP协议:面向链接,安全 ② 基于请求-响应模型的:一…

search_fields与filterset_fields的使用

在Django中,search_fields 和 filterset_fields 可以在视图类中使用,尤其是在 Django REST Framework (DRF) 中。它们分别用于实现搜索和过滤功能。以下是它们在视图类中的具体使用方法。 1. search_fields 在视图类中的使用 search_fields 是 DRF 中 S…

数据建模流程: 概念模型>>逻辑模型>>物理模型

数据建模流程 概念模型 概念模型是一种高层次的数据模型,用于描述系统中的关键业务概念及其之间的关系。它主要关注业务需求和数据需求,而不涉及具体的技术实现细节。概念模型通常用于在项目初期帮助业务人员和技术人员达成共识,确保对业务需…

在 Ubuntu 中用 Docker 安装 RAGFlow

一、安装 1.前提条件 CPU > 4 核 RAM > 16 GB Disk > 50 GB Docker > 24.0.0 & Docker Compose > v2.26.1 安装docker:在Ubuntu中安装Docker并配置国内镜像 2.设置 vm.max_map_count #设置 vm.max_map_count 不小于 262144# 查看 sysctl vm.…

Java随机生成n位验证码

Java学习笔记 今天写一个随机生成n位的验证码,包含字母大小写和数字,直接见代码。 package com.itheima.hello;// 生成一个随机位数的验证码 public class ScannerDemo1 {public static void main(String[] args){System.out.println(getCode(4));Syst…

go复习目录

全部都是博主的学习笔记,放着链接用的,自己收藏,包含基础内容、go三方包、vue、数据结构、web框架、设计模式、docker、go连接kafka、redis、grpc、中间件 文章目录 基础内容go三方包vue数据结构web框架设计模式dockergo连接kafkaredisgrpc中…

23种设计模式-创建型模式-抽象工厂

文章目录 简介场景问题1. 风格一致性失控2. 对象创建硬编码3. 产品族管理失效 解决总结 简介 抽象工厂是一种创建型设计模式,可以生成相关对象系列,而无需指定它们的具体类。 场景 假设你正在写一个家具店模拟器。 你的代码这些类组成: 相…

案例:网络命名空间模拟隔离主机场景

场景描述 假设我们需要在同一台物理机上模拟两台独立的主机(Host A 和 Host B),它们分别位于不同的网络命名空间中,并通过虚拟以太网对(veth pair)进行通信。目标是展示网络命名空间的隔离性和跨命名空间的…

新闻发布时间抽取(二)

1. 再论抽取方法 在前一期实验中,对gne组件进行分析和完善,对三种时间抽取的方法进行了实验对比。 在对抽取结果进行个例分析的过程中,我发现此前实验存在几个问题: 抽取的1000篇新闻存在一定的重复,经过ID去重大约减…

算法基础——栈

一、栈的概念 栈是⼀种只允许在⼀端进⾏数据插⼊和删除操作的线性表。 进⾏数据插⼊或删除的⼀端称为栈顶,另⼀端称为栈底。不含元素的栈称为空栈。进栈就是往栈中放⼊元素,出栈就是将元素弹出栈顶。 二、栈的模拟实现 1. 创建 本质还是线性表&#…

Android11至15系统定制篇

Android 11至15系统定制核心要点解析 一、Android 11关键定制特性 ‌分区存储强制化‌ 公共目录(如Downloads、Pictures)与应用专属目录分离,应用更新后无法通过requestLegacyExternalStorage绕过限制‌1。需申请MANAGE_EXTERNAL_STORAGE权限…

macOS 使用 enca 识别 文件编码类型(比 file 命令准确)

文章目录 macOS 上安装 enca基本使用起因 - iconv关于 enca安装 Encaenca & enconv 其它用法 macOS 上安装 enca brew install enca基本使用 enca filepath.txt示例 $ enca 动态规划算法.txt [0] Simplified Chinese National Standard; GB2312CRLF line terminat…

线段树与扫描线 —— 详解算法思想及其C++实现

目录 一、线段树(Segment Tree) 基本概念 结构 操作 示例代码 二、扫描线(Sweep Line) 基本概念 应用场景 示例代码(矩形面积并集) 三、总结 一、线段树(Segment Tree) 基本…

汇编代码中嵌入回调函数的优化说明

一、概述 在 PowerPC 的汇编代码中,我们需要实现调用 C 函数(例如回调函数),并传递参数。本文将详细介绍如何通过一系列步骤完成这一目标,包括代码示例和详细的注释。 二、调用 C 函数的基本步骤及代码 1. 保存工作寄…

Uni-App 双栏联动滚动组件开发详解 (电梯导航)

本文基于提供的代码实现一个左右联动的滚动组件&#xff0c;以下是详细的代码解析与实现原理说明&#xff1a; <!--双栏联动滚动组件 - 技术解析功能特性&#xff1a;1. 左侧导航栏与右侧内容区双向联动2. 自适应容器高度3. 平滑滚动定位4. 动态内容位置计算 --> <te…

软考复习-传输介质与编码

传输介质 双绞线 传输距离100一200m&#xff0c;即网线&#xff0c;有多种分类 UTP非屏蔽双绞线 STP屏蔽双绞线 线序标准有两种为&#xff1a; T568A标准&#xff1a;绿白、绿、橙白、蓝、蓝白、橙、棕白、棕 T568B标准&#xff1a;橙白、橙、绿白、蓝、蓝白、绿、棕白、…

论文阅读笔记:Denoising Diffusion Probabilistic Models (3)

论文阅读笔记&#xff1a;Denoising Diffusion Probabilistic Models (1) 论文阅读笔记&#xff1a;Denoising Diffusion Probabilistic Models (2) 论文阅读笔记&#xff1a;Denoising Diffusion Probabilistic Models (3) 4、损失函数逐项分析 可以看出 L L L总共分为了3项…