websocket 生成完整的文件

This commit is contained in:
wangxiangshun
2025-10-05 14:13:11 +08:00
parent ab7213e4ab
commit eeeb176098
4 changed files with 206 additions and 8 deletions

View File

@@ -0,0 +1,58 @@
package com.vetti.socket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.HandshakeInterceptor;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Configuration
@EnableWebSocket
public class FileReceiverConfig implements WebSocketConfigurer {
@Bean
public FileReceiverWebSocketHandler fileReceiverWebSocketHandler() {
return new FileReceiverWebSocketHandler();
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(fileReceiverWebSocketHandler(), "/voice-websocket")
.addInterceptors(handshakeInterceptor())
.setAllowedOrigins("*"); // 生产环境需替换为具体域名
}
@Bean
public HandshakeInterceptor handshakeInterceptor() {
return new HandshakeInterceptor() {
@Override
public boolean beforeHandshake(org.springframework.http.server.ServerHttpRequest request,
org.springframework.http.server.ServerHttpResponse response,
org.springframework.web.socket.WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
if (request instanceof org.springframework.http.server.ServletServerHttpRequest) {
HttpServletRequest servletRequest =
((org.springframework.http.server.ServletServerHttpRequest) request).getServletRequest();
String clientId = servletRequest.getParameter("clientId");
if (clientId != null && !clientId.isEmpty()) {
attributes.put("clientId", clientId);
return true;
}
}
return false;
}
@Override
public void afterHandshake(org.springframework.http.server.ServerHttpRequest request,
org.springframework.http.server.ServerHttpResponse response,
org.springframework.web.socket.WebSocketHandler wsHandler,
Exception exception) {
// 握手后处理
}
};
}
}

View File

@@ -0,0 +1,141 @@
package com.vetti.socket;
import com.vetti.socket.vo.FileMetadata;
import com.vetti.socket.vo.FileTransferState;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class FileReceiverWebSocketHandler extends TextWebSocketHandler {
// 存储客户端的文件传输状态clientId -> FileTransferState
private final Map<String, FileTransferState> transferStates = new ConcurrentHashMap<>();
private final ObjectMapper objectMapper = new ObjectMapper();
private static final String STORAGE_DIR = "received_files/";
// 初始化存储目录
static {
try {
Files.createDirectories(Paths.get(STORAGE_DIR));
} catch (IOException e) {
throw new RuntimeException("无法创建文件存储目录", e);
}
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
String clientId = (String) session.getAttributes().get("clientId");
transferStates.put(clientId, new FileTransferState());
System.out.println("客户端连接: " + clientId);
}
// 处理文本消息(文件元数据)
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String clientId = (String) session.getAttributes().get("clientId");
FileMetadata metadata = objectMapper.readValue(message.getPayload(), FileMetadata.class);
// 初始化文件传输状态
FileTransferState state = transferStates.get(clientId);
state.setFileName(metadata.getFileName());
state.setTotalSize(metadata.getTotalSize());
state.setTotalParts(metadata.getTotalParts());
state.setOutputStream(new FileOutputStream(STORAGE_DIR + metadata.getFileName()));
System.out.println("开始接收文件: " + metadata.getFileName() + " (" + metadata.getTotalParts() + "个分片)");
// 确认已收到元数据
session.sendMessage(new TextMessage("{\"type\":\"metadata_ack\"}"));
}
// 处理二进制消息(文件分片)
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message){
try{
System.out.println("开始-接收文件分片数据流");
String clientId = (String) session.getAttributes().get("clientId");
FileTransferState state = transferStates.get(clientId);
if (state == null || state.getOutputStream() == null) {
session.sendMessage(new TextMessage("{\"type\":\"error\", \"message\":\"未收到文件元数据\"}"));
return;
}
System.out.println("进行中-接收文件分片数据流");
// 解析分片数据
ByteBuffer payload = message.getPayload();
// int partNumber = payload.getInt(); // 前4字节是分片编号
byte[] data = new byte[payload.remaining()];
payload.get(data);
// 写入文件
state.getOutputStream().write(data);
state.incrementReceivedParts();
// 发送进度更新每5个分片或最后一个分片
if (state.getReceivedParts() % 5 == 0 || state.getReceivedParts() == state.getTotalParts()) {
// 检查是否接收完成
if (state.getReceivedParts() == state.getTotalParts()) {
System.out.println("生成完整的文件-接收文件分片数据流");
completeFileTransfer(session, state, clientId);
//进行文件数据转换
//获取最终的文件结果
//把文件转成对应的文件流,返回给前端
// session.sendMessage(new BinaryMessage());
}
}
}catch (Exception e){
e.printStackTrace();
}
}
// 完成文件传输
private void completeFileTransfer(WebSocketSession session, FileTransferState state, String clientId) throws IOException {
// 关闭文件输出流
state.getOutputStream().close();
// 验证文件大小
File file = new File(STORAGE_DIR + state.getFileName());
boolean fileValid = file.length() == state.getTotalSize();
// 发送完成消息
String result = fileValid ?
"{\"type\":\"complete\", \"message\":\"文件接收完成\", \"filePath\":\"" + file.getAbsolutePath() + "\"}" :
"{\"type\":\"error\", \"message\":\"文件损坏,大小不匹配\"}";
session.sendMessage(new TextMessage(result));
System.out.println("文件接收" + (fileValid ? "完成" : "失败") + ": " + state.getFileName());
// 清理状态
transferStates.remove(clientId);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String clientId = (String) session.getAttributes().get("clientId");
FileTransferState state = transferStates.remove(clientId);
// 关闭可能存在的文件流
if (state != null && state.getOutputStream() != null) {
try {
state.getOutputStream().close();
// 删除未完成的文件
Files.deleteIfExists(Paths.get(STORAGE_DIR + state.getFileName()));
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("客户端断开连接: " + clientId);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
System.err.println("传输错误: " + exception.getMessage());
session.close(CloseStatus.SERVER_ERROR);
}
}

View File

@@ -81,15 +81,16 @@ public class VoiceWebSocketHandler extends TextWebSocketHandler {
// 发送进度更新每5个分片或最后一个分片 // 发送进度更新每5个分片或最后一个分片
if (state.getReceivedParts() % 5 == 0 || state.getReceivedParts() == state.getTotalParts()) { if (state.getReceivedParts() % 5 == 0 || state.getReceivedParts() == state.getTotalParts()) {
double progress = (double) state.getReceivedParts() / state.getTotalParts() * 100;
session.sendMessage(new TextMessage(
"{\"type\":\"progress\", \"progress\":" + progress + "}"
));
// 检查是否接收完成 // 检查是否接收完成
if (state.getReceivedParts() == state.getTotalParts()) { if (state.getReceivedParts() == state.getTotalParts()) {
log.info("生成完整的文件-接收文件分片数据流"); log.info("生成完整的文件-接收文件分片数据流");
completeFileTransfer(session, state, clientId); completeFileTransfer(session, state, clientId);
//进行文件数据转换
//获取最终的文件结果
//把文件转成对应的文件流,返回给前端
// session.sendMessage(new BinaryMessage());
} }
} }
}catch (Exception e){ }catch (Exception e){
@@ -101,11 +102,9 @@ public class VoiceWebSocketHandler extends TextWebSocketHandler {
private void completeFileTransfer(WebSocketSession session, FileTransferState state, String clientId) throws IOException { private void completeFileTransfer(WebSocketSession session, FileTransferState state, String clientId) throws IOException {
// 关闭文件输出流 // 关闭文件输出流
state.getOutputStream().close(); state.getOutputStream().close();
// 验证文件大小 // 验证文件大小
File file = new File(STORAGE_DIR + state.getFileName()); File file = new File(STORAGE_DIR + state.getFileName());
boolean fileValid = file.length() == state.getTotalSize(); boolean fileValid = file.length() == state.getTotalSize();
// 发送完成消息 // 发送完成消息
String result = fileValid ? String result = fileValid ?
"{\"type\":\"complete\", \"message\":\"文件接收完成\", \"filePath\":\"" + file.getAbsolutePath() + "\"}" : "{\"type\":\"complete\", \"message\":\"文件接收完成\", \"filePath\":\"" + file.getAbsolutePath() + "\"}" :

View File

@@ -24,7 +24,7 @@ public class WebSocketConfig implements WebSocketConfigurer {
@Override @Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册WebSocket处理器设置路径和允许跨域 // 注册WebSocket处理器设置路径和允许跨域
registry.addHandler(voiceWebSocketHandler, "/voice-websocket") registry.addHandler(voiceWebSocketHandler, "/voice-websocket123")
.addInterceptors(voiceHandshakeInterceptor) .addInterceptors(voiceHandshakeInterceptor)
.setAllowedOrigins("*"); // 生产环境应指定具体域名而非* .setAllowedOrigins("*"); // 生产环境应指定具体域名而非*
} }