目录

用户模块握手验证

目录

用户模块——握手验证

1. 引言

在现代 Web 应用中, WebSocket 以其 全双工通信、低延迟、减少 HTTP 开销 等优势,被广泛应用于即时通讯、在线游戏、实时数据推送等场景。然而,在涉及 用户认证 时,WebSocket 存在一个常见问题—— 每次刷新页面都会重新建立 WebSocket 连接,导致用户需要重新认证

1.1 WebSocket 连接与用户认证的挑战

WebSocket 连接是 长连接 ,与传统 HTTP 请求不同,它并不会在每次通信时都自动携带身份认证信息(如 Cookie、Session)。这意味着:

  1. 页面刷新后,WebSocket 连接会断开并重新建立 ,服务器无法自动识别用户身份,必须重新进行认证。
  2. 传统的 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 连接流程中,前端通常按照以下方式进行身份认证:

  1. 建立 WebSocket 连接new WebSocket(url) )。
  2. WebSocket 连接建立后,前端发送 Tokenws.send(token) )。
  3. 服务器接收 Token 并进行身份验证
  4. 服务器返回认证结果,前端决定是否继续通信

这种方式的主要问题是:

  • 增加了不必要的通信回合 。连接建立后,还需要额外的消息往返进行身份认证,导致 三次通信 (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 拼接 TokenURL 参数✅ 浏览器支持⚠ 需要删除 Token🚀 简单易用,减少一次通信推荐,适合大多数情况
HTTP 头部 TokenAuthorization❌ 需自定义客户端✅ 安全性高🚀 服务器端认证直接完成适合服务器或非浏览器环境
WebSocket protocolsprotocols❌ 不支持❌ 不安全❌ 无法正确传递 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 服务器端的逻辑主要分为三步:

  1. 在 WebSocket 握手阶段拦截 HTTP 请求
  2. 提取 Token 并进行身份认证
  3. 认证成功后,将 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 连接在建立时完成身份认证。然而,现有方案仍然存在优化空间,包括:

  1. Token 认证逻辑的优化 :减少重复解析 Token,提高认证效率。
  2. Token 续期机制 :当 Token 即将过期时,支持在线续期,避免用户被强制下线。
  3. 多端登录管理 :确保同一账号可以安全地在多个设备登录,或限制单设备登录。
  4. 权限控制优化 :不同用户类型可访问不同的 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 认证成功后,用户的身份信息已经确定,此时可以进行 用户信息的推送和刷新 。这一环节主要涉及:

  1. 用户连接后,主动推送用户的最新信息 ,确保前端拿到最新状态。
  2. 用户信息发生变化(如昵称、头像、权限等)时,自动推送更新 ,避免前端数据滞后。
  3. 支持前端主动请求刷新用户信息 ,保证客户端在需要时能够获取最新数据。

下面,我们逐步分析如何在 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 连接建立后,服务器主动推送用户最新信息 ,避免前端等待。

当用户信息发生变更时,服务器自动推送更新 ,确保数据一致。

前端可以主动请求刷新用户信息 ,避免数据滞后。