当前位置: 首页 > news >正文

HTTP、HTTPS 和 WebSocket 协议和开发

目录

HTTP(超文本传输协议)

 HTTPS(HTTP Secure)

WebSocket

三者关系

深入剖析 HTTP 的演进

1. HTTP/1.0 (1996) - “一次性邮差”

2. HTTP/1.1 (1997) - “可复用的邮差” - 目前仍最主流

3.HTTP/2 (2015) - “超级高速公路”

HTTP/1.1 vs HTTP/2 性能对比

WebSocket 再深入:它是如何“升级”的?

WebSocket 和Http/2

1. 通信模型的根本差异(这是最重要的区别)

2. 对“服务器推送”的误解澄清

3. 如何选择?

开发中(SpringBoot Vue)

HTTP请求的接收和发送

SpringBoot后端

接收HTTP请求

表面现象:为什么我们不需要手动构建HTTP响应

发送HTTP请求(调用外部API)

1.使用RestTemplate

2.WebClient 详细讲解

Vue前端

发送 + 接收HTTP请求

WebSocket完整通信

后端

前端

重新讲解WebSocket

用一个超形象的比喻理解WebSocket

传统HTTP vs WebSocket

WebSocket 实际开发详解(用WebSocketHandler)

第一步:Spring Boot后端设置

1. 添加依赖

2. 创建WebSocket配置(就像安装电话线路)

3. 创建WebSocket处理器(就像接线员)

第二步:Vue前端使用WebSocket

1. 创建WebSocket聊天组件

四个关键事件:

核心方法:

连接状态:

WebSocket 实际开发详解(用@ServerEndpoint)

第一步:Spring Boot后端配置

1. 添加依赖(与之前相同)

2. 关键配置:启用ServerEndpoint

3. 使用@ServerEndpoint创建WebSocket端点

4. 业务服务示例(展示依赖注入)

第二步:Vue前端使用(与之前类似,但连接地址不同)

@ServerEndpoint 核心注解说明

四个核心注解:

重要特性:

两种方式对比总结

选择建议:


协议特点运行过程(简述)OSI 层TCP/IP 层
HTTP明文无状态请求-响应模型1. 建立 TCP 连接
2. 客户端发送请求
3. 服务器返回响应
4. 关闭连接
应用层应用层
HTTPS加密身份验证完整性校验1. 建立 TCP 连接
2. TLS 握手(交换密钥)
3. 在加密通道内进行 HTTP 通信
应用层应用层
WebSocket全双工长连接低延迟1. HTTP 握手(Upgrade 请求)
2. 连接升级为 WebSocket
3. 双向持久通信,无需重复握手
应用层应用层

HTTP(超文本传输协议)

所属层级: 应用层协议。它基于传输层的 TCP 协议。

特点:

  1. 明文传输:请求和响应的内容都是未加密的,容易被窃听和篡改。

  2. 无状态:服务器不记录每次请求之间的关联信息。每个请求都是独立的(通常使用 Cookie/Session 技术来弥补这一缺陷)。

  3. 请求-响应模型:通信总是由客户端(如浏览器)发起,服务器被动响应。服务器不会主动向客户端推送消息。

  4. 简单灵活:传输的内容类型由 Content-Type 标头定义,可以传输任意类型的数据。

运行过程:

  1. 建立 TCP 连接:客户端(浏览器)首先与服务器的 80 端口建立一个可靠的 TCP 连接。

  2. 发送 HTTP 请求:客户端通过这个连接发送一个请求报文,包含:

    • 请求行(方法:GET/POST,URL,协议版本)

    • 请求头(Host, User-Agent, Accept 等)

    • 请求体(可选,如 POST 方法提交的表单数据)

  3. 服务器处理并返回响应:服务器解析请求,处理业务逻辑,然后返回一个响应报文,包含:

    • 状态行(状态码:200 OK,404 Not Found 等)

    • 响应头(Content-Type, Content-Length, Set-Cookie 等)

    • 响应体(请求的资源,如 HTML、图片、JSON 数据等)

  4. 关闭连接:在 HTTP/1.0 中,每次请求-响应后都会关闭 TCP 连接。HTTP/1.1 引入了持久连接,允许在同一个连接上进行多次请求-响应,减少了建立连接的开销。

 HTTPS(HTTP Secure)

所属层级: 应用层协议。它是在 HTTP 和 TCP 之间加入了一个安全层(SSL/TLS)。

特点:

  1. 加密传输:通过 SSL/TLS 协议对通信内容进行加密,防止数据被窃听。

  2. 身份验证:通过数字证书验证服务器的身份,防止中间人攻击。

  3. 完整性保护:通过消息认证码来校验数据在传输过程中是否被篡改。

  4. 本质还是 HTTP:在建立安全通道后,通信的内容和方式与 HTTP 完全一样。

运行过程:

  1. 建立 TCP 连接:客户端连接到服务器的 443 端口。

  2. TLS 握手:这是 HTTPS 安全的核心。

    • 客户端 Hello:客户端向服务器发送支持的加密算法列表和一个随机数。

    • 服务器 Hello:服务器选择加密算法,发送自己的数字证书和一个随机数。

    • 验证证书:客户端验证证书的合法性(是否由可信机构颁发,域名是否匹配等)。

    • 生成会话密钥:客户端用证书中的公钥加密一个预主密钥并发送给服务器。

    • 生成会话密钥:服务器用自己的私钥解密得到预主密钥。此时,客户端和服务器都拥有了三个随机数(客户端随机数、服务器随机数、预主密钥),它们使用相同的算法生成唯一的会话密钥

    • 握手结束:双方交换加密完成的“Finished”消息,验证握手是否成功。

  3. 加密的 HTTP 通信:之后的整个 HTTP 请求和响应过程,都使用上一步生成的会话密钥进行加密和解密。

  4. 关闭连接

WebSocket

所属层级: 应用层协议。它同样基于 TCP,并借用了 HTTP 的握手过程。

特点:

  1. 全双工通信:连接建立后,服务器和客户端可以同时向对方发送消息。

  2. 持久化长连接:只需一次握手,连接就会一直保持,避免了 HTTP 的频繁建立和断开连接的开销。

  3. 低延迟:由于没有频繁的握手和头部信息,通信效率极高,非常适合实时应用。

  4. 服务器主动推送:服务器可以随时主动向客户端发送数据,完美解决了 HTTP 轮询带来的性能问题。

运行过程:

  1. HTTP 握手请求:客户端首先发送一个特殊的 HTTP 请求,其头部包含:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
    • Upgrade: websocket 和 Connection: Upgrade 表明客户端希望将协议升级为 WebSocket。

        2. HTTP 握手响应:服务器如果同意升级,会返回一个状态码为 101 Switching Protocols 的响应:

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    • Sec-WebSocket-Accept 是对客户端发送的 Key 计算后的结果,用于验证握手有效性。

         3. WebSocket 数据传输:握手成功后,TCP 连接保持,但通信协议从 HTTP 切换到了 WebSocket。此时,双方使用 WebSocket 定义的数据帧格式进行双向、低开销的数据传输。

         4. 关闭连接:任何一方都可以发送一个关闭帧来优雅地终止连接。

    三者关系

    +-------------------------------------------------------------------+
    |                   应用层 (Application Layer)                      |
    +-------------------+-------------------+---------------------------+
    |      HTTP 1.x     |      HTTP/2       |       WebSocket           |
    |    (明文,队头阻塞) |   (二进制,多路复用)  |    (全双工,持久连接)      |
    +-------------------+-------------------+---------------------------+
    |         HTTPS (HTTP + TLS/SSL) 安全的传输层                        |
    +-------------------------------------------------------------------+
    |                   传输层 (Transport Layer)                        |
    |                           TCP                                     |
    +-------------------------------------------------------------------+
    |                   网络层 (Network Layer)                          |
    |                           IP                                      |
    +-------------------------------------------------------------------+

    核心比喻:

    • TCP/IP:相当于高速公路系统。它负责把数据包从A点可靠地送到B点。
    • HTTP:相当于在高速公路上跑的邮递车。它负责运送具体的“信件”(网页、图片等),但每次送货都需要重新装货、发货、收货确认,效率较低。
    • WebSocket:相当于在高速公路上建立的一条专用管道。一旦建好,双方可以随时、持续地向管道里扔东西,效率极高。

    深入剖析 HTTP 的演进

    1. HTTP/1.0 (1996) - “一次性邮差”

    特点:

    • 短连接:每次请求都需要建立一次 TCP 连接,收到响应后立即断开。想象成邮差每送一封信,都要回邮局一趟再送下一封。效率极低。

    图示过程:

    客户端 (浏览器)             服务器|---- TCP连接 ---->||--- HTTP请求1 --->||<-- HTTP响应1 ----||---- 断开连接 ---->|---- TCP连接 ---->||--- HTTP请求2 --->||<-- HTTP响应2 ----||---- 断开连接 ---->

    缺点:建立 TCP 连接是昂贵的(三次握手),频繁连接断开开销巨大。

    2. HTTP/1.1 (1997) - “可复用的邮差” - 目前仍最主流

    核心改进:

    • 持久连接:默认保持 TCP 连接打开,可以在一个连接上发送多个请求和响应。用 Connection: keep-alive 头控制。邮差一次出门可以送多封信。

    • 管道化:理论上,客户端可以连续发送多个请求,而不用等待上一个响应返回。但在实践中,队头阻塞 问题严重。

    队头阻塞比喻:
    就像一个单车道收费站(服务器),前面的车交费慢了,后面的车即使准备好了钱也得等着。

    图示过程 (无管道化(默认模式)):

    客户端                      服务器|---- TCP连接 ----------->||--- 请求1 -------------->||<--------- 响应1 --------||--- 请求2 -------------->||<--------- 响应2 --------||--- 请求3 -------------->||<--------- 响应3 --------||---- 断开连接 ----------->|

    图示过程 (有管道化(Pipelining)但存在队头阻塞):

    客户端                      服务器|---- TCP连接 ----------->||--- 请求1 -------------->||--- 请求2 -------------->|  # 连续发送请求|--- 请求3 -------------->||<--------- 响应1 --------|  # 响应1必须最先返回|<--------- 响应2 --------|  # 如果响应1处理慢,2和3就被堵住了|<--------- 响应3 --------|

    HTTP/1.1 的其他问题:请求和响应头信息重复且未压缩,浪费带宽。

    3.HTTP/2 (2015) - “超级高速公路”

    HTTP/2 没有改变 HTTP 的语义(方法、状态码等),而是改变了数据格式和传输方式

    核心改进:

    • 二进制分帧:不再是纯文本格式,而是被分解为更小的二进制帧。更容易机器的解析,也更紧凑。

    • 多路复用:解决了队头阻塞问题。多个请求和响应可以在同一个连接上混杂传输,互不干扰。

    多路复用比喻:
    就像一个多车道高速公路。即使一辆车(请求1)在慢速行驶,其他车(请求2、3)也可以从旁边车道超车。

    图示过程:

    客户端                              服务器|--------- TCP连接 --------------->||---- 流1: 头帧 + 数据帧 ---------->||---- 流2: 头帧 + 数据帧 ---------->|  # 多个流混杂在一起|---- 流3: 头帧 + 数据帧 ---------->||<---- 流2: 数据帧 ----------------|  # 服务器可以优先返回准备好的流2|<---- 流1: 数据帧 ----------------||<---- 流3: 数据帧 ----------------|
    • :一个虚拟通道,承载一个完整的请求-响应过程。每个流有唯一ID。

    • :数据通信的最小单位,每个帧都标明了它属于哪个流。

    其他重要特性:

    • 头部压缩:使用 HPACK 算法压缩请求头,大大减少了冗余。

    • 服务器推送:服务器可以预测客户端需要哪些资源(如CSS、JS),在客户端请求之前就主动推送过去。

    HTTP/1.1 vs HTTP/2 性能对比

    想象一个网页,需要加载 HTML, CSS, JS, 图片1, 图片2。

    HTTP/1.1 (有并发连接数限制,如6个):

    时间线 | [HTML] [CSS] [JS] [IMG1] [IMG2] ... | 排队等待,可能有多轮
    • 虽然连接复用,但响应必须按顺序返回,容易堵塞。

    HTTP/2 (多路复用):

    时间线 | [HTML][CSS][JS][IMG1][IMG2]... | 所有资源几乎并行下载
    • 所有资源争抢同一个连接的“带宽”,谁先准备好谁就先发送,效率极高。

    现在所有的 HTTP 默认用的是哪个版本?

    现状总结:

    • HTTP/1.1 是当前绝对的主流和保底选择。

    • HTTP/2 已在绝大多数现代网站和浏览器中普及,是性能优化的首选。

    • HTTP/3 (基于QUIC协议) 正在快速崛起,是未来方向。

    协议版本使用场景与现状默认程度
    HTTP/1.0基本已被淘汰。仅在一些非常古老的客户端或嵌入式设备中可能出现。 绝非默认
    HTTP/1.1目前的绝对基准和保底协议。所有客户端和服务器都100%支持。如果无法协商更新版本,一定会降级到 HTTP/1.1 功能上的“默认”
    (当其他版本不可用时)
    HTTP/2现代网站的默认性能选择。超过一半的网站已支持。几乎所有现代浏览器都支持。需要 HTTPS 性能上的“默认”
    (对于现代网站)
    HTTP/3前沿和未来。由 Google 推动,基于 QUIC 协议(在 UDP 上而非 TCP)。能进一步解决延迟和队头阻塞问题。支持率正在快速增长(如 Cloudflare, Google 等服务已支持)。 正在成为下一代默认

    WebSocket 再深入:它是如何“升级”的?

    阶段一:HTTP 握手 (“敲门,对暗号”)

    客户端 (敲门)  --- HTTP Request --->GET /chat HTTP/1.1Host: example.comUpgrade: websocket       # “我想升级成WebSocket”Connection: UpgradeSec-WebSocket-Key: dGhl... # 暗号的一部分Sec-WebSocket-Version: 13服务器 (核对暗号) <--- HTTP Response ---HTTP/1.1 101 Switching Protocols # “好的,升级成功!”Upgrade: websocketConnection: UpgradeSec-WebSocket-Accept: s3pPL... # 暗号的另一部分

    阶段二:WebSocket 全双工通信 (“推开大门,自由交谈”)

    客户端                     服务器|------- WebSocket 数据帧 ------->| # 客户端可以随时发消息|<------ WebSocket 数据帧 --------| # 服务器也可以随时主动发消息|<------ WebSocket 数据帧 --------||------- WebSocket 数据帧 ------->|... (连接持久,双向通信) ...

    WebSocket 和Http/2

    • WebSocket 是为了提供持续的、双向的、对话式的通信通道。

    • HTTP/2 是为了高效地传输大量的 Web 资源(HTML, CSS, JS, 图片等),它优化的是请求-响应模型。

    特性WebSocketHTTP/2
    通信模型真正的、基于消息的全双工通道。连接建立后,双方平等,可随时主动发送消息。增强的请求-响应模型。通信仍由客户端请求发起。服务器推送是对请求的“预测响应”,并非真正的主动通信。
    服务器主动推送原生支持。服务器可以在任何业务逻辑需要时,主动向客户端发送消息。有限的 Server Push。服务器只能将资源推送到客户端缓存,无法触发客户端JavaScript代码执行。
    数据格式轻量级的帧结构。头部开销极小,适合高频、小数据量的消息交换。二进制分帧层。虽然也是二进制,但头部经过HPACK压缩,仍是为传输Web资源优化。
    连接与状态有状态的。连接一旦建立,会一直保持,直到一方关闭。服务器知道哪些客户端在线。无状态的。每个请求-响应在逻辑上仍是独立的(尽管在同一个TCP连接上)。
    与 HTTP 的关系借用 HTTP 进行初始握手,之后完全脱离 HTTP,使用自己的协议。是 HTTP 语义的进化。它改变了数据的传输格式,但没有改变 HTTP 的方法、状态码等核心概念。
    延迟极低。一旦连接建立,消息可以立即往返,没有协议上的开销和延迟。较低。相比 HTTP/1.1 大大降低,但由于仍是请求-响应模式,对于真正的实时应用,延迟高于 WebSocket。

    解析关键区别

    1. 通信模型的根本差异(这是最重要的区别)
    • WebSocket:像一个电话通话

      • 你拨号(HTTP 握手),对方接听(101 Switching Protocols)。

      • 之后,你们就建立了一条持续的连接,任何一方都可以随时说话,也可以同时听对方说。

      • 适用场景: 在线聊天、实时游戏、协同编辑、股票行情推送。任何需要服务器在数据产生时立即通知客户端的场景。

    • HTTP/2:像一个高效的邮递员

      • 你给他一张购物清单(请求),他可以用一辆大卡车(多路复用)把所有的货品(响应)一起运回来,效率很高。

      • 他甚至能预测你可能还需要酱油,于是把酱油也提前塞进你的包裹(Server Push)。

      • 但是,他不能主动敲门给你送一封你不知道的信。 所有送来的东西,都必须源于你最初的那张“购物清单”或基于它的预测。

      • 适用场景: 加载复杂的网页。它极大地优化了网页的加载性能

    2. 对“服务器推送”的误解澄清

    这是最关键的混淆点。

    • HTTP/2 Server Push:

      • 目的: 是为了减少延迟。当服务器收到对一个HTML页面的请求时,它可以“推送”这个页面所需的CSS和JS文件到客户端缓存,省去了客户端解析HTML后再去请求这些资源的时间。

      • 本质: 是对已知资源的缓存填充。推送到浏览器的资源,不能被你的 JavaScript 代码直接接收和处理。它只是安静地待在缓存里,等你需要时直接读取。

      • 例子: 浏览器请求 index.html,服务器连同 style.css 和 app.js 一起推送过来。

    • WebSocket 推送:

      • 目的: 是为了传递动态的、未知的业务数据

      • 本质: 是一条消息。当服务器通过 WebSocket 发送数据时,会直接触发客户端的 onmessage 事件,你的 JavaScript 代码可以立即处理它。

      • 例子: 在聊天应用中,当另一个用户发送了一条消息,服务器通过 WebSocket 立即将它推送到你的浏览器,聊天窗口实时更新。

    // WebSocket 的推送可以直接被JS代码处理
    websocket.onmessage = function(event) {var message = event.data; // 直接拿到消息内容appendMessageToChatWindow(message);
    };
    3. 如何选择?
    • 选择 HTTP/2

      • 你的主要目标是加快网站或Web应用的加载速度

      • 你不需要服务器主动向客户端发送实时业务消息。

      • 你的应用架构仍然是传统的“客户端请求,服务器响应”。

    • 选择 WebSocket

      • 你需要真正的、低延迟的双向通信

      • 你的应用功能依赖于服务器主动发起的事件(如聊天消息、实时通知、游戏状态同步、直播评论)。

    开发中(SpringBoot Vue)

    下面我们分别详细讲解。

    • HTTP请求的接收和发送
    • WebSocket消息的接收和发送

    HTTP请求的接收和发送

    SpringBoot后端

    接收HTTP请求

    1. 依赖配置

    <!-- pom.xml -->
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    2. 接收HTTP请求的Controller

    @RestController
    @RequestMapping("/api")
    public class UserController {//  接收GET请求 - 获取用户列表@GetMapping("/users")public ResponseEntity<List<User>> getUsers() {List<User> users = userService.findAll();return ResponseEntity.ok(users); // 自动构建HTTP响应}//  接收POST请求 - 创建用户@PostMapping("/users")public ResponseEntity<User> createUser(@RequestBody User user) {// @RequestBody 自动将JSON请求体转换为Java对象User savedUser = userService.save(user);return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);}//  接收带路径参数的GET请求@GetMapping("/users/{id}")public ResponseEntity<User> getUserById(@PathVariable Long id) {User user = userService.findById(id);return ResponseEntity.ok(user);}//  接收带查询参数的GET请求@GetMapping("/users/search")public ResponseEntity<List<User>> searchUsers(@RequestParam String keyword,@RequestParam(defaultValue = "0") int page) {List<User> users = userService.search(keyword, page);return ResponseEntity.ok(users);}//  接收PUT请求 - 更新用户@PutMapping("/users/{id}")public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {User updatedUser = userService.update(id, user);return ResponseEntity.ok(updatedUser);}//  接收DELETE请求@DeleteMapping("/users/{id}")public ResponseEntity<Void> deleteUser(@PathVariable Long id) {userService.delete(id);return ResponseEntity.noContent().build(); // 204 No Content}
    }
    表面现象:为什么我们不需要手动构建HTTP响应

            这是Spring MVC框架的功劳

    Spring MVC的工作流程:

    HTTP请求 -> DispatcherServlet -> @Controller/@RestController -> 返回对象 -> HttpMessageConverter -> JSON/XML -> HTTP响应

    具体发生了什么:

    写一个简单的Controller方法:

    @RestController
    public class UserController {@GetMapping("/users/{id}")public User getUser(@PathVariable Long id) {User user = userService.findById(id);return user; // 直接返回Java对象,不是HttpResponse!}
    }
    • Spring MVC在背后帮你做了这些:

      1. 自动设置状态码:默认200 OK

      2. 自动设置Content-Type头application/json

      3. 自动将Java对象序列化为JSON(响应体)

      4. 自动处理异常,返回合适的错误码

    这就是框架的价值:把重复的样板代码隐藏起来,让开发者专注于业务逻辑。

    发送HTTP请求(调用外部API)

    我们使用RestTemplate或WebClient来发送HTTP请求。

    工具时代编程模型底层实现Spring Boot起步依赖
    RestTemplateSpring 3.0+同步阻塞可配置(默认JDK HttpURLConnection)spring-boot-starter-web
    WebClientSpring 5.0+异步非阻塞 (Reactive)Reactor Nettyspring-boot-starter-webflux

    RestTemplate

    • 默认:JDK的HttpURLConnection

    • 可替换为:Apache HttpClient、OkHttp等

    @Bean
    public RestTemplate restTemplate() {return new RestTemplate(new HttpComponentsClientHttpRequestFactory()); // 使用Apache HttpClient
    }

    WebClient

    • 基于Reactor和Netty,使用事件驱动、非阻塞IO模型。
    // 简化的执行流程
    public Mono<ClientResponse> exchange() {return Mono.defer(() -> {// 1. 创建HttpClientRequestHttpClientRequest request = httpClient.request(httpMethod);// 2. 设置请求头、体等// 3. 发送请求并返回Mono<ClientResponse>return request.response();});
    }

      1.使用RestTemplate

      1.依赖配置

      起步依赖spring-boot-starter-web (这是最常用的Web开发starter)

      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      spring-boot-starter-web
      ├── spring-webmvc          # Spring MVC核心
      ├── spring-web             # Spring Web核心
      ├── jackson-databind       # JSON序列化
      └── tomcat-embed-core      # 内嵌Tomcat

      2.RestTemplate在Spring Boot中的使用方式:

      方式1:手动配置Bean(推荐,可定制化)

      @Configuration
      public class RestTemplateConfig {@Beanpublic RestTemplate restTemplate() {RestTemplate restTemplate = new RestTemplate();// 可选的定制配置restTemplate.setErrorHandler(new MyErrorHandler());// 设置超时时间等...return restTemplate;}
      }

      然后,在需要的地方注入并使用。

      @Service
      public class UserService {@Autowiredprivate RestTemplate restTemplate;// GET请求 - 获取用户信息public User getUserFromExternalAPI(Long userId) {String url = "https://jsonplaceholder.typicode.com/users/{id}";// 方法1:getForObject - 直接返回对象User user = restTemplate.getForObject(url, User.class, userId);// 方法2:getForEntity - 返回包含响应头的完整响应ResponseEntity<User> response = restTemplate.getForEntity(url, User.class, userId);if (response.getStatusCode() == HttpStatus.OK) {return response.getBody();}return null;}// POST请求 - 创建用户public User createUser(User user) {String url = "https://jsonplaceholder.typicode.com/users";// 方法1:postForObjectUser createdUser = restTemplate.postForObject(url, user, User.class);// 方法2:exchange - 最灵活的方法HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);headers.set("Authorization", "Bearer token123");HttpEntity<User> request = new HttpEntity<>(user, headers);ResponseEntity<User> response = restTemplate.exchange(url, HttpMethod.POST, request, User.class);return response.getBody();}// PUT、DELETE请求public void updateUser(User user) {String url = "https://jsonplaceholder.typicode.com/users/{id}";restTemplate.put(url, user, user.getId());}public void deleteUser(Long userId) {String url = "https://jsonplaceholder.typicode.com/users/{id}";restTemplate.delete(url, userId);}
      }

      方式2:直接使用(不推荐)

      // 可以直接new,但失去了Spring管理的优势
      RestTemplate restTemplate = new RestTemplate();
      String result = restTemplate.getForObject("http://example.com", String.class);
      2.WebClient 详细讲解

      1.配置依赖。起步依赖:spring-boot-starter-webflux

      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId>
      </dependency>
      spring-boot-starter-webflux
      ├── spring-webflux         # Reactive Web核心
      ├── reactor-core           # Reactor响应式编程
      └── netty                  # 底层网络库

      2.配置Bean:

      @Configuration
      public class WebClientConfig {@Beanpublic WebClient webClient() {return WebClient.builder().baseUrl("https://jsonplaceholder.typicode.com").defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).build();}
      }

      3.然后,在需要的地方注入并使用。

      @Service
      public class ReactiveUserService {@Autowiredprivate WebClient webClient;// 异步获取用户public Mono<User> getUserAsync(Long userId) {return webClient.get().uri("/users/{id}", userId).retrieve().bodyToMono(User.class);}// 带错误处理的请求public Mono<User> getUserWithErrorHandling(Long userId) {return webClient.get().uri("/users/{id}", userId).retrieve().onStatus(HttpStatus::is4xxClientError, response -> Mono.error(new RuntimeException("Client error: " + response.statusCode()))).onStatus(HttpStatus::is5xxServerError, response -> Mono.error(new RuntimeException("Server error: " + response.statusCode()))).bodyToMono(User.class);}// POST请求public Mono<User> createUser(User user) {return webClient.post().uri("/users").bodyValue(user).retrieve().bodyToMono(User.class);}// 同步调用(在需要阻塞的地方)public User getUserSync(Long userId) {return webClient.get().uri("/users/{id}", userId).retrieve().bodyToMono(User.class).block(); // 阻塞直到得到结果}
      }

      RestTemplate/WebClient主要就是为了在分布式系统中调用其他服务!

      Vue前端

      发送 + 接收HTTP请求

      1. 安装依赖

      npm install axios

      2. HTTP服务封装

      // src/services/api.js
      import axios from 'axios';// 创建axios实例
      const apiClient = axios.create({baseURL: 'http://localhost:8080/api', // Spring Boot后端地址timeout: 10000,headers: {'Content-Type': 'application/json'}
      });export default {//  发送GET请求 + 接收响应getUsers() {return apiClient.get('/users').then(response => {// 接收后端响应数据return response.data;}).catch(error => {console.error('获取用户列表失败:', error);throw error;});},//  发送POST请求 + 接收响应createUser(userData) {return apiClient.post('/users', userData).then(response => {// 接收创建成功的用户数据return response.data;});},//  发送PUT请求 + 接收响应updateUser(id, userData) {return apiClient.put(`/users/${id}`, userData).then(response => response.data);},//  发送DELETE请求deleteUser(id) {return apiClient.delete(`/users/${id}`);}
      }

      3. Vue组件中使用

      <template><div><!-- 发送请求的UI --><button @click="loadUsers">加载用户</button><button @click="createUser">创建用户</button><!-- 接收并显示响应数据 --><div v-if="loading">加载中...</div><ul v-else><li v-for="user in users" :key="user.id">{{ user.name }} - {{ user.email }}<button @click="deleteUser(user.id)">删除</button></li></ul></div>
      </template><script>
      import apiService from '@/services/api.js';export default {data() {return {users: [],      //  接收到的数据loading: false //  接收加载状态}},methods: {//  发送GET请求并接收响应async loadUsers() {this.loading = true;try {// 发送请求 + 接收响应this.users = await apiService.getUsers();} catch (error) {console.error('加载失败:', error);} finally {this.loading = false;}},//  发送POST请求并接收响应async createUser() {const newUser = {name: '张三',email: 'zhangsan@example.com'};try {// 发送请求 + 接收响应const createdUser = await apiService.createUser(newUser);console.log('创建成功:', createdUser);this.loadUsers(); // 重新加载列表} catch (error) {console.error('创建失败:', error);}},//  发送DELETE请求async deleteUser(userId) {try {await apiService.deleteUser(userId);this.loadUsers(); // 重新加载} catch (error) {console.error('删除失败:', error);}}},mounted() {this.loadUsers(); // 组件加载时自动获取数据}
      }
      </script>

      WebSocket完整通信

      后端

      基本步骤:

      • 添加WebSocket依赖。
      • 配置WebSocket,比如注册WebSocket处理器或使用@ServerEndpoint注解。
      • 编写WebSocket处理器,处理连接、消息、关闭和错误。

      1.在pom.xml中添加WebSocket依赖。

      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
      </dependency>

      两种方式配置WebSocket:一种是实现WebSocketConfigurer,另一种是使用@ServerEndpoint注解。

      方式一:

      1. 创建一个配置类启用WebSocket,并注册WebSocket端点。

      @Configuration
      @EnableWebSocket
      public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(new MyWebSocketHandler(), "/ws").setAllowedOrigins("*"); // 允许跨域}
      }

      2.实现WebSocket处理器,实现WebSocketHandler接口来处理WebSocket连接和消息。

      @Component
      public class MyWebSocketHandler extends TextWebSocketHandler {private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();//  接收连接建立@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {sessions.add(session);System.out.println("客户端连接: " + session.getId());//  主动发送欢迎消息session.sendMessage(new TextMessage("欢迎连接WebSocket!"));}//  接收客户端消息@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {String clientMessage = message.getPayload();System.out.println("收到消息: " + clientMessage + " from " + session.getId());//  发送响应消息给这个客户端String response = "服务器回应: " + clientMessage;session.sendMessage(new TextMessage(response));//  广播消息给所有客户端broadcast("用户 " + session.getId() + " 说: " + clientMessage);}//  接收连接关闭@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {sessions.remove(session);System.out.println("客户端断开: " + session.getId());}//  主动发送消息给所有客户端(服务器推送)public void broadcast(String message) {for (WebSocketSession session : sessions) {try {if (session.isOpen()) {session.sendMessage(new TextMessage(message));}} catch (IOException e) {e.printStackTrace();}}}//  主动发送消息给特定客户端public void sendToUser(String sessionId, String message) {for (WebSocketSession session : sessions) {if (session.getId().equals(sessionId) && session.isOpen()) {try {session.sendMessage(new TextMessage(message));} catch (IOException e) {e.printStackTrace();}}}}
      }

      方式二:

      1.配置ServerEndpointExporter

      @Configuration
      public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
      }

      2.然后,使用@ServerEndpoint注解定义一个端点。

      @Component
      @ServerEndpoint("/my-websocket")
      public class MyWebSocketEndpoint {@OnOpenpublic void onOpen(Session session) {System.out.println("Connected: " + session.getId());}@OnMessagepublic void onMessage(String message, Session session) {System.out.println("Received: " + message);try {// 发送回复session.getBasicRemote().sendText("Echo: " + message);} catch (IOException e) {e.printStackTrace();}}@OnClosepublic void onClose(Session session) {System.out.println("Closed: " + session.getId());}@OnErrorpublic void onError(Session session, Throwable error) {error.printStackTrace();}
      }

      前端

      在Vue组件中,我们使用JavaScript原生的WebSocket API。

      基本步骤:

      • 在组件创建时(mounted)建立WebSocket连接。
      • 监听WebSocket的open、message、close、error事件。
      • 在方法中发送消息。
      • 在组件销毁前(beforeUnmount)关闭WebSocket连接。
      <template><div><h3>WebSocket聊天室</h3><!-- 显示接收到的消息 --><div class="message-container"><div v-for="(msg, index) in messages" :key="index" class="message">{{ msg }}</div></div><!-- 发送消息的输入框 --><div class="input-area"><input v-model="inputMessage" @keyup.enter="sendMessage" placeholder="输入消息..."><button @click="sendMessage">发送</button><button @click="connectWebSocket">连接</button><button @click="disconnectWebSocket">断开</button></div><div>连接状态: {{ connectionStatus }}</div></div>
      </template><script>
      export default {data() {return {websocket: null,messages: [],           //  接收到的消息列表inputMessage: '',       //  要发送的消息connectionStatus: '未连接'}},methods: {//  建立WebSocket连接connectWebSocket() {// 创建WebSocket连接this.websocket = new WebSocket('ws://localhost:8080/ws');//  接收连接建立事件this.websocket.onopen = (event) => {this.connectionStatus = '已连接';this.messages.push('系统: 连接服务器成功');console.log('WebSocket连接已建立');};//  接收服务器消息this.websocket.onmessage = (event) => {// 接收到服务器发送的消息this.messages.push('服务器: ' + event.data);console.log('收到消息:', event.data);};//  接收连接关闭事件this.websocket.onclose = (event) => {this.connectionStatus = '已断开';this.messages.push('系统: 连接已断开');console.log('WebSocket连接已关闭');};//  接收错误事件this.websocket.onerror = (error) => {this.connectionStatus = '连接错误';this.messages.push('系统: 连接发生错误');console.error('WebSocket错误:', error);};},//  发送消息到服务器sendMessage() {if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {this.websocket.send(this.inputMessage);this.messages.push('我: ' + this.inputMessage);this.inputMessage = ''; // 清空输入框} else {alert('WebSocket未连接');}},//  断开连接disconnectWebSocket() {if (this.websocket) {this.websocket.close();}}},mounted() {// 组件加载时自动连接this.connectWebSocket();},beforeUnmount() {// 组件销毁前断开连接this.disconnectWebSocket();}
      }
      </script><style scoped>
      .message-container {height: 300px;border: 1px solid #ccc;overflow-y: auto;margin-bottom: 10px;padding: 10px;
      }
      .message {margin: 5px 0;padding: 5px;background: #f5f5f5;border-radius: 4px;
      }
      .input-area {display: flex;gap: 10px;
      }
      </style>

      重新讲解WebSocket

      用一个超形象的比喻理解WebSocket

      传统HTTP vs WebSocket

      HTTP = 打电话

      • 你拨号 → 对方接听 → 你说一句 → 对方回一句 → 挂断

      • 每次要说新的话,都要重新拨号、接听、挂断

      WebSocket = 微信视频通话

      • 拨通一次 → 保持连接 → 双方随时都能说话 → 还能同时说话 → 直到有人挂断

      WebSocket 实际开发详解(用WebSocketHandler)

      第一步:Spring Boot后端设置

      1. 添加依赖
      <!-- pom.xml -->
      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
      </dependency>
      2. 创建WebSocket配置(就像安装电话线路)
      @Configuration
      @EnableWebSocket
      public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {// 注册WebSocket处理器,"/ws"就是客户端的连接地址registry.addHandler(new MyWebSocketHandler(), "/ws").setAllowedOrigins("*"); // 允许所有来源访问}
      }
      3. 创建WebSocket处理器(就像接线员)
      @Component
      public class MyWebSocketHandler extends TextWebSocketHandler {// 保存所有在线的客户端连接private static final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();// 📞 当有客户端连接时(有人打来电话)@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {sessions.add(session);System.out.println("新客户端连接: " + session.getId());// 🎤 主动向这个客户端发送欢迎消息session.sendMessage(new TextMessage("欢迎连接!你是第" + sessions.size() +个用户"));// 📢 向所有客户端广播有新用户加入broadcast("系统消息: 有新用户加入聊天室");}// 💬 当收到客户端发来的消息时(对方说话了)@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {String clientMessage = message.getPayload(); // 获取客户端发来的内容String clientId = session.getId();System.out.println("收到来自 " + clientId + " 的消息: " + clientMessage);// 🎤 给这个客户端单独回复String personalReply = "你说: " + clientMessage;session.sendMessage(new TextMessage(personalReply));// 📢 向所有其他客户端广播这条消息(群聊效果)String broadcastMsg = "用户" + clientId.substring(0, 6) + "说: " + clientMessage;broadcastToOthers(session, broadcastMsg);}// 📞 当客户端断开连接时(对方挂电话了)@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {sessions.remove(session);System.out.println("客户端断开: " + session.getId());// 📢 通知其他用户有人离开broadcast("系统消息: 有用户离开了聊天室");}// 📢 广播消息给所有客户端private void broadcast(String message) {for (WebSocketSession session : sessions) {try {if (session.isOpen()) {session.sendMessage(new TextMessage(message));}} catch (IOException e) {e.printStackTrace();}}}// 📢 广播给除指定session外的所有客户端private void broadcastToOthers(WebSocketSession excludeSession, String message) {for (WebSocketSession session : sessions) {if (!session.getId().equals(excludeSession.getId()) && session.isOpen()) {try {session.sendMessage(new TextMessage(message));} catch (IOException e) {e.printStackTrace();}}}}// 🎯 主动给特定用户发送消息(私聊)public void sendToUser(String targetSessionId, String message) {for (WebSocketSession session : sessions) {if (session.getId().equals(targetSessionId) && session.isOpen()) {try {session.sendMessage(new TextMessage("私信: " + message));} catch (IOException e) {e.printStackTrace();}break;}}}
      }

      第二步:Vue前端使用WebSocket

      1. 创建WebSocket聊天组件
      <template><div class="websocket-demo"><h2>💬 实时聊天室 (WebSocket)</h2><!-- 连接状态显示 --><div class="status" :class="connectionStatus">状态: {{ statusText }}</div><!-- 连接控制按钮 --><div class="controls"><button @click="connect" :disabled="isConnected">📞 连接</button><button @click="disconnect" :disabled="!isConnected">❌ 断开</button></div><!-- 消息显示区域 --><div class="messages"><div v-for="(msg, index) in messages" :key="index" class="message":class="{ 'my-message': msg.isMyMessage }">{{ msg.content }}</div></div><!-- 消息发送区域 --><div class="send-area"><input v-model="inputMessage" @keyup.enter="sendMessage"placeholder="输入消息...":disabled="!isConnected"><button @click="sendMessage" :disabled="!isConnected">📤 发送</button></div><!-- 在线用户列表 --><div class="online-users" v-if="onlineUsers.length > 0"><h4>👥 在线用户</h4><div v-for="user in onlineUsers" :key="user" class="user">{{ user }}</div></div></div>
      </template><script>
      export default {name: 'WebSocketChat',data() {return {websocket: null,           // WebSocket连接实例messages: [],              // 存储所有消息inputMessage: '',          // 输入框的消息connectionStatus: 'disconnected', // 连接状态onlineUsers: [],           // 在线用户列表myUserId: ''               // 我的用户ID}},computed: {// 计算属性:是否已连接isConnected() {return this.websocket && this.websocket.readyState === WebSocket.OPEN;},// 计算属性:状态显示文本statusText() {switch (this.connectionStatus) {case 'connected': return '🟢 已连接';case 'connecting': return '🟡 连接中...';case 'disconnected': return '🔴 未连接';case 'error': return '🔴 连接错误';default: return '未知状态';}}},methods: {// 📞 建立WebSocket连接connect() {this.connectionStatus = 'connecting';// 创建WebSocket连接,连接到后端的 /ws 端点this.websocket = new WebSocket('ws://localhost:8080/ws');// 🎧 监听连接成功事件this.websocket.onopen = (event) => {console.log('✅ WebSocket连接成功');this.connectionStatus = 'connected';this.addSystemMessage('连接服务器成功!');};// 🎧 监听收到消息事件(最重要的部分!)this.websocket.onmessage = (event) => {console.log('📨 收到服务器消息:', event.data);// 处理接收到的消息this.handleReceivedMessage(event.data);};// 🎧 监听连接关闭事件this.websocket.onclose = (event) => {console.log('❌ WebSocket连接关闭');this.connectionStatus = 'disconnected';this.addSystemMessage('连接已断开');};// 🎧 监听连接错误事件this.websocket.onerror = (error) => {console.error('💥 WebSocket错误:', error);this.connectionStatus = 'error';this.addSystemMessage('连接发生错误');};},// ❌ 断开WebSocket连接disconnect() {if (this.websocket) {this.websocket.close();}},// 📤 发送消息到服务器sendMessage() {if (this.inputMessage.trim() && this.isConnected) {// 发送消息this.websocket.send(this.inputMessage);// 在消息列表中显示自己发送的消息this.messages.push({content: `我: ${this.inputMessage}`,isMyMessage: true});// 清空输入框this.inputMessage = '';// 滚动到底部this.$nextTick(() => {this.scrollToBottom();});}},// 🎯 处理接收到的消息handleReceivedMessage(message) {// 根据消息内容进行不同的处理if (message.includes('系统消息')) {this.addSystemMessage(message);} else if (message.includes('欢迎连接')) {this.addSystemMessage(message);} else if (message.includes('你说:')) {// 服务器对个人消息的确认this.messages.push({content: `系统: ${message}`,isMyMessage: false});} else {// 其他用户的消息this.messages.push({content: message,isMyMessage: false});}// 滚动到底部this.$nextTick(() => {this.scrollToBottom();});},// 📝 添加系统消息addSystemMessage(message) {this.messages.push({content: `💡 ${message}`,isMyMessage: false});},// 📜 滚动到底部scrollToBottom() {const messagesContainer = this.$el.querySelector('.messages');if (messagesContainer) {messagesContainer.scrollTop = messagesContainer.scrollHeight;}}},// 组件挂载时自动连接mounted() {this.connect();},// 组件销毁前断开连接beforeUnmount() {this.disconnect();}
      }
      </script><style scoped>
      .websocket-demo {max-width: 600px;margin: 0 auto;padding: 20px;font-family: Arial, sans-serif;
      }.status {padding: 10px;border-radius: 5px;margin-bottom: 10px;text-align: center;font-weight: bold;
      }.status.connected {background-color: #d4edda;color: #155724;
      }.status.connecting {background-color: #fff3cd;color: #856404;
      }.status.disconnected, .status.error {background-color: #f8d7da;color: #721c24;
      }.controls, .send-area {display: flex;gap: 10px;margin-bottom: 15px;
      }.controls button, .send-area button {padding: 8px 16px;border: none;border-radius: 4px;cursor: pointer;
      }.controls button:disabled, .send-area button:disabled {opacity: 0.5;cursor: not-allowed;
      }.messages {height: 300px;border: 1px solid #ddd;border-radius: 5px;padding: 10px;overflow-y: auto;margin-bottom: 15px;background-color: #f9f9f9;
      }.message {margin: 8px 0;padding: 8px 12px;border-radius: 15px;max-width: 80%;word-wrap: break-word;
      }.message:not(.my-message) {background-color: #e3f2fd;align-self: flex-start;
      }.message.my-message {background-color: #c8e6c9;margin-left: auto;text-align: right;
      }.send-area input {flex: 1;padding: 8px 12px;border: 1px solid #ddd;border-radius: 4px;
      }.online-users {margin-top: 20px;padding: 10px;border: 1px solid #ddd;border-radius: 5px;background-color: #f0f8ff;
      }.user {padding: 5px;border-bottom: 1px solid #e0e0e0;
      }
      </style>

      四个关键事件:

      1. onopen - 连接建立时(电话接通)

      2. onmessage - 收到消息时(听到对方说话)

      3. onclose - 连接关闭时(对方挂断)

      4. onerror - 连接错误时(通话故障)

      核心方法:

      • WebSocket.send() - 发送消息(说话)

      • WebSocket.close() - 关闭连接(挂断)

      连接状态:

      • WebSocket.CONNECTING (0) - 连接中

      • WebSocket.OPEN (1) - 已连接

      • WebSocket.CLOSING (2) - 关闭中

      • WebSocket.CLOSED (3) - 已关闭

      WebSocket 实际开发详解(用@ServerEndpoint)

      方式优点缺点
      WebSocketHandler更Spring风格,依赖注入方便代码相对繁琐
      @ServerEndpoint代码简洁,标准JSR-356依赖注入需要特殊处理

      第一步:Spring Boot后端配置

      1. 添加依赖(与之前相同)
      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
      </dependency>
      2. 关键配置:启用ServerEndpoint
      @Configuration
      public class WebSocketConfig {/*** 这个Bean是必须的!* 它会自动扫描带有@ServerEndpoint注解的类并注册为WebSocket端点*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
      }
      3. 使用@ServerEndpoint创建WebSocket端点
      @Component
      @ServerEndpoint("/ws/chat")  // 🔑 定义WebSocket连接路径
      public class ChatWebSocketEndpoint {// 保存所有连接的会话private static final Map<String, Session> sessions = new ConcurrentHashMap<>();// 🔧 解决@ServerEndpoint中@Autowired为null的问题private static SomeService someService;@Autowiredpublic void setSomeService(SomeService someService) {ChatWebSocketEndpoint.someService = someService;}// 📞 连接建立时调用@OnOpenpublic void onOpen(Session session) {String sessionId = session.getId();sessions.put(sessionId, session);System.out.println("🔗 新连接: " + sessionId + ", 当前在线: " + sessions.size());// 🎤 发送欢迎消息sendMessage(session, "欢迎加入聊天室!你的ID: " + sessionId);// 📢 广播用户上线通知broadcast("系统: 用户 " + sessionId.substring(0, 6) + " 加入聊天室", sessionId);}// 💬 收到客户端消息时调用@OnMessagepublic void onMessage(String message, Session session) {String sessionId = session.getId();System.out.println("📨 收到消息: " + message + " from " + sessionId);// 🎯 处理不同类型的消息if (message.startsWith("/name ")) {// 设置昵称handleSetName(message, session);} else {// 普通聊天消息String userNickname = getUserNickname(session);String broadcastMsg = userNickname + ": " + message;// 📢 广播消息给所有人(除了发送者)broadcast(broadcastMsg, sessionId);// 🎤 给发送者回执sendMessage(session, "我: " + message);}}// 📞 连接关闭时调用@OnClosepublic void onClose(Session session) {String sessionId = session.getId();sessions.remove(sessionId);System.out.println("❌ 连接关闭: " + sessionId + ", 剩余在线: " + sessions.size());// 📢 广播用户离开通知broadcast("系统: 用户 " + sessionId.substring(0, 6) + " 离开聊天室", sessionId);}// ⚠️ 发生错误时调用@OnErrorpublic void onError(Session session, Throwable error) {String sessionId = session.getId();System.err.println("💥 WebSocket错误 [" + sessionId + "]: " + error.getMessage());error.printStackTrace();}// 🎯 处理设置昵称private void handleSetName(String message, Session session) {try {String nickname = message.substring(6).trim(); // 提取昵称session.getUserProperties().put("nickname", nickname);sendMessage(session, "系统: 昵称已设置为: " + nickname);} catch (Exception e) {sendMessage(session, "系统: 设置昵称失败");}}// 🎯 获取用户昵称private String getUserNickname(Session session) {String nickname = (String) session.getUserProperties().get("nickname");return nickname != null ? nickname : session.getId().substring(0, 6);}// 📤 发送消息给单个客户端private void sendMessage(Session session, String message) {try {if (session.isOpen()) {session.getBasicRemote().sendText(message);}} catch (IOException e) {System.err.println("发送消息失败: " + e.getMessage());}}// 📢 广播消息给所有客户端(可排除指定会话)private void broadcast(String message, String excludeSessionId) {sessions.forEach((sessionId, session) -> {if (!sessionId.equals(excludeSessionId) && session.isOpen()) {try {session.getBasicRemote().sendText(message);} catch (IOException e) {System.err.println("广播消息失败: " + e.getMessage());}}});}// 🎯 主动发送消息给特定用户(可从其他业务类调用)public static void sendToUser(String targetSessionId, String message) {Session session = sessions.get(targetSessionId);if (session != null && session.isOpen()) {try {session.getBasicRemote().sendText("私信: " + message);} catch (IOException e) {System.err.println("发送私信失败: " + e.getMessage());}}}// 📊 获取在线用户数public static int getOnlineCount() {return sessions.size();}// 👥 获取所有在线会话IDpublic static Set<String> getOnlineUsers() {return sessions.keySet();}
      }
      4. 业务服务示例(展示依赖注入)
      @Service
      public class SomeService {public String processMessage(String message) {// 这里可以处理业务逻辑,比如保存到数据库、调用其他服务等return "处理后的消息: " + message.toUpperCase();}// 主动推送消息到WebSocketpublic void pushNotification(String message) {// 可以在这里调用WebSocket的静态方法进行推送ChatWebSocketEndpoint.broadcastToAll("通知: " + message);}
      }

      第二步:Vue前端使用(与之前类似,但连接地址不同)

      <template><div class="chat-room"><h2>💬 聊天室 (@ServerEndpoint方式)</h2><div class="connection-info"><span>状态: {{ connectionStatus }}</span><span>在线用户: {{ onlineCount }}</span></div><!-- 消息显示区域 --><div class="messages-container"><div v-for="(msg, index) in messages" :key="index" class="message":class="{'system-message': msg.type === 'system','my-message': msg.type === 'my','other-message': msg.type === 'other'}">{{ msg.content }}</div></div><!-- 消息发送区域 --><div class="input-area"><input v-model="inputMessage" @keyup.enter="sendMessage"placeholder="输入消息... 输入 /name 昵称 来设置昵称":disabled="!isConnected"><button @click="sendMessage" :disabled="!isConnected">发送</button><button @click="setNickname">设置昵称</button></div><!-- 在线用户列表 --><div class="online-users"><h4>👥 在线用户 ({{ onlineUsers.length }})</h4><div v-for="user in onlineUsers" :key="user" class="user-item">{{ user }}</div></div></div>
      </template><script>
      export default {name: 'ChatRoom',data() {return {websocket: null,messages: [],inputMessage: '',connectionStatus: 'disconnected',onlineUsers: [],onlineCount: 0,myNickname: '用户' + Math.random().toString(36).substr(2, 5)}},computed: {isConnected() {return this.websocket && this.websocket.readyState === WebSocket.OPEN;}},methods: {// 连接WebSocketconnect() {this.connectionStatus = 'connecting';// 🔑 注意:连接地址与@ServerEndpoint注解的路径一致this.websocket = new WebSocket('ws://localhost:8080/ws/chat');this.websocket.onopen = () => {this.connectionStatus = 'connected';this.addSystemMessage('连接成功!');// 连接成功后设置昵称this.setNickname();};this.websocket.onmessage = (event) => {this.handleReceivedMessage(event.data);};this.websocket.onclose = () => {this.connectionStatus = 'disconnected';this.addSystemMessage('连接已断开');};this.websocket.onerror = (error) => {this.connectionStatus = 'error';this.addSystemMessage('连接错误: ' + error);};},// 处理接收到的消息handleReceivedMessage(message) {if (message.includes('系统:') || message.includes('欢迎') || message.includes('昵称')) {this.messages.push({content: message,type: 'system'});} else if (message.startsWith('我:')) {this.messages.push({content: message,type: 'my'});} else {this.messages.push({content: message,type: 'other'});// 简单的在线用户数统计(根据消息内容判断)if (message.includes('加入聊天室')) {this.onlineCount++;} else if (message.includes('离开聊天室')) {this.onlineCount = Math.max(0, this.onlineCount - 1);}}this.scrollToBottom();},// 发送消息sendMessage() {if (this.inputMessage.trim() && this.isConnected) {this.websocket.send(this.inputMessage);this.inputMessage = '';}},// 设置昵称setNickname() {if (this.isConnected) {this.websocket.send('/name ' + this.myNickname);}},// 添加系统消息addSystemMessage(message) {this.messages.push({content: message,type: 'system'});},// 滚动到底部scrollToBottom() {this.$nextTick(() => {const container = this.$el.querySelector('.messages-container');if (container) {container.scrollTop = container.scrollHeight;}});},// 断开连接disconnect() {if (this.websocket) {this.websocket.close();}}},mounted() {this.connect();},beforeUnmount() {this.disconnect();}
      }
      </script><style scoped>
      .chat-room {max-width: 800px;margin: 0 auto;padding: 20px;display: grid;grid-template-columns: 1fr 200px;grid-gap: 20px;
      }.connection-info {grid-column: 1 / -1;display: flex;justify-content: space-between;padding: 10px;background: #f5f5f5;border-radius: 5px;
      }.messages-container {grid-column: 1;height: 400px;border: 1px solid #ddd;border-radius: 8px;padding: 15px;overflow-y: auto;background: #fafafa;
      }.message {margin: 10px 0;padding: 8px 12px;border-radius: 8px;word-wrap: break-word;
      }.system-message {background: #fff3cd;color: #856404;text-align: center;font-style: italic;
      }.my-message {background: #d1ecf1;margin-left: 20%;text-align: right;
      }.other-message {background: #e2e3e5;margin-right: 20%;
      }.input-area {grid-column: 1;display: flex;gap: 10px;
      }.input-area input {flex: 1;padding: 10px;border: 1px solid #ddd;border-radius: 4px;
      }.input-area button {padding: 10px 15px;border: none;border-radius: 4px;background: #007bff;color: white;cursor: pointer;
      }.input-area button:disabled {background: #6c757d;cursor: not-allowed;
      }.online-users {grid-column: 2;border: 1px solid #ddd;border-radius: 8px;padding: 15px;background: white;
      }.online-users h4 {margin: 0 0 10px 0;color: #333;
      }.user-item {padding: 5px 0;border-bottom: 1px solid #f0f0f0;
      }
      </style>

      @ServerEndpoint 核心注解说明

      四个核心注解:

      @OnOpen - 连接建立时

      @OnOpen
      public void onOpen(Session session) {// session: 代表一个WebSocket连接
      }

      @OnMessage - 收到消息时

      @OnMessage
      public void onMessage(String message, Session session) {// message: 客户端发送的消息内容
      }

      @OnClose - 连接关闭时

      @OnClose
      public void onClose(Session session) {// 连接关闭清理工作
      }

      @OnError - 发生错误时

      @OnError
      public void onError(Session session, Throwable error) {// 错误处理
      }
      重要特性:

      1. Session对象

      • 每个连接都有一个唯一的Session
      • 可以存储用户属性:session.getUserProperties().put("key", value)
      • 可以发送消息:session.getBasicRemote().sendText(message)

      2. 依赖注入问题解决
      由于@ServerEndpoint实例由WebSocket容器创建,不是Spring管理的,所以需要特殊处理:

      @Component
      @ServerEndpoint("/ws/chat")
      public class MyEndpoint {private static SomeService someService;@Autowiredpublic void setSomeService(SomeService someService) {MyEndpoint.someService = someService; // 静态变量保存}
      }

      3. 路径参数支持

      @ServerEndpoint("/ws/chat/{roomId}/{userId}")
      public class ChatEndpoint {@OnOpenpublic void onOpen(Session session, @PathParam("roomId") String roomId,@PathParam("userId") String userId) {// 可以获取路径参数System.out.println("房间: " + roomId + ", 用户: " + userId);}
      }

      两种方式对比总结

      特性WebSocketHandler@ServerEndpoint
      代码风格Spring风格JSR-356标准风格
      依赖注入直接使用@Autowired需要静态变量中转
      配置方式实现接口+注册注解+ServerEndpointExporter
      Session管理手动管理集合手动管理集合
      适用场景复杂业务逻辑简单实时通信

      选择建议:

      • 新手/简单项目:推荐@ServerEndpoint,代码更简洁

      • 复杂业务/需要深度Spring集成:推荐WebSocketHandler

      http://www.dtcms.com/a/527941.html

      相关文章:

    • 动态规划详细题解——力扣198.打家劫舍
    • 【LeetCode热题100(52/100)】课程表
    • 什么行业必须做网站棋牌软件开发一个多少钱
    • LeetCode:698. 划分为k个相等的子集
    • 【LeetCode100】--- 101.重排链表【思维导图+复习回顾】
    • 【C++:继承】C++面向对象继承全面解析:派生类构造、多继承、菱形虚拟继承与设计模式实践
    • LeetCode 面试经典 150_链表_反转链表 II(60_92_C++_中等)(头插法)
    • 第 08 天:编辑器和终端快捷键 (nano, vi/vim)
    • 读取文件夹内的pdf装换成npg给vlm分类人工确认然后填入excel vlmapi速度挺快 qwen3-vl-plus webbrowser.open
    • 主流 AI IDE 之一的 CodeBuddy IDE 介绍
    • 展示型网站制作公司做网站好的网站建设公司哪家好
    • MySQL 大表查询优化、超大分页处理、SQL 慢查询优化、主键选择
    • Unity公共Mono模块:非继承脚本也能更新
    • 使用 Flownex 对发电厂周期进行建模
    • 前端基础:JS基础语法
    • 中山网站建设的企业深圳哪些公司做网站
    • 网站 验收访问的网页正在升级中
    • 29-机器学习与大模型开发数学教程-3-3 张量的运算(Einstein求和约定)
    • 天猫网站建设的优势有哪些室内装修3d动态演示效果图
    • 如何提升网站的搜索排名专业外贸网站建设公司价格
    • 【GESP】C++四级真题 luogu-B4361 [GESP202506 四级] 排序
    • 出版社类网站模板手机商城网站源码
    • 旅游网站建设实施方案湖南建筑工程集团
    • 投资融资理财网站模板如何网站专题策划
    • 淄博网站制作优化安装百度到手机桌面
    • 江苏和住房建设厅网站东莞樟木头网站建设公司
    • 内蒙古工程建设招投标中心网站网站编辑适不适合男生做
    • 网站开发的经费预算跨境电商是干嘛的
    • 高端网站定制设计公司果蔬网站规划建设方案
    • 网站开发怎么接入支付宝app管理系统