1、如果想在 Controller
中将实时调用第三方模型的流式数据逐行返回给前端,可以使用 Spring MVC 的 ResponseBodyEmitter
。在这种情况下,需要确保 Controller
方法是异步的,并使用一个新线程或者异步任务来获取第三方模型的实时数据,并将数据逐行发送给客户端。
import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.HttpURLConnection;import java.net.URL;@RestController@RequestMapping("/api")public class YourController { @PostMapping("/streamData") public ResponseBodyEmitter streamData() { ResponseBodyEmitter emitter = new ResponseBodyEmitter(); // 启动一个新的线程来处理数据流 new Thread(() -> { String apiUrl = "your_api_url_here"; try { URL url = new URL(apiUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); // 设置请求方法为POST connection.setRequestMethod("POST"); connection.setDoOutput(true); // 获取输入流,这里是获取返回的数据流 InputStream inputStream = connection.getInputStream(); // 处理输入流,这里可以根据需要进行流式处理 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = reader.readLine()) != null) { // 发送每行数据给客户端 try { emitter.send(line, MediaType.TEXT_PLAIN); } catch (IOException e) { e.printStackTrace(); // 处理发送异常,比如客户端断开连接 break; } } // 关闭资源 reader.close(); // 获取响应码,根据需要处理 int responseCode = connection.getResponseCode(); System.out.println("Response Code: " + responseCode); // 断开连接 connection.disconnect(); // 发送完成信号 try { emitter.complete(); } catch (IOException e) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); // 发送错误信号 try { emitter.completeWithError(e); } catch (IOException ex) { ex.printStackTrace(); } } }).start(); return emitter; }}
在这个例子中,streamData
方法返回了 ResponseBodyEmitter
对象,并在一个新的线程中启动了获取第三方模型实时数据的任务。每获取到一行数据,就使用 emitter.send(line, MediaType.TEXT_PLAIN)
将数据逐行发送给客户端。
2、执行逻辑
当使用 ResponseBodyEmitter
时,你实际上创建了一个可以异步写入响应体的对象。在这个具体的例子中,当请求到达 /api/streamData
接口时,streamData
方法会创建一个 ResponseBodyEmitter
对象,并返回给客户端。
在这个方法内,我们启动了一个新的线程,该线程负责调用第三方模型,并从模型那里获取流式数据。具体来说,我们使用 HttpURLConnection
打开一个连接到第三方 API(your_api_url_here
),发送 POST 请求,并获取该 API 返回的输入流(InputStream
)。
接下来,我们创建一个 BufferedReader
来逐行读取从第三方 API 返回的数据流。对于每一行数据,我们使用 emitter.send(line, MediaType.TEXT_PLAIN)
将数据发送到客户端。这实际上是将数据推送给客户端,而不是等待客户端拉取数据。
在发送数据的过程中,我们对异常进行了处理。如果客户端断开连接或者其他错误发生,我们捕获 IOException
并相应地处理。最终,在处理完数据流之后,我们发送 complete
信号,表示数据流传输完成。
这种方式允许你在异步的情况下,实时地将第三方模型返回的数据发送给客户端。客户端可以通过订阅这个接口来获取实时数据,而不需要等待整个流程执行完毕。
请注意,这里使用了一个简单的线程来执行异步任务,但在实际应用中,你可能会考虑使用 Spring 的异步特性,例如 @Async
注解结合 ThreadPoolTaskExecutor
,以便更好地管理异步任务。
3、客户端如何订阅接收返回的ResponseBodyEmitter
?
在前端使用 WebSocket 订阅数据的步骤包括创建 WebSocket 连接、监听连接状态、发送和接收消息等。以下是一个简单的使用 WebSocket 的示例,假设你的后端已经支持 WebSocket:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WebSocket Example</title></head><body><script> // 创建 WebSocket 连接,指定连接的地址 const socket = new WebSocket('ws://your-server-address/api/streamData'); // 监听连接打开事件 socket.addEventListener('open', (event) => { console.log('WebSocket连接已打开:', event); }); // 监听连接关闭事件 socket.addEventListener('close', (event) => { console.log('WebSocket连接已关闭:', event); }); // 监听接收到消息事件 socket.addEventListener('message', (event) => { // 处理从服务器收到的消息 const data = event.data; console.log('收到消息:', data); // 在这里,你可以将 data 渲染到页面上,或者进行其他业务逻辑的处理 }); // 监听发生错误事件 socket.addEventListener('error', (event) => { console.error('WebSocket连接发生错误:', event); }); // 当页面卸载时,关闭 WebSocket 连接 window.addEventListener('beforeunload', () => { socket.close(); });</script></body></html>
这个示例中,我们创建了一个 WebSocket 连接到 ws://your-server-address/api/streamData
,这个地址应该替换为你实际的后端 WebSocket 地址。然后,我们监听连接打开、关闭、接收消息和错误等事件。
在 message
事件中,我们处理从服务器端实时传输过来的数据。这个数据可能是由 ResponseBodyEmitter
在后端逐行发送的。你可以根据你的需求,将这些数据渲染到页面上或者进行其他业务逻辑的处理。
确保你的后端代码正确处理 WebSocket 连接,以及在适当的时候关闭连接。这样,前端就能够通过 WebSocket 实时接收到后端发送的数据。
请注意:
new WebSocket('ws://your-server-address/api/streamData')
中的 your-server-address
应该替换为你实际的后端服务器地址。如果你的后端支持 wss
(WebSocket Secure),你应该使用 wss
而不是 ws
。这个示例假设你的后端已经提供了 WebSocket 服务,并且在 /api/streamData
路径上提供了 WebSocket 端点。你需要确保后端正确处理 WebSocket 连接,并在数据准备好时通过 WebSocket 发送给客户端。在 message
事件中,你可以处理从服务器端收到的实时数据。你可以将这些数据渲染到页面上,或者进行其他业务逻辑的处理。 在实际应用中,你可能需要根据你的业务逻辑定制 WebSocket 的行为,并添加更多的错误处理和状态管理。
4、data
参数的含义
ResponseBodyEmitter
是用于在 Spring MVC 中异步发送响应数据的类。在这个场景下,data
参数中包含的数据是在后端逐行发送给前端的实时数据。
具体来说,data
包含的是每一行文本数据,这取决于后端代码如何使用 ResponseBodyEmitter
来发送数据。在先前的例子中,我们通过 emitter.send(line, MediaType.TEXT_PLAIN)
来发送每一行数据,而这个数据就是 line
。
举例来说,如果后端的逻辑是读取一个文本文件,逐行发送给前端,那么 data
就是文本文件中的每一行数据。如果后端的逻辑是调用某个 API 获取实时数据,那么 data
就是从该 API 获取到的每一行数据。