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,16 @@
package com.vetti.socket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class ChatWebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}

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;
}
}

View File

@@ -1,58 +0,0 @@
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-websocket111111")
.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

@@ -1,141 +0,0 @@
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

@@ -0,0 +1,19 @@
package com.vetti.socket;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
import javax.websocket.server.ServerEndpointConfig;
@Component
public class LargeMessageWebSocketConfigurator extends ServerEndpointConfig.Configurator {
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(1048576); // 文本消息缓冲区
container.setMaxBinaryMessageBufferSize(10485760); // 二进制消息缓冲区
return container;
}
}

View File

@@ -1,36 +0,0 @@
package com.vetti.socket;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;
@Component
public class VoiceHandshakeInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
// 从请求参数中获取客户端ID
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
String clientId = servletRequest.getServletRequest().getParameter("clientId");
if (clientId != null && !clientId.isEmpty()) {
attributes.put("clientId", clientId);
System.out.println("客户端连接: " + clientId);
}
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Exception exception) {
// 握手后操作,可留空
}
}

View File

@@ -1,170 +0,0 @@
package com.vetti.socket;
import cn.hutool.json.JSONUtil;
import com.vetti.socket.vo.FileTransferState;
import com.vetti.socket.vo.VoicePartMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.Base64;
@Slf4j
@Component
public class VoiceWebSocketHandler extends TextWebSocketHandler {
// 存储每个客户端的语音分片key: clientId, value: 分片映射
private final Map<String, Map<Integer, byte[]>> clientVoiceParts = new ConcurrentHashMap<>();
// 存储每个客户端的总分片数key: clientId
private final Map<String, Integer> clientTotalParts = new ConcurrentHashMap<>();
// 用于并发控制的锁
private final Map<String, ReentrantLock> clientLocks = new ConcurrentHashMap<>();
// JSON序列化工具
private final ObjectMapper objectMapper = new ObjectMapper();
// 语音文件保存目录
private static final String VOICE_STORAGE_DIR = "voice_files/";
public VoiceWebSocketHandler() {
// 初始化存储目录
File dir = new File(VOICE_STORAGE_DIR);
if (!dir.exists()) {
dir.mkdirs();
}
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
String clientId = getClientId(session);
if (clientId != null) {
// 初始化客户端数据结构
clientVoiceParts.put(clientId, new TreeMap<>()); // TreeMap保证分片有序
clientLocks.putIfAbsent(clientId, new ReentrantLock());
System.out.println("客户端连接建立: " + clientId);
}
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
log.info("开始进入文本传输里面了");
String clientId = getClientId(session);
if (clientId == null) {
System.err.println("无法获取客户端ID");
return;
}
try {
// 解析前端发送的JSON消息
VoicePartMessage voiceMessage = objectMapper.readValue(message.getPayload(), VoicePartMessage.class);
// 处理语音分片
if ("voice_part".equals(voiceMessage.getType())) {
processVoicePart(clientId, voiceMessage, session);
}
} catch (Exception e) {
System.err.println("处理消息出错: " + e.getMessage());
e.printStackTrace();
}
}
// 处理二进制消息(文件分片)
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message){
log.info("开始进入文件流传输里面了");
log.info("获取的数据为:{}", JSONUtil.toJsonStr(message));
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String clientId = getClientId(session);
if (clientId != null) {
// 清理客户端资源
clientVoiceParts.remove(clientId);
clientTotalParts.remove(clientId);
clientLocks.remove(clientId);
System.out.println("客户端连接关闭: " + clientId);
}
}
/**
* 处理语音分片
*/
private void processVoicePart(String clientId, VoicePartMessage message, WebSocketSession session) throws Exception {
ReentrantLock lock = clientLocks.get(clientId);
lock.lock(); // 加锁确保线程安全
try {
// 保存总分片数
clientTotalParts.put(clientId, message.getTotalParts());
// 解码Base64数据并存储分片
byte[] voiceData = Base64.getDecoder().decode(message.getData());
clientVoiceParts.get(clientId).put(message.getPartNumber(), voiceData);
System.out.printf("接收客户端 %s 的分片 %d/%d%n",
clientId, message.getPartNumber() + 1, message.getTotalParts());
// 检查是否所有分片都已接收
checkAndMergeParts(clientId, session);
} finally {
lock.unlock(); // 释放锁
}
}
/**
* 检查是否所有分片都已接收,如果是则合并
*/
private void checkAndMergeParts(String clientId, WebSocketSession session) throws Exception {
Map<Integer, byte[]> parts = clientVoiceParts.get(clientId);
Integer totalParts = clientTotalParts.get(clientId);
if (parts == null || totalParts == null) {
return;
}
// 所有分片都已接收
if (parts.size() == totalParts) {
System.out.println("所有分片接收完成,开始合并: " + clientId);
// 生成唯一文件名
String fileName = clientId + "_" + System.currentTimeMillis() + ".wav";
Path outputPath = Paths.get(VOICE_STORAGE_DIR + fileName);
// 合并分片
try (FileOutputStream fos = new FileOutputStream(outputPath.toFile())) {
for (byte[] part : parts.values()) {
fos.write(part);
}
}
System.out.println("语音文件合并完成,保存路径: " + outputPath);
// 向客户端发送处理完成消息
Map<String, Object> response = new HashMap<>();
response.put("type", "complete");
response.put("message", "语音接收完成");
response.put("fileName", fileName);
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
// 清理已合并的分片数据
clientVoiceParts.get(clientId).clear();
}
}
/**
* 从会话中获取客户端ID
*/
private String getClientId(WebSocketSession session) {
return (String) session.getAttributes().get("clientId");
}
}

View File

@@ -1,31 +0,0 @@
package com.vetti.socket;
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;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
private final VoiceWebSocketHandler voiceWebSocketHandler;
private final VoiceHandshakeInterceptor voiceHandshakeInterceptor;
// 构造函数注入
public WebSocketConfig(VoiceWebSocketHandler voiceWebSocketHandler,
VoiceHandshakeInterceptor interceptor) {
this.voiceWebSocketHandler = voiceWebSocketHandler;
this.voiceHandshakeInterceptor = interceptor;
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册WebSocket处理器设置路径和允许跨域
registry.addHandler(voiceWebSocketHandler, "/voice-websocket")
.addInterceptors(voiceHandshakeInterceptor)
.setAllowedOrigins("*"); // 生产环境应指定具体域名而非*
}
}

View File

@@ -1,20 +0,0 @@
package com.vetti.socket.vo;
/**
* 文件元数据类
*/
public class FileMetadata {
private String fileName;
private long totalSize;
private int totalParts;
// getter和setter方法
public String getFileName() { return fileName; }
public void setFileName(String fileName) { this.fileName = fileName; }
public long getTotalSize() { return totalSize; }
public void setTotalSize(long totalSize) { this.totalSize = totalSize; }
public int getTotalParts() { return totalParts; }
public void setTotalParts(int totalParts) { this.totalParts = totalParts; }
}

View File

@@ -1,27 +0,0 @@
package com.vetti.socket.vo;
import java.io.FileOutputStream;
/**
* 文件传输状态类
*/
public class FileTransferState {
private String fileName;
private long totalSize;
private int totalParts;
private int receivedParts = 0;
private FileOutputStream outputStream;
// getter和setter方法
public String getFileName() { return fileName; }
public void setFileName(String fileName) { this.fileName = fileName; }
public long getTotalSize() { return totalSize; }
public void setTotalSize(long totalSize) { this.totalSize = totalSize; }
public int getTotalParts() { return totalParts; }
public void setTotalParts(int totalParts) { this.totalParts = totalParts; }
public int getReceivedParts() { return receivedParts; }
public void incrementReceivedParts() { this.receivedParts++; }
public FileOutputStream getOutputStream() { return outputStream; }
public void setOutputStream(FileOutputStream outputStream) { this.outputStream = outputStream; }
}

View File

@@ -1,47 +0,0 @@
package com.vetti.socket.vo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* 语音分片消息实体类对应前端发送的JSON结构
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class VoicePartMessage {
private String type; // 消息类型,如"voice_part"
private int partNumber; // 分片编号从0开始
private int totalParts; // 总分片数
private String data; // Base64编码的分片数据
// getter和setter
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getPartNumber() {
return partNumber;
}
public void setPartNumber(int partNumber) {
this.partNumber = partNumber;
}
public int getTotalParts() {
return totalParts;
}
public void setTotalParts(int totalParts) {
this.totalParts = totalParts;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}

View File

@@ -25,7 +25,6 @@ public class AiCommonController extends BaseController
@Autowired
private ElevenLabsClient elevenLabsClient;
@Autowired
private ChatGPTClient chatGPTClient;

View File

@@ -75,6 +75,6 @@ xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
excludes: /system/notice,/voice-websocket/*
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
urlPatterns: /system/*,/monitor/*,/tool/*,/voice-websocket/*

View File

@@ -75,6 +75,6 @@ xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
excludes: /system/notice,/voice-websocket/*
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
urlPatterns: /system/*,/monitor/*,/tool/*,/voice-websocket/*

View File

@@ -111,7 +111,7 @@ public class SecurityConfig
.authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.antMatchers("/login", "/register", "/captchaImage","/aiCommon/**","/voice-websocket").permitAll()
requests.antMatchers("/login", "/register", "/captchaImage","/aiCommon/**","/voice-websocket/**").permitAll()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()