用户模块握手验证
用户模块——握手验证
1. 引言
在现代 Web 应用中, WebSocket 以其 全双工通信、低延迟、减少 HTTP 开销 等优势,被广泛应用于即时通讯、在线游戏、实时数据推送等场景。然而,在涉及 用户认证 时,WebSocket 存在一个常见问题—— 每次刷新页面都会重新建立 WebSocket 连接,导致用户需要重新认证 。
1.1 WebSocket 连接与用户认证的挑战
WebSocket 连接是 长连接 ,与传统 HTTP 请求不同,它并不会在每次通信时都自动携带身份认证信息(如 Cookie、Session)。这意味着:
- 页面刷新后,WebSocket 连接会断开并重新建立 ,服务器无法自动识别用户身份,必须重新进行认证。
- 传统的 WebSocket 认证流程较长
,通常是:
- 前端建立 WebSocket 连接;
- 连接成功后,前端发送 Token 进行身份验证;
- 服务器验证 Token,返回认证结果。
这样下来,整个认证过程涉及 三次数据往返 (RTT,Round Trip Time),不仅增加了服务器压力,还会影响用户体验,尤其是在网络延迟较高的环境下,认证速度会明显变慢。
1.2 目标:减少认证回合,提高用户体验
为了解决这个问题,我们希望优化 WebSocket 认证流程,目标是:
- 减少认证数据往返次数 ,从三次缩减为两次甚至一次;
- 在 WebSocket 连接建立时就完成认证 ,避免额外的认证消息交互;
- 确保认证方式安全可靠 ,防止 Token 泄露或被劫持。
通过对 WebSocket 连接建立过程的深入研究,我们发现可以在 WebSocket 握手阶段 (仍然是 HTTP 请求)直接传递 Token,从而让服务器 在握手时就完成身份认证 。这样可以大大减少通信回合,提高用户体验。
2. WebSocket 认证优化思路
为了减少 WebSocket 认证的额外开销,我们需要优化传统的认证流程,让用户在刷新页面时不需要额外发送认证请求,而是 在 WebSocket 连接建立的同时完成认证 。本节将详细介绍 WebSocket 认证的优化方向,并对比不同方案的优缺点。
2.1 传统 WebSocket 认证方式及其问题
在标准的 WebSocket 连接流程中,前端通常按照以下方式进行身份认证:
- 建立 WebSocket 连接
(
new WebSocket(url)
)。 - WebSocket 连接建立后,前端发送 Token
(
ws.send(token)
)。 - 服务器接收 Token 并进行身份验证 。
- 服务器返回认证结果,前端决定是否继续通信 。
这种方式的主要问题是:
- 增加了不必要的通信回合 。连接建立后,还需要额外的消息往返进行身份认证,导致 三次通信 (RTT3)。
- 影响用户体验 。网络延迟高时,用户可能需要等待较长时间才能完成认证,体验不够流畅。
- 安全性问题
。Token 通过 WebSocket 发送,可能被恶意中间人劫持,存在一定的安全风险(虽然 WebSocket 连接一般是
wss://
加密的,但仍需谨慎)。
2.2 目标:让 WebSocket 认证更高效
为了优化 WebSocket 认证,我们希望达到以下目标:
- 减少通信回合 ,最好在 WebSocket 连接建立的同时完成认证 ,避免额外的认证数据交互。
- 不依赖额外的 WebSocket 消息 进行认证,而是在 握手阶段(WebSocket Upgrade 请求) 直接完成 Token 认证。
- 兼容性强,安全性高 ,避免 Token 通过 WebSocket 发送,减少被劫持的风险。
基于以上目标,我们提出两种优化方案:
2.3 方案一:通过 URL 传递 Token(推荐方案)
实现思路:
在 WebSocket 连接 URL 中直接拼接 Token,例如:
const ws = new WebSocket("wss://example.com/ws?token=your_token_here");
服务器在 Netty 中解析 WebSocket 握手请求的 URL,提取 Token 进行身份认证,认证成功后绑定 Token 到用户的 WebSocket 会话中。
优点:
✅ 减少一次往返 ,从 RTT3 降低为 RTT2 (握手 + 服务器返回认证结果)。
✅ 无需额外 WebSocket 消息 进行认证,认证逻辑清晰。
✅ 易于实现 ,只需在 WebSocket 握手阶段解析 URL 参数即可。
注意事项:
⚠ 安全性问题 :
- 避免 Token 长期存留在 URL 记录 (如浏览器历史、服务器日志等)。
- 可以在服务器解析 Token 后立即从 URL 中删除 ,避免 Token 泄露。
2.4 方案二:利用 HTTP 头传递 Token(更安全但兼容性稍差)
实现思路:
WebSocket 的初始握手是基于 HTTP 进行的,因此可以利用 HTTP 头部(Authorization) 传递 Token,例如:
const ws = new WebSocket("wss://example.com/ws", [], {
headers: { Authorization: "Bearer your_token_here" }
});
然后,在 Netty 服务器中解析
Authorization
头,提取 Token 进行身份认证。
优点:
✅ 安全性更高 ,Token 不会出现在 URL 中,避免泄露风险。
✅ 减少 WebSocket 消息认证,直接在握手时完成身份校验 。
缺点:
⚠
前端受限
:浏览器原生
WebSocket
API
不支持自定义 HTTP 头
,必须通过
自定义 WebSocket 客户端
(如 Node.js 或 JavaScript WebSocket 库)实现。
⚠ 兼容性问题 :部分 WebSocket 服务器和代理可能不会转发自定义 HTTP 头,需要额外配置。
2.5 方案三(失败方案):利用 WebSocket protocols
传递 Token
WebSocket 连接时支持
protocols
参数,原本希望使用它来传递 Token,例如:
const ws = new WebSocket("wss://example.com/ws", ["token_your_token_here"]);
然而,WebSocket
protocols
的作用是
指定子协议
(如
graphql-ws
),并不是设计来传递 Token,因此
服务器端 Netty 解析
protocols
时无法正确获取 Token
。
最终结论: 该方案不可行,不推荐使用。
2.6 方案对比与最终选择
方案 | 传递方式 | 兼容性 | 安全性 | 优势 | 适用场景 |
---|---|---|---|---|---|
URL 拼接 Token | URL 参数 | ✅ 浏览器支持 | ⚠ 需要删除 Token | 🚀 简单易用,减少一次通信 | 推荐,适合大多数情况 |
HTTP 头部 Token | Authorization 头 | ❌ 需自定义客户端 | ✅ 安全性高 | 🚀 服务器端认证直接完成 | 适合服务器或非浏览器环境 |
WebSocket protocols | protocols | ❌ 不支持 | ❌ 不安全 | ❌ 无法正确传递 Token | 失败方案,不推荐 |
综合来看, “URL 拼接 Token” 是最简单、最易用、且兼容性最好的方案 ,因此推荐作为主要优化方式。
2.7 结论与下一步
通过在 WebSocket 连接握手阶段 直接传递 Token,我们可以有效减少不必要的认证回合,优化用户体验。在接下来的章节,我们将深入讲解如何在 Netty 服务器端 解析 Token 并完成身份认证,确保 WebSocket 认证的安全性和稳定性。
3. WebSocket 握手阶段传递 Token 的技术实现
在上一节中,我们确定了 在 WebSocket 连接握手阶段传递 Token 是优化 WebSocket 认证的最佳方案。本节将详细讲解如何 在前端传递 Token ,以及 如何在 Netty 服务器端解析和验证 Token ,确保 WebSocket 连接的安全性和高效性。
3.1 前端如何在 WebSocket 握手时传递 Token
方案一:通过 URL 传递 Token(推荐)
在 WebSocket 连接 URL 中直接拼接 Token,例如:
const token = "your_token_here"; // 这个 Token 一般从 localStorage 或 cookies 获取
const ws = new WebSocket(`wss://example.com/ws?token=${encodeURIComponent(token)}`);
📌 注意事项:
encodeURIComponent(token)
作用是对 Token 进行 URL 编码,避免特殊字符影响 URL 解析。- 不要让 Token 长时间存留在 URL 记录中 ,服务器端拿到 Token 后要尽快清理,防止 Token 泄露。
方案二:使用 HTTP 头传递 Token(仅适用于非浏览器环境)
如果 WebSocket 连接是由 Node.js 或其他后端服务 发起的,可以使用 HTTP 头传递 Token,例如:
const WebSocket = require('ws');
const ws = new WebSocket("wss://example.com/ws", {
headers: {
Authorization: `Bearer your_token_here`
}
});
📌 注意事项:
- 这种方式 浏览器原生 WebSocket API 不支持 ,适用于后端对后端(Server-to-Server)WebSocket 连接。
- 服务器端需要在 HTTP 头中提取
Authorization
并解析 Token。
3.2 Netty 服务器端如何解析 Token
无论 Token 是通过 URL 传递 还是 HTTP 头传递 ,Netty 服务器端的逻辑主要分为三步:
- 在 WebSocket 握手阶段拦截 HTTP 请求 。
- 提取 Token 并进行身份认证 。
- 认证成功后,将 Token 绑定到 WebSocket 连接(Channel) ,用于后续通信。
3.2.1 Netty 服务器端代码实现
(1)自定义 WebSocket 握手处理器
我们需要在 WebSocket 握手阶段解析 Token,并存储到 Channel 的
Attributes
中,方便后续使用。
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import java.util.List;
import java.util.Map;
public class WebSocketHandshakeHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) {
// 解析 Token
String token = extractToken(req);
if (token == null || !isValidToken(token)) {
// 认证失败,关闭连接
ctx.close();
return;
}
// 认证成功,将 Token 绑定到 Channel
ctx.channel().attr(AttributeKey.valueOf("token")).set(token);
// 继续处理 WebSocket 握手
ctx.fireChannelRead(req.retain());
}
// 解析 URL 中的 Token
private String extractToken(FullHttpRequest req) {
QueryStringDecoder decoder = new QueryStringDecoder(req.uri());
Map<String, List<String>> params = decoder.parameters();
if (params.containsKey("token")) {
return params.get("token").get(0);
}
return null;
}
// 这里可以调用自己的 Token 认证逻辑,例如 JWT 解析
private boolean isValidToken(String token) {
return "your_token_here".equals(token); // 这里替换成实际的 Token 验证逻辑
}
}
📌 代码解读:
channelRead0()
方法会在 WebSocket 握手阶段拦截 HTTP 请求。extractToken(req)
方法用于从 URL 解析 Token。isValidToken(token)
方法用于验证 Token 的有效性(这里可以换成 JWT 解析)。ctx.channel().attr(AttributeKey.valueOf("token")).set(token);
用于将 Token 绑定到 Channel,方便后续使用。
(2)WebSocket 握手完成后,继续处理 WebSocket 消息
WebSocket 握手完成后,我们需要创建 WebSocket 处理器,确保后续通信时能够获取用户身份。
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.AttributeKey;
public class WebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
// 读取用户 Token(之前存储在 Channel 的 Attribute 中)
String token = (String) ctx.channel().attr(AttributeKey.valueOf("token")).get();
if (token == null) {
ctx.close();
return;
}
// 处理 WebSocket 消息
System.out.println("Received message: " + msg.text());
ctx.writeAndFlush(new TextWebSocketFrame("Server received your message: " + msg.text()));
}
}
📌 代码解读:
ctx.channel().attr(AttributeKey.valueOf("token")).get();
读取 WebSocket 握手阶段存储的 Token。- 如果 Token 为空,说明认证失败,关闭连接。
- 认证成功后,继续处理 WebSocket 消息。
3.3 Netty 服务器端完整 Pipeline 组装
在 Netty 服务器启动时,我们需要正确组装 WebSocket 握手处理器和消息处理器。
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new HttpServerCodec()); // HTTP 解析
ch.pipeline().addLast(new HttpObjectAggregator(65536)); // 处理完整 HTTP 请求
ch.pipeline().addLast(new WebSocketHandshakeHandler()); // 解析 Token 并认证
ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws")); // WebSocket 握手
ch.pipeline().addLast(new WebSocketFrameHandler()); // 处理 WebSocket 消息
}
}
📌 代码解读:
HttpServerCodec
处理 HTTP 请求和响应。HttpObjectAggregator
处理 HTTP 完整请求(包括 WebSocket 握手)。WebSocketHandshakeHandler
解析 Token 并进行认证。WebSocketServerProtocolHandler("/ws")
处理 WebSocket 协议升级。WebSocketFrameHandler
处理 WebSocket 消息传输。
4. Netty 服务器端 WebSocket 认证优化
在上一节中,我们实现了 WebSocket 握手阶段的 Token 传递和验证 ,确保 WebSocket 连接在建立时完成身份认证。然而,现有方案仍然存在优化空间,包括:
- Token 认证逻辑的优化 :减少重复解析 Token,提高认证效率。
- Token 续期机制 :当 Token 即将过期时,支持在线续期,避免用户被强制下线。
- 多端登录管理 :确保同一账号可以安全地在多个设备登录,或限制单设备登录。
- 权限控制优化 :不同用户类型可访问不同的 WebSocket 通道,提高安全性和可维护性。
本节将针对这些优化点,详细讲解 Netty 服务器端的优化方案和实现方法。
4.1 Token 认证逻辑优化
🔹 问题:每次握手都要解析 Token,导致性能损耗
在当前实现中,每次 WebSocket 握手时,服务器都需要解析并验证 Token。如果 Token 解析逻辑复杂(如 JWT 解析、数据库查询等),可能会影响服务器性能。
✅ 解决方案:引入缓存,加速 Token 解析
可以使用 本地缓存(如 Caffeine)或分布式缓存(如 Redis) ,存储已验证的 Token,避免重复解析。例如:
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
public class TokenCache {
// 使用 Caffeine 作为本地缓存,Token 过期时间设为 10 分钟
private static final Cache<String, Boolean> tokenCache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10_000)
.build();
// 校验 Token
public static boolean isValidToken(String token) {
// 先查缓存
Boolean isValid = tokenCache.getIfPresent(token);
if (isValid != null) {
return isValid;
}
// 若缓存中不存在,则进行 Token 解析(可以是 JWT 解析、数据库查询等)
boolean valid = verifyTokenFromDatabaseOrJWT(token);
// 缓存结果,避免重复解析
tokenCache.put(token, valid);
return valid;
}
// 这里模拟一个 Token 验证逻辑
private static boolean verifyTokenFromDatabaseOrJWT(String token) {
return "valid_token".equals(token); // 实际上应调用数据库或 JWT 解析库
}
}
📌 优化效果 :
- 第一次验证 Token 仍然执行完整的解析逻辑。
- 之后的请求将直接从缓存读取,减少计算量,提高认证效率。
- 10 分钟后 Token 过期,防止长期缓存导致安全隐患。
4.2 Token 续期机制
🔹 问题:Token 过期后,WebSocket 连接被强制断开,用户体验差
如果 Token 有效期较短,用户需要频繁重新连接 WebSocket,影响体验。
✅ 解决方案:支持 Token 续期
可以在 WebSocket 连接中定期检查 Token 是否即将过期 ,并在即将过期时让客户端自动更新 Token,避免断开连接。例如:
1️⃣ 服务器端实现 Token 续期检测
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import java.util.concurrent.ConcurrentHashMap;
public class TokenManager {
private static final ConcurrentHashMap<Channel, String> channelTokenMap = new ConcurrentHashMap<>();
// 绑定 Token 到 Channel
public static void bindToken(Channel channel, String token) {
channel.attr(AttributeKey.valueOf("token")).set(token);
channelTokenMap.put(channel, token);
}
// 检查 Token 是否即将过期
public static boolean isTokenExpiringSoon(String token) {
return token.equals("expiring_token"); // 模拟 Token 过期检查
}
// 更新 Token
public static void refreshToken(Channel channel, String newToken) {
channel.attr(AttributeKey.valueOf("token")).set(newToken);
channelTokenMap.put(channel, newToken);
}
}
2️⃣ 客户端定期检查 Token 过期,并更新 Token
function checkTokenExpiry() {
setInterval(() => {
fetch('/api/check-token') // 询问服务器 Token 是否即将过期
.then(response => response.json())
.then(data => {
if (data.expiring) {
refreshToken(); // 如果快过期了,就刷新 Token
}
});
}, 30000); // 每 30 秒检查一次
}
function refreshToken() {
fetch('/api/refresh-token')
.then(response => response.json())
.then(data => {
ws.close(); // 关闭当前 WebSocket 连接
ws = new WebSocket(`wss://example.com/ws?token=${encodeURIComponent(data.newToken)}`);
});
}
📌 优化效果 :
- 避免 Token 过期导致 WebSocket 断开 ,提升用户体验。
- 仅在 Token 需要续期时进行刷新 ,减少不必要的 Token 解析操作。
4.3 多端登录管理
🔹 问题:同一账号可能在多个设备登录,导致 Token 被滥用
例如,用户在 PC 和手机同时使用 WebSocket,可能导致 Token 重复使用,带来安全风险。
✅ 解决方案:限制同一账号的 WebSocket 连接数量
可以使用 Redis 记录当前在线的 Token ,如果发现同一账号的 Token 在多个设备登录,则主动踢掉旧连接。
Netty 服务器端实现
import java.util.concurrent.ConcurrentHashMap;
import io.netty.channel.Channel;
public class WebSocketSessionManager {
private static final ConcurrentHashMap<String, Channel> userSessions = new ConcurrentHashMap<>();
public static void bindUser(String userId, Channel channel) {
if (userSessions.containsKey(userId)) {
// 踢掉之前的连接
userSessions.get(userId).close();
}
userSessions.put(userId, channel);
}
public static void removeUser(String userId) {
userSessions.remove(userId);
}
}
📌 优化效果 :
- 防止 Token 被盗用 ,限制同时在线的 WebSocket 连接数量。
- 确保 WebSocket 连接始终是最新的 ,避免同一用户在多个设备上造成数据不一致。
4.4 权限控制优化
🔹 问题:不同用户类型需要访问不同的 WebSocket 通道
例如:
- 普通用户只能订阅
public_chat
频道。 - 管理员可以订阅
admin_dashboard
频道。
✅ 解决方案:基于角色的 WebSocket 频道管理
在服务器端,解析 Token 后,将 用户角色信息绑定到 Channel ,限制用户访问的 WebSocket 频道。
public class WebSocketAuthHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
String role = ctx.channel().attr(AttributeKey.valueOf("role")).get().toString();
if (!"admin".equals(role) && msg.text().contains("admin_dashboard")) {
ctx.writeAndFlush(new TextWebSocketFrame("Permission denied"));
return;
}
ctx.fireChannelRead(msg.retain());
}
}
📌 优化效果 :
- 基于 Token 角色限制 WebSocket 频道访问 ,提高安全性。
- 确保不同用户类型只能访问自己的数据 ,防止权限泄露。
总结
本节优化了 WebSocket 认证,包括:
✔ 缓存 Token 提高认证效率
✔ 支持 Token 续期,避免连接断开
✔ 限制多端登录,提升安全性
✔ 基于角色的权限管理
5. 认证通过后的用户信息推送与刷新
在 WebSocket 认证成功后,用户的身份信息已经确定,此时可以进行 用户信息的推送和刷新 。这一环节主要涉及:
- 用户连接后,主动推送用户的最新信息 ,确保前端拿到最新状态。
- 用户信息发生变化(如昵称、头像、权限等)时,自动推送更新 ,避免前端数据滞后。
- 支持前端主动请求刷新用户信息 ,保证客户端在需要时能够获取最新数据。
下面,我们逐步分析如何在 Netty 服务器端 实现这些功能。
5.1 用户连接后主动推送信息
🔹 问题:用户 WebSocket 连接成功后,前端需要获取最新用户信息
当 WebSocket 连接建立后,前端可能需要:
- 获取用户的基本信息(如用户名、头像、权限等)。
- 获取用户的未读消息数量。
- 获取其他应用状态数据(如好友在线状态、系统通知等)。
✅ 解决方案:在 WebSocket 握手成功后,服务器主动推送用户信息
我们可以在 WebSocket 握手完成时 ,从数据库或缓存中读取用户信息,并主动推送给前端。
Netty 服务器端实现
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import com.fasterxml.jackson.databind.ObjectMapper;
public class UserInfoPushHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
private static final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void channelActive(ChannelHandlerContext ctx) {
Channel channel = ctx.channel();
String userId = channel.attr(AttributeKey.valueOf("userId")).get().toString();
// 查询用户信息
UserInfo userInfo = getUserInfoFromDatabase(userId);
// 发送用户信息到前端
try {
String userInfoJson = objectMapper.writeValueAsString(userInfo);
channel.writeAndFlush(new TextWebSocketFrame(userInfoJson));
} catch (Exception e) {
e.printStackTrace();
}
}
private UserInfo getUserInfoFromDatabase(String userId) {
// 这里应该是数据库查询或缓存获取
return new UserInfo(userId, "张三", "/avatar.png", "admin");
}
}
📌 优化效果 :
- 用户连接后立即获取最新信息 ,无需前端主动请求。
- 避免前端页面出现数据缺失或延迟加载的问题 。
5.2 用户信息变化时自动推送更新
🔹 问题:如果用户信息在其他地方修改了,WebSocket 连接的前端不会自动更新
例如:
- 用户修改了头像或昵称,前端不会自动更新。
- 管理员修改了用户权限,但 WebSocket 连接中的用户权限仍然是旧的。
✅ 解决方案:服务器监听用户信息变更,主动通知所有在线 WebSocket 客户端
可以使用 消息队列(如 Redis Pub/Sub、Kafka)或事件驱动机制 ,当用户信息更新时,通知 WebSocket 服务器推送消息。
1️⃣ 监听用户信息变更事件
import java.util.concurrent.ConcurrentHashMap;
import io.netty.channel.Channel;
public class UserSessionManager {
private static final ConcurrentHashMap<String, Channel> userChannelMap = new ConcurrentHashMap<>();
public static void bindUser(String userId, Channel channel) {
userChannelMap.put(userId, channel);
}
public static void unbindUser(String userId) {
userChannelMap.remove(userId);
}
// 当用户信息更新时,推送最新数据
public static void pushUserInfoUpdate(String userId, UserInfo newUserInfo) {
Channel channel = userChannelMap.get(userId);
if (channel != null && channel.isActive()) {
try {
String json = new ObjectMapper().writeValueAsString(newUserInfo);
channel.writeAndFlush(new TextWebSocketFrame(json));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2️⃣ 用户信息修改时触发推送
public class UserService {
public void updateUserInfo(String userId, String newNickname, String newAvatar) {
// 更新数据库中的用户信息
UserInfo updatedUser = updateUserInDatabase(userId, newNickname, newAvatar);
// 触发 WebSocket 推送
UserSessionManager.pushUserInfoUpdate(userId, updatedUser);
}
private UserInfo updateUserInDatabase(String userId, String nickname, String avatar) {
return new UserInfo(userId, nickname, avatar, "admin"); // 模拟数据库更新
}
}
📌 优化效果 :
- 当用户信息变更时,WebSocket 服务器自动推送最新信息 ,确保前端数据一致。
- 无需前端轮询,大幅降低服务器负载 。
5.3 前端主动请求刷新用户信息
🔹 问题:某些情况下,前端可能需要主动刷新用户信息
例如:
- 用户希望手动刷新个人资料。
- WebSocket 连接异常后重新同步数据。
- 用户切换页面后,希望重新获取最新数据。
✅ 解决方案:前端发送请求,服务器返回最新的用户信息
我们可以在 WebSocket 服务器端监听 “refreshUserInfo” 事件,当收到该消息时,返回最新的用户信息。
Netty 服务器端处理用户刷新请求
public class UserInfoRefreshHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
if ("refreshUserInfo".equals(msg.text())) {
String userId = ctx.channel().attr(AttributeKey.valueOf("userId")).get().toString();
UserInfo userInfo = getUserInfoFromDatabase(userId);
try {
String json = new ObjectMapper().writeValueAsString(userInfo);
ctx.channel().writeAndFlush(new TextWebSocketFrame(json));
} catch (Exception e) {
e.printStackTrace();
}
} else {
ctx.fireChannelRead(msg.retain());
}
}
}
前端请求刷新用户信息
// 发送刷新请求
function requestUserInfoRefresh() {
ws.send("refreshUserInfo");
}
// 监听 WebSocket 返回的新用户信息
ws.onmessage = function(event) {
const userInfo = JSON.parse(event.data);
console.log("用户信息更新:", userInfo);
};
📌 优化效果 :
- 前端可以在任意时间主动请求最新用户信息 ,提升灵活性。
- 服务器只在必要时返回数据,减少不必要的推送,提高效率 。
总结
本节介绍了 WebSocket 认证通过后,如何推送和刷新用户信息 ,主要优化点包括:
✔ WebSocket 连接建立后,服务器主动推送用户最新信息 ,避免前端等待。
✔ 当用户信息发生变更时,服务器自动推送更新 ,确保数据一致。
✔ 前端可以主动请求刷新用户信息 ,避免数据滞后。