AI 面试业务逻辑初始化

This commit is contained in:
wangxiangshun
2025-10-07 13:20:51 +08:00
parent 67c22e3082
commit e04f5e3091
15 changed files with 279 additions and 536 deletions

View File

@@ -0,0 +1,239 @@
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 lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* 语音面试 web处理器
*
*/
@Slf4j
@ServerEndpoint("/voice-websocket/{clientId}")
@Component
public class ChatWebSocketHandler {
// 音频参数配置 - 根据实际发送的音频流参数调整
private static final float SAMPLE_RATE = 16000.0f; // 采样率
private static final int SAMPLE_SIZE_IN_BITS = 16; // 采样位数
private static final int CHANNELS = 1; // 声道数 (1=单声道, 2=立体声)
private static final boolean SIGNED = true; // 是否有符号
private static final boolean BIG_ENDIAN = false; // 字节序
// 语音文件保存目录
private static final String VOICE_STORAGE_DIR = "/voice_files/";
// 语音结果文件保存目录
private static final String VOICE_STORAGE_RESULT_DIR = "/voice_result_files/";
@Autowired
private ElevenLabsClient elevenLabsClient;
@Autowired
private ChatGPTClient chatGPTClient;
@Autowired
private WhisperClient whisperClient;
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[] data = new byte[byteBuffer.remaining()];
// 生成唯一文件名
String fileName = clientId + "_" + System.currentTimeMillis() + ".mp3";
String pathUrl = RuoYiConfig.getProfile()+VOICE_STORAGE_DIR + fileName;
log.info("文件路径为:{}",pathUrl);
saveAsWebM(data,pathUrl);
// //拿到文件进行文字转换
// String resultText = whisperClient.handleVoiceToText(pathUrl);
// //把提问的文字发送给CPT
// String resultMsg = chatGPTClient.handleAiChat(resultText);
// //把结果文字转成语音文件
// //生成文件
// // 生成唯一文件名
// String resultFileName = clientId + "_" + System.currentTimeMillis() + ".webm";
// String resultPathUrl = RuoYiConfig.getProfile()+VOICE_STORAGE_DIR + resultFileName;
// elevenLabsClient.handleTextToVoice(resultMsg,resultPathUrl);
//把语音文件转换成流,发送给前端
System.out.println("接收到二进制数据,长度: " + data.length + " bytes");
try {
//文件转换成文件流
// ByteBuffer outByteBuffer = convertFileToByteBuffer(resultPathUrl);
// session.getBasicRemote().sendBinary(outByteBuffer);
// 发送响应确认
session.getBasicRemote().sendText("已收到二进制数据,长度: " + data.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;
}
/**
* 将接收的音频数据保存为WAV文件
*/
private void saveAudioToFile(byte[] audioData,String pathUrl) {
if (audioData == null || audioData.length == 0) {
System.out.println("没有接收到音频数据,不保存文件");
return;
}
try {
// 合并所有音频数据
// 创建音频格式
AudioFormat format = new AudioFormat(
SAMPLE_RATE,
SAMPLE_SIZE_IN_BITS,
CHANNELS,
SIGNED,
BIG_ENDIAN
);
// 创建音频输入流
ByteArrayInputStream bais = new ByteArrayInputStream(audioData);
AudioInputStream ais = new AudioInputStream(bais, format, audioData.length / format.getFrameSize());
// 保存为WAV文件
File outputFile = new File(pathUrl);
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, outputFile);
System.out.println("音频文件保存成功: " + outputFile.getAbsolutePath());
} catch (IOException e) {
System.err.println("保存音频文件失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 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;
}
}