简单实现一个DEEPSEEK前端+后端

1、效果展示

项目链接:beite-ai-ui

2、运行环境

text 复制代码
版本介绍
vue:3.5.13
node:18.20.7

运行:
npm i
npm run dev
http://localhost:5173/stream

3、遇到的问题

  • 处理deepseek返回的数据直接展示格式的问题

在deepseek返回的数据消息看到,数据是一个md形式的返回,以及代码高亮,因为我也不是专业的前端,参考了若依大神开发的项目,项目地址:ruoyi-vue-pro

  • 流式返回对话不显示问题

因为是使用自己后端跟deepseek对接的,一开始我是直接前端直接访问,是可以的,后面切换成访问自己的后端,发现不行,那我就观察了一下deepseek是怎么返回的,发现我的流返回的不对,折腾了一天,后面去访问各大大神,最终解决,这里附上代码 原来的代码,如果有大神知道是哪里的问题,望提出交流交流

java 复制代码
@PostMapping(value = "/deep_seek/chat_stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter deepSeekChatSteam(@Validated @RequestBody DeepSeekQueryDTO deepSeekQueryDTO) {
    DeepSeekModelDTO modelDTO = deepSeekModelConverter.queryDtoToDTO(deepSeekQueryDTO);

    modelDTO.setTemperature(0.7);
    modelDTO.setMaxTokens(2000);
    modelDTO.setTopP(1.0);
    modelDTO.setStream(Objects.nonNull(deepSeekQueryDTO.getStream()) ? deepSeekQueryDTO.getStream() : Boolean.FALSE);

    // 流式返回
    SseEmitter emitter = new SseEmitter();

    // 调用 Service 层的 streamChat 方法
    objectProvider.stream().forEach(item -> {
        if (item.support(AiModelOptions.DEEP_SEEK)) {
            ExecutorService executor = Executors.newSingleThreadExecutor();
            executor.execute(() -> {
                item.streamChat(modelDTO, new EventSourceListener() {
                    @Override
                    public void onEvent(@NotNull EventSource eventSource, String id, String type, @NotNull String data) {
                        System.out.println(JSON.isValid(data));
                        System.out.println(data);
                        try {
                            emitter.send(SseEmitter.event()
                                    .name("message") // 事件名称(前端可通过 addEventListener 监听)
                                    .data(data));
                        } catch (IOException e) {
                            emitter.completeWithError(e);
                        } finally {
                            emitter.complete();
                            executor.shutdown();
                        }
                    }

                    @Override
                    public void onClosed(@NotNull EventSource eventSource) {
                        emitter.complete();
                    }

                    @Override
                    public void onFailure(@NotNull EventSource eventSource, Throwable t, okhttp3.Response response) {
                        emitter.completeWithError(t); // 处理失败
                    }
                });
            });

        }
    });

    return emitter;
}

后来改成这个可以了:

java 复制代码
@PostMapping(value = "/deep_seek/chat_stream")
public void deepSeekChatStream(
        @Validated @RequestBody DeepSeekQueryDTO deepSeekQueryDTO,
        HttpServletResponse httpServletResponse) throws IOException, InterruptedException {

    httpServletResponse.setContentType("text/event-stream");
    httpServletResponse.setCharacterEncoding("utf-8");

    DeepSeekModelDTO modelDTO = deepSeekModelConverter.queryDtoToDTO(deepSeekQueryDTO);
    modelDTO.setTemperature(0.7);
    modelDTO.setMaxTokens(2000);
    modelDTO.setTopP(1.0);
    modelDTO.setStream(Objects.nonNull(deepSeekQueryDTO.getStream()) ? deepSeekQueryDTO.getStream() : Boolean.FALSE);

    // 使用 CountDownLatch 同步流式输出完成
    CountDownLatch completionLatch = new CountDownLatch(1);
    PrintWriter pw = httpServletResponse.getWriter(); // 提前获取 Writer

    objectProvider.stream().forEach(item -> {
        if (item.support(AiModelOptions.DEEP_SEEK)) {
            item.streamChat(modelDTO, new EventSourceListener() {
                @Override
                public void onEvent(
                        @NotNull EventSource eventSource,
                        String id,
                        String type,
                        @NotNull String data) {
                    String sendData = "data: " + data + "\n\n";
                    pw.println(sendData);
                    pw.flush();
                }

                @Override
                public void onClosed(@NotNull EventSource eventSource) {
                    completionLatch.countDown(); // 流关闭时释放锁
                }

                @Override
                public void onFailure(
                        @NotNull EventSource eventSource,
                        Throwable t,
                        okhttp3.Response response) {
                    completionLatch.countDown(); // 出错时也释放锁
                    // 可选:记录错误或发送错误信息到客户端
                    try {
                        pw.println("data: [ERROR] " + t.getMessage() + "\n\n");
                        pw.flush();
                    } catch (Exception e) {
                        // 处理异常
                    }
                }
            });
        }
    });

    // 等待流完成(超时时间根据业务需求设置)
    completionLatch.await(1000, TimeUnit.SECONDS);
}
相关推荐
超级大只老咪7 小时前
数组相邻元素比较的循环条件(Java竞赛考点)
java
小浣熊熊熊熊熊熊熊丶7 小时前
《Effective Java》第25条:限制源文件为单个顶级类
java·开发语言·effective java
毕设源码-钟学长7 小时前
【开题答辩全过程】以 公交管理系统为例,包含答辩的问题和答案
java·eclipse
啃火龙果的兔子7 小时前
JDK 安装配置
java·开发语言
星哥说事7 小时前
应用程序监控:Java 与 Web 应用的实践
java·开发语言
派大鑫wink7 小时前
【JAVA学习日志】SpringBoot 参数配置:从基础到实战,解锁灵活配置新姿势
java·spring boot·后端
我是小路路呀7 小时前
element级联选择器:已选中一个二级节点,随后又点击了一个一级节点(仅浏览,未确认选择),此时下拉框失去焦点并关闭
javascript·vue.js·elementui
xUxIAOrUIII8 小时前
【Spring Boot】控制器Controller方法
java·spring boot·后端
Dolphin_Home8 小时前
从理论到实战:图结构在仓库关联业务中的落地(小白→中级,附完整代码)
java·spring boot·后端·spring cloud·database·广度优先·图搜索算法
醇氧8 小时前
org.jetbrains.annotations的@Nullable 学习
java·开发语言·学习·intellij-idea