package admin.server; import admin.config.SpeechToTextConfig; import admin.model.WebSocketModel; import admin.modules.chatgpt.domain.ChatgptRoleRecords; import admin.modules.chatgpt.domain.ChatgptRoleSession; import admin.modules.chatgpt.service.ChatgptRoleRecordsService; import admin.modules.chatgpt.service.ChatgptRoleSessionService; import admin.service.MaxkbOpenApiService; import admin.util.DateUtil; import admin.util.ScreenEnum; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.ResponseBody; import org.springframework.stereotype.Component; import javax.annotation.Resource; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URL; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; /** * @author lj * @date 2025/2/20 19:46 */ @ServerEndpoint("/webSocket/voice/{memberId}") @Component @Slf4j public class VoiceWebSocketServer { private static AtomicInteger onlineNum = new AtomicInteger(); private static SpeechToTextConfig voiceConfig; private static MaxkbOpenApiService maxkbOpenApiService; private static ChatgptRoleRecordsService chatgptRoleRecordsService; private static ChatgptRoleSessionService chatgptRoleSessionService; @Resource public void setVoiceConfig(SpeechToTextConfig speechToTextConfig){ voiceConfig = speechToTextConfig; } @Resource public void setChatgptRoleRecordsService(ChatgptRoleRecordsService chatgptRoleRecordsService){ this.chatgptRoleRecordsService = chatgptRoleRecordsService; } @Resource public void setChatgptRoleSessionService(ChatgptRoleSessionService chatgptRoleSessionService){ this.chatgptRoleSessionService = chatgptRoleSessionService; } @Resource public void setMaxkbOpenApiService(MaxkbOpenApiService maxkbOpenApiService){ VoiceWebSocketServer.maxkbOpenApiService = maxkbOpenApiService; } private static ConcurrentHashMap<String, Session> sessionPools = new ConcurrentHashMap<>(); //建立连接成功调用 @OnOpen public void onOpen(Session session, @PathParam(value = "memberId") String memberId) { log.info("{}已连接", memberId); sessionPools.put(memberId, session); //1.获取最新的会话id String maxSessionId = chatgptRoleRecordsService.queryMaxSessionId(); WebSocketModel webSocketModel = new WebSocketModel(); webSocketModel.setType("text"); webSocketModel.setAction("replaySessionId"); webSocketModel.setContent(maxSessionId); webSocketModel.setSessionId(maxSessionId); //2.返回给前端最新的sessionId String msg = JSON.toJSONString(webSocketModel); try { session.getBasicRemote().sendText(msg); } catch (IOException e) { throw new RuntimeException(e); } } //关闭连接时调用 @OnClose public void onClose(Session session, CloseReason closeReason,@PathParam(value = "memberId") String memberId) { sessionPools.remove(memberId); log.info("关闭连接"); System.out.println(sessionPools); } //收到客户端信息 @OnMessage(maxMessageSize = 10*1024*1024) public void onTextMessage(Session session,String message) throws IOException { // session.setMaxBinaryMessageBufferSize(50 * 1024 * 1024); log.info("message:{}", message); @SuppressWarnings({"unchecked"}) Map<String, Object> map = JSON.parseObject(message, Map.class); WebSocketModel webSocketModel = JSON.parseObject(message, WebSocketModel.class); //1.如果问题不存在则创建问题 ChatgptRoleSession byId = chatgptRoleSessionService.getById(webSocketModel.getSessionId()); if (byId == null && webSocketModel.getAction().equals("text")) { ChatgptRoleSession chatgptRoleSession = new ChatgptRoleSession(); chatgptRoleSession.setId(Integer.valueOf(webSocketModel.getSessionId())); chatgptRoleSession.setName(webSocketModel.getData()); chatgptRoleSession.setRoleDescId(10001); chatgptRoleSession.setUserId(ScreenEnum.getByUserName(webSocketModel.getName())); chatgptRoleSessionService.save(chatgptRoleSession); } else if (byId != null && webSocketModel.getAction().equals("text")) { ChatgptRoleRecords chatgptRoleRecords = new ChatgptRoleRecords(); chatgptRoleRecords.setMessage(webSocketModel.getData()); chatgptRoleRecords.setReceiverId(10001); chatgptRoleRecords.setSenderId(Integer.valueOf(webSocketModel.getSessionId())); chatgptRoleRecords.setCreateTime(DateUtil.getNowTimestamp()); chatgptRoleRecordsService.save(chatgptRoleRecords); } String msg = ""; // 文字聊天 if ("sendText".equals(map.get("action"))) { msg = (String) map.get("data"); if ("sendText".equals(webSocketModel.getAction())) { msg = webSocketModel.getData(); } if (msg != null && !msg.isEmpty()) { maxkbOpenApiService.talk(msg, session); } } } // @OnMessage // public void onBinaryMessage(Session session, ByteBuffer data) { // session.setMaxBinaryMessageBufferSize(50 * 1024 * 1024); // // 处理二进制音频数据块 // log.info("data:{}",data); // handleAudioChunk(session, data); // } //错误时调用 @OnError public void onError(Session session, Throwable throwable) { System.out.println("发生错误"); for (Map.Entry<String, Session> map : sessionPools.entrySet()) { Session value = map.getValue(); if (value.equals(session)) { sessionPools.remove(map.getKey()); } } throwable.printStackTrace(); } //发送消息 public void sendMessage(Session session, String message) { if (session != null) { synchronized (session) { try { session.getBasicRemote().sendText(message); } catch (IOException e) { throw new RuntimeException(e); } } } } /** * 处理音频数据 * @param session * @param data */ private void handleAudioChunk(Session session, ByteBuffer data) { //1.音频转文本 speechToText(data); //2.向ai提问获取答案 //3.文本转语音 textToSpeech(); sendMessage(session, null); } private void textToSpeech() { } /** * 语音转文本 * @param data 语音数据 */ private void speechToText(ByteBuffer data) { byte[] bytes = new byte[data.remaining()]; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try { byteArrayOutputStream.write(bytes); String authUrl = null; authUrl = getAuthUrl(voiceConfig.getHostUrl(), voiceConfig.getApiKey(), voiceConfig.getApiSecret()); OkHttpClient client = new OkHttpClient.Builder().build(); String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://"); Request request = new Request.Builder().url(url).build(); ResponseBody body = client.newCall(request).execute().body(); System.out.println(body.string()); // WebSocket webSocket = client.newWebSocket(request, new ResponseData()); } catch (Exception e) { throw new RuntimeException(e); } } public String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception { URL url = new URL(hostUrl); SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); format.setTimeZone(TimeZone.getTimeZone("GMT")); String date = format.format(new Date()); StringBuilder builder = new StringBuilder("host: ").append(url.getHost()).append("\n").// append("date: ").append(date).append("\n").// append("GET ").append(url.getPath()).append(" HTTP/1.1"); //System.out.println(builder); Charset charset = Charset.forName("UTF-8"); Mac mac = Mac.getInstance("hmacsha256"); SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(charset), "hmacsha256"); mac.init(spec); byte[] hexDigits = mac.doFinal(builder.toString().getBytes(charset)); String sha = Base64.getEncoder().encodeToString(hexDigits); //System.out.println(sha); String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha); //System.out.println(authorization); HttpUrl httpUrl = HttpUrl.parse("https://" + url.getHost() + url.getPath()).newBuilder().// addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(charset))).// addQueryParameter("date", date).// addQueryParameter("host", url.getHost()).// build(); return httpUrl.toString(); } }