Files
Vetti-Service-new/vetti-admin/src/main/java/com/vetti/socket/ChatWebSocketHandler.java
2025-10-07 14:30:50 +08:00

199 lines
6.9 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.vetti.socket;
import com.vetti.common.ai.elevenLabs.ElevenLabsClient;
import com.vetti.common.ai.gpt.ChatGPTClient;
import com.vetti.common.ai.whisper.WhisperClient;
import com.vetti.common.config.RuoYiConfig;
import com.vetti.common.utils.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* 语音面试 web处理器
*/
@Slf4j
@ServerEndpoint("/voice-websocket/{clientId}")
@Component
public class ChatWebSocketHandler {
// 语音文件保存目录
private static final String VOICE_STORAGE_DIR = "/voice_files/";
// 语音结果文件保存目录
private static final String VOICE_STORAGE_RESULT_DIR = "/voice_result_files/";
public ChatWebSocketHandler() {
// 初始化存储目录
File dir = new File(RuoYiConfig.getProfile() + VOICE_STORAGE_DIR);
if (!dir.exists()) {
dir.mkdirs();
}
File resultDir = new File(RuoYiConfig.getProfile() + VOICE_STORAGE_RESULT_DIR);
if (!resultDir.exists()) {
resultDir.mkdirs();
}
}
// 连接建立时调用
@OnOpen
public void onOpen(Session session, @PathParam("clientId") String clientId) {
log.info("WebSocket 链接已建立:{}", clientId);
//创建会话
}
// 接收文本消息
@OnMessage
public void onTextMessage(Session session, String message) {
System.out.println("接收到文本消息: " + message);
// 可以在这里处理文本流数据
try {
// 发送响应
session.getBasicRemote().sendText("已收到文本: " + message);
} catch (IOException e) {
e.printStackTrace();
}
}
// 接收二进制消息(流数据)
@OnMessage
public void onBinaryMessage(Session session, @PathParam("clientId") String clientId, ByteBuffer byteBuffer) {
try {
log.info("客户端ID为:{}", clientId);
// 处理二进制流数据
byte[] bytes = new byte[byteBuffer.remaining()];
//从缓冲区中读取数据并存储到指定的字节数组中
byteBuffer.get(bytes);
// 生成唯一文件名
String fileName = clientId + "_" + System.currentTimeMillis() + ".webm";
String pathUrl = RuoYiConfig.getProfile()+VOICE_STORAGE_DIR + fileName;
// String pathUrl = "/Users/wangxiangshun/Desktop/0.8733346782733291.webm";
log.info("文件路径为:{}", pathUrl);
saveAsWebM(bytes, pathUrl);
//拿到文件进行文字转换
WhisperClient whisperClient = SpringUtils.getBean(WhisperClient.class);
String resultText = whisperClient.handleVoiceToText(pathUrl);
//把提问的文字发送给CPT
ChatGPTClient chatGPTClient = SpringUtils.getBean(ChatGPTClient.class);
String resultMsg = chatGPTClient.handleAiChat(resultText);
//把结果文字转成语音文件
//生成文件
// 生成唯一文件名
String resultFileName = clientId + "_" + System.currentTimeMillis() + ".webm";
String resultPathUrl = RuoYiConfig.getProfile() + VOICE_STORAGE_RESULT_DIR + resultFileName;
ElevenLabsClient elevenLabsClient = SpringUtils.getBean(ElevenLabsClient.class);
elevenLabsClient.handleTextToVoice(resultMsg, resultPathUrl);
//把语音文件转换成流,发送给前端
System.out.println("接收到二进制数据,长度: " + bytes.length + " bytes");
try {
//文件转换成文件流
ByteBuffer outByteBuffer = convertFileToByteBuffer(resultPathUrl);
session.getBasicRemote().sendBinary(outByteBuffer);
// 发送响应确认
session.getBasicRemote().sendText("已收到二进制数据,长度: " + bytes.length);
} catch (IOException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 连接关闭时调用
@OnClose
public void onClose(Session session, CloseReason reason) {
System.out.println("WebSocket连接已关闭: " + session.getId() + ", 原因: " + reason.getReasonPhrase());
}
// 发生错误时调用
@OnError
public void onError(Session session, Throwable throwable) {
System.err.println("WebSocket错误发生: " + throwable.getMessage());
throwable.printStackTrace();
}
/**
* 将字节数组保存为WebM文件
*
* @param byteData 包含WebM数据的字节数组
* @param filePath 目标文件路径
* @return 操作是否成功
*/
private boolean saveAsWebM(byte[] byteData, String filePath) {
// 检查输入参数
if (byteData == null || byteData.length == 0) {
System.err.println("字节数组为空无法生成WebM文件");
return false;
}
if (filePath == null || filePath.trim().isEmpty()) {
System.err.println("文件路径不能为空");
return false;
}
// 确保文件以.webm结尾
if (!filePath.toLowerCase().endsWith(".webm")) {
filePath += ".webm";
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(filePath);
fos.write(byteData);
fos.flush();
System.out.println("WebM文件已成功生成: " + filePath);
return true;
} catch (IOException e) {
System.err.println("写入文件时发生错误: " + e.getMessage());
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
/**
* File 转换成 ByteBuffer
*
* @param fileUrl 文件路径
* @return
*/
private ByteBuffer convertFileToByteBuffer(String fileUrl) {
File file = new File(fileUrl);
// 使用RandomAccessFile获取FileChannel
try (RandomAccessFile raf = new RandomAccessFile(file, "r");
FileChannel channel = raf.getChannel()) {
// 分配与文件大小相同的ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate((int) channel.size());
// 从通道读取数据到缓冲区
channel.read(buffer);
// 切换为读模式
buffer.flip();
return buffer;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}