基于deepseek的智能语音客服【第二讲】后端异步接口调用封装

本篇内容主要讲前端请求(不包含)访问后端服务接口,接口通过检索知识库,封装提示词,调用deepseek的,并返回给前端的全过程,非完整代码,不可直接运行。

1.基于servlet封装异步请求

为什么要进行异步分装?因为前段需要流式输出,以减少用户长时间等待造成的不良体验

集成HttpServlet 实现POST方法,get方式多伦对话有数据了限制

java 复制代码
@WebServlet(
    urlPatterns = "/ds",
    asyncSupported = true // 启用异步支持
)
public class DeepseekApi extends HttpServlet 

2.设置跨域(如果没有前后端分离可以忽略此步骤)

java 复制代码
		response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with, Content-Type");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        
        // 设置SSE头
        response.setContentType("text/event-stream");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Connection", "keep-alive");
        // 处理OPTIONS预检请求
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            asyncContext.complete();
            return;
        }

3.获取参数

我这里前段直接封装好多轮对话参数和当前问题,当前问题为什么要分开,应为问题需要在知识库做增强检索,这样好取参数

java 复制代码
 // 获取问题参数
    String question = request.getParameter("question");
    String his = request.getParameter("his");

4.封装异步任务

asyncContext

java 复制代码
 // 获取异步上下文
        final AsyncContext asyncContext = request.startAsync();
        asyncContext.setTimeout(3*60*1000); // 超时 60 秒
         writer = response.getWriter();
 processRequest(asyncContext, writer, question,his);

5.设置异步事件监听

java 复制代码
 asyncContext.addListener(new AsyncListener() {
        	        @Override
        	        public void onComplete(AsyncEvent event) {
        	            // 确保资源释放
        	        }

        	        @Override
        	        public void onTimeout(AsyncEvent event) {
        	            writer.write("event:error\ndata:请求超时\n\n");
        	            writer.flush();
        	            asyncContext.complete();
        	        }

        	        @Override
        	        public void onError(AsyncEvent event) {
        	            asyncContext.complete();
        	        }

        	        @Override
        	        public void onStartAsync(AsyncEvent event) {}
        	    });

6.检索向量库

java 复制代码
   // 检索向量库
    List<Map<String, Object>> kl = KnowledgeBaseService.searchKnowledge(question, 40);

7.构建提示词

java 复制代码
 private static String buildPrompt(String question, List<Map<String, Object>> knowledge) {
        System.out.println("提示词封装!");
        String txtString="";
		for (Map<String, Object> map : knowledge) {
			txtString+=map.get("title")+"\n"+map.get("text")+"\n";
		 }
        return "问题:\n" + question + "\n\n参考知识:" +txtString+ "\n\n请以参考知识为主,给出简明扼要的回复,如果参考知识与问题没有相关性或不存在请拒绝答复:";
    }

8.构建请求体

java 复制代码
  // 新的对话内容
            JSONObject newMessage = new JSONObject();
            newMessage.put("role", "user");
            newMessage.put("content", prompt);

            // 插入新的对话
            messages.add(newMessage);
            
            System.out.println(messages.toJSONString());
            // 构建请求体
            Map<String, String> headers = new HashMap<>();
            headers.put("Authorization", "Bearer " + Consist.DEEPSEEK_API_KEY);
            headers.put("Content-Type", "application/json");

            JSONObject requestBody = new JSONObject();
            requestBody.put("model", Consist.MODEL_NAME);
            requestBody.put("messages", messages);
            requestBody.put("stream", true);
            requestBody.put("max_tokens", Consist.MAX_TOKENS);

9.进行异步调用

java 复制代码
sendAsyncRequestWithCallback(
            		Consist.DEEPSEEK_API_URL,
                headers,
                requestBody.toJSONString(),
                new StreamCallback() {
                    @Override
                    public void onDataReceived(String content) {
//                    	System.out.print(content);
                        writer.write("data:" + content+"\n\n");
                        writer.flush();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println("调用完成!");
                        writer.write("event:done\ndata:\n\n");
                        writer.flush();
                        asyncContext.complete();
                    }

                    @Override
                    public void onError(Exception ex) {
//                    	ex.printStackTrace();
                        System.err.println("报错!");
                        writer.write("event:error\ndata:发生错误\n\n");
                        writer.flush();
                        asyncContext.complete();
                    }
                }
            );

10.监察调用状态,防止客户端掉线造成的异常

java 复制代码
  if (asyncClient == null || !asyncClient.isRunning()) {
            synchronized (DeepseekR1WebApiPost.class) {
                if (asyncClient == null || !asyncClient.isRunning()) {
                    try {
                        if (asyncClient != null) {
                            asyncClient.close();
                        }
                        asyncClient = HttpAsyncClients.createDefault();
                        asyncClient.start();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

11.封装客户端参数,调用deepseek官网接口

java 复制代码
 HttpPost request = new HttpPost(new URI(url));
        request.setEntity(new StringEntity(requestBody, "UTF-8"));
        headers.forEach(request::setHeader);

        HttpHost target = new HttpHost(
            new URI(url).getHost(),
            new URI(url).getPort(),
            new URI(url).getScheme()
        );

12.执行异步请求,并处理返回数据

java 复制代码
   @Override
                protected void onContentReceived(ContentDecoder decoder, IOControl ioctrl) {
                    try {
                        ByteBuffer bb = ByteBuffer.allocate(Consist.MAX_TOKENS);
                        int read;

                        while ((read = decoder.read(bb)) > 0) {
                            bb.flip();
                            byte[] bytes = new byte[bb.remaining()];
                            bb.get(bytes);
                            buffer.write(bytes);
                            processBuffer(callback);
                            bb.clear();
                        }
                    } catch (Exception e) {
                    	e.printStackTrace();
                        System.out.println("报错: " + e.getMessage().toString());
                        callback.onError(e);
                    }
                }

13.解析流式数据返回给前端

java 复制代码
 private void processBuffer(StreamCallback callback) throws Exception {
                    String chunk = buffer.toString("UTF-8");
                    buffer.reset();
                    // 按行分割并过滤空行
                    String[] lines = chunk.split("\\r?\\n");
                    for (String line : lines) {
                        if (line.isEmpty()) continue;
                        if (line.startsWith("data: ")) {
                            String jsonStr = line.substring(6).trim();
                            if ("[DONE]".equals(jsonStr)) {
                                callback.onComplete();
                                return; // 提前返回避免后续处理
                            }
                            try {
                            	if(isJsonComplete(jsonStr)) {
                            		   JsonObject responseJson = JsonParser.parseString(jsonStr).getAsJsonObject();
                                       JsonArray choices = responseJson.getAsJsonArray("choices");
                                        callback.onDataReceived(choices.toString());
                            	};
//                             
                            	// callback.onDataReceived(jsonStr);
                            } catch (Exception e) {
                            	callback.onError(new RuntimeException("解析 JSON 失败: " + jsonStr, e));
                            	continue;
                            	
                            }
                        }
                    }
                }
相关推荐
AI街潜水的八角3 分钟前
深度学习图像分类数据集—蘑菇识别分类
人工智能·深度学习·分类
掘金-我是哪吒15 分钟前
分布式微服务系统架构第157集:JavaPlus技术文档平台日更-Java多线程编程技巧
java·分布式·微服务·云原生·架构
飞翔的佩奇23 分钟前
Java项目:基于SSM框架实现的忘忧小区物业管理系统【ssm+B/S架构+源码+数据库+毕业论文+开题报告】
java·数据库·mysql·vue·毕业设计·ssm框架·小区物业管理系统
飞睿科技27 分钟前
乐鑫代理商飞睿科技,2025年AI智能语音助手市场发展趋势与乐鑫芯片解决方案分析
人工智能
许泽宇的技术分享29 分钟前
从新闻到知识图谱:用大模型和知识工程“八步成诗”打造科技并购大脑
人工智能·科技·知识图谱
RainbowSea41 分钟前
跨域问题(Allow CORS)解决(3 种方法)
java·spring boot·后端
掘金-我是哪吒42 分钟前
分布式微服务系统架构第155集:JavaPlus技术文档平台日更-Java线程池实现原理
java·分布式·微服务·云原生·架构
坤坤爱学习2.043 分钟前
求医十年,病因不明,ChatGPT:你看起来有基因突变
人工智能·ai·chatgpt·程序员·大模型·ai编程·大模型学
RainbowSea1 小时前
问题 1:MyBatis-plus-3.5.9 的分页功能修复
java·spring boot·mybatis
前端 贾公子1 小时前
monorepo + Turborepo --- 开发应用程序
java·前端·javascript