基于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;
                            	
                            }
                        }
                    }
                }
相关推荐
kalvin_y_liu12 分钟前
智能体框架大PK!谷歌ADK VS 微软Semantic Kernel
人工智能·microsoft·谷歌·智能体
爱看科技15 分钟前
智能眼镜行业腾飞在即,苹果/微美全息锚定“AR+AI眼镜融合”之路抢滩市场!
人工智能·ar
瓯雅爱分享15 分钟前
Java+Vue构建的采购招投标一体化管理系统,集成招标计划、投标审核、在线竞价、中标公示及合同跟踪功能,附完整源码,助力企业实现采购全流程自动化与规范化
java·mysql·vue·软件工程·源代码管理
mit6.8243 小时前
[C# starter-kit] 命令/查询职责分离CQRS | MediatR |
java·数据库·c#
Juchecar3 小时前
LLM模型与ML算法之间的关系
人工智能
诸神缄默不语3 小时前
Maven用户设置文件(settings.xml)配置指南
xml·java·maven
任子菲阳3 小时前
学Java第三十四天-----抽象类和抽象方法
java·开发语言
FIN66683 小时前
昂瑞微:深耕射频“芯”赛道以硬核实力冲刺科创板大门
前端·人工智能·科技·前端框架·信息与通信·智能
benben0443 小时前
京东agent之joyagent解读
人工智能
LONGZETECH3 小时前
【龙泽科技】汽车动力与驱动系统综合分析技术1+X仿真教学软件(1.1.3 -初级)
人工智能·科技·汽车·汽车仿真教学软件·汽车教学软件