基于Spring Boot + Vue 项目中引入deepseek方法

news2025/5/10 20:53:31

准备工作
在开始调用 DeepSeek API 之前,你需要完成以下准备工作:

1.访问 DeepSeek 官网,注册一个账号。

2.获取 API 密钥:登录 DeepSeek 平台,进入 API 管理 页面。创建一个新的 API 密钥(API Key),并妥善保存。

3.阅读 API 文档:

访问 DeepSeek 的 API 文档,了解支持的 API 端点、请求参数和返回格式


 开始调用                 

1.前端只需要简单的渲染页面渲染,将所需问题传递到后端,页面效果:

代码:

<template>
  
  <div class="total">
    <div class="chat-container">
      <div class="chat-header">
        <h1>快来和我聊天吧~~~</h1>
      </div>
      <div class="chat-messages" ref="messagesContainer">
        <div
          v-for="(message, index) in messages"
          :key="index"
          :class="['chat-message', message.sender]"
        >
          <div v-html="formatContent(message.content)"></div>
        </div>
      </div>
      <div class="chat-input">
        <input
          type="text"
          v-model="inputText"
          placeholder="输入消息..."
          @keydown.enter="sendMessage"
          :disabled="isLoading"
        />
        <button @click="sendMessage" :disabled="!canSend">
          <svg viewBox="0 0 24 24">
            <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
          </svg>
          发送
        </button>
      </div>
    </div>
  </div>
</template>

<script>
import { getToken } from "@/utils/storage.js";
const token = getToken();

export default {
  data() {
    return {
      inputText: "",
      messages: [],
      isLoading: false,
    };
  },
  computed: {
    canSend() {
      return this.inputText.trim() && !this.isLoading;
    },
  },
  methods: {
    formatContent(text) {
      // 基础Markdown转换
      return text
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
        .replace(/\*(.*?)\*/g, "<em>$1</em>")
        .replace(/`(.*?)`/g, "<code>$1</code>");
    },

    async sendMessage() {
      if (!this.canSend) return;

      const question = this.inputText.trim();
      this.inputText = "";

      // 添加用户消息
      this.messages.push({
        sender: "user",
        content: question,
      });

      // 添加初始bot消息
      const botMessage = {
        sender: "bot",
        content: "",
      };
      this.messages.push(botMessage);
      this.scrollToBottom();

      try {
        this.isLoading = true;

        const response = await fetch(
          "http://localhost:21090/api/online-travel-sys/v1.0/deepSeek/chat",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              token: token,
            },
            body: JSON.stringify({ question }),
          }
        );

        if (!response.ok || !response.body) {
          throw new Error("请求失败");
        }

        const reader = response.body.getReader();
        const decoder = new TextDecoder();
        let buffer = "";

        while (true) {
          const { done, value } = await reader.read();
          if (done) break;

          buffer += decoder.decode(value, { stream: true });

          // 处理SSE格式数据
          let position;
          while ((position = buffer.indexOf("\n\n")) >= 0) {
            const chunk = buffer.slice(0, position);
            buffer = buffer.slice(position + 2);

            const event = this.parseSSEEvent(chunk);
            if (event && event.data) {
              if (event.data === "[DONE]") {
                break; // 流结束
              }
              botMessage.content += event.data;
              this.scrollToBottom();
            }
          }
        }
      } catch (error) {
        botMessage.content = "请求出错,请稍后重试";
      } finally {
        this.isLoading = false;
        this.scrollToBottom();
      }
    },

    parseSSEEvent(chunk) {
      const lines = chunk.split("\n");
      const event = {};

      lines.forEach((line) => {
        if (line.startsWith("data:")) {
          event.data = line.replace(/^data:\s*/, "");
        }
      });

      return event;
    },

    scrollToBottom() {
      this.$nextTick(() => {
        const container = this.$refs.messagesContainer;
        if (container) {
          container.scrollTop = container.scrollHeight;
        }
      });
    },
  },
};
</script>



<style scoped>
/* 新增的样式 */
/* body 样式 */
html,
body {
  margin: 0;
  padding: 0;
  height: 100%; /* 确保高度占满整个视口 */
}

.total {
  width: 100%;
  height: 100%; /* 继承父容器的高度 */
  display: flex;
  justify-content: center;
  align-items: center;
  background-image: url("../../assets/img/seek.jpg");
  background-size: cover;
  background-position: center;
}
.chat-container {
  width: 100%;
  max-width: 800px;

  height: 75vh;
  background: rgba(255, 255, 255, 0.5);
  border-radius: 20px;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
  backdrop-filter: blur(10px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  margin: auto; /* 水平居中 */
  margin-top: 95px;
  margin-bottom: 20px;
}
.chat-header {
  padding: 24px;
  background: linear-gradient(135deg, #497bf1 0%, #4874ed 100%);
  color: white;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.chat-header h1 {
  margin: 0;
  font-size: 17px;
  font-weight: 400;
  letter-spacing: -0.5px;
}

.chat-messages {
  flex: 1;
  padding: 20px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.chat-message {
  max-width: 75%;
  padding: 16px 20px;
  border-radius: 20px;
  line-height: 1.5;
  animation: messageAppear 0.3s ease-out;
  position: relative;
  word-break: break-word;
}

.chat-message.user {
  background: linear-gradient(135deg, #5b8cff 0%, #3d6ef7 100%);
  color: white;
  align-self: flex-end;
  border-bottom-right-radius: 4px;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}

.chat-message.bot {
  background: linear-gradient(135deg, #f0f8ff 0%, #e6f3ff 100%);
  color: #2d3748;
  align-self: flex-start;
  border-bottom-left-radius: 4px;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}

.chat-input {
  padding: 20px;
  background: rgba(255, 255, 255, 0.9);
  border-top: 1px solid rgba(0, 0, 0, 0.05);
  display: flex;
  gap: 12px;
}

.chat-input input {
  flex: 1;
  padding: 14px 20px;
  border: 2px solid rgba(0, 0, 0, 0.1);
  border-radius: 16px;
  font-size: 1rem;
  transition: all 0.2s ease;
  background: rgba(255, 255, 255, 0.8);
}

.chat-input input:focus {
  outline: none;
  border-color: #5b8cff;
  box-shadow: 0 0 0 3px rgba(91, 140, 255, 0.2);
}

.chat-input button {
  padding: 12px 24px;
  border: none;
  border-radius: 16px;
  background: #5b8cff;
  color: white;
  font-size: 1rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s ease;
  display: flex;
  align-items: center;
  gap: 8px;
}

.chat-input button:hover:not(:disabled) {
  background: #406cff;
  transform: translateY(-1px);
}

.chat-input button:disabled {
  background: #c2d1ff;
  cursor: not-allowed;
  transform: none;
}

.chat-input button svg {
  width: 18px;
  height: 18px;
  fill: currentColor;
}

.typing-indicator {
  display: inline-flex;
  gap: 6px;
  padding: 12px 20px;
  background: linear-gradient(135deg, #f0f8ff 0%, #e6f3ff 100%);
  border-radius: 20px;
}

.typing-dot {
  width: 8px;
  height: 8px;
  background: rgba(0, 0, 0, 0.3);
  border-radius: 50%;
  animation: typing 1.4s infinite ease-in-out;
}

.typing-dot:nth-child(2) {
  animation-delay: 0.2s;
}

.typing-dot:nth-child(3) {
  animation-delay: 0.4s;
}

@keyframes messageAppear {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes typing {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-4px);
  }
}

@media (max-width: 640px) {
  .chat-container {
    height: 95vh;
    border-radius: 16px;
  }

  .chat-message {
    max-width: 85%;
  }
}

/* Markdown内容样式 */
.chat-message :deep(pre) {
  background: rgba(0, 0, 0, 0.05);
  padding: 12px;
  border-radius: 8px;
  overflow-x: auto;
  margin: 8px 0;
}

.chat-message :deep(code) {
  font-family: monospace;
  background: rgba(0, 0, 0, 0.08);
  padding: 2px 4px;
  border-radius: 4px;
}

.chat-message :deep(strong) {
  font-weight: 600;
}

.chat-message :deep(em) {
  font-style: italic;
}

.chat-message :deep(blockquote) {
  border-left: 3px solid #5b8cff;
  margin: 8px 0;
  padding-left: 12px;
  color: #666;
}

.chat-message :deep(a) {
  color: #5b8cff;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: all 0.2s;
}

.chat-message :deep(a:hover) {
  border-bottom-color: currentColor;
}
</style>

后端,这里并没有将历史记录保存到数据库,如果有需要根据实际情况自行添加:

1.在yml文件中添加相关配置

ds:
  key: 填写在官方自己申请的key,需要一定费用但很少
  url: https://api.deepseek.com/chat/completions

2.控制层:

package cn.kmbeast.controller;

import cn.kmbeast.service.DsChatService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import javax.annotation.Resource;

/**
 * DsController
 * @author senfel
 * @version 1.0
 * @date 2025/3/13 17:21
 */
@RestController
@RequestMapping("/deepSeek")
@Slf4j
public class DsController {
    @Resource
    private DsChatService dsChatService;

    /**
     * chat page
     * @param modelAndView
     * @author senfel
     * @date 2025/3/13 17:39
     * @return org.springframework.web.servlet.ModelAndView
     */
    @GetMapping()
    public ModelAndView chat(ModelAndView modelAndView) {
        modelAndView.setViewName("chat");
        return modelAndView;
    }

    /**
     * chat
     * @param question
     * @author senfel
     * @date 2025/3/13 17:39
     * @return org.springframework.web.servlet.mvc.method.annotation.SseEmitter
     */
    @PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter chat(@RequestBody String question) {
        //TODO 默认用户ID,实际场景从token获取
        String userId = "senfel";
        return dsChatService.chat(userId, question);
    }
}

3.service层:

package cn.kmbeast.service;

import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

/**
 * DsChatService
 * @author senfel
 * @version 1.0
 * @date 2025/4/13 17:30
 */
public interface DsChatService {

    /**
     * chat
     * @param userId
     * @param question
     * @author senfel
     * @date 2025/3/13 17:30
     * @return org.springframework.web.servlet.mvc.method.annotation.SseEmitter
     */
    SseEmitter chat(String userId, String question);
}

4.service实现:

package cn.kmbeast.service.impl;

import cn.kmbeast.service.DsChatService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * DsChatServiceImpl
 * @author senfel
 * @version 1.0
 * @date 2025/3/13 17:31
 */
@Service
@Slf4j
public class DsChatServiceImpl implements DsChatService {
    @Value("${ds.key}")
    private String dsKey;
    @Value("${ds.url}")
    private String dsUrl;
    // 用于保存每个用户的对话历史
    private final Map<Object, ArrayList<Map<String, String>>> sessionHistory = new ConcurrentHashMap<Object, ArrayList<Map<String, String>>>();
    private final ExecutorService executorService = Executors.newCachedThreadPool();
    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * chat
     * @param userId
     * @param question
     * @author senfel
     * @date 2025/3/13 17:36
     * @return org.springframework.web.servlet.mvc.method.annotation.SseEmitter
     */
    @Override
    public SseEmitter chat(String userId,String question) {
        SseEmitter emitter = new SseEmitter(-1L);
        executorService.execute(() -> {
            try {
                log.info("流式回答开始, 问题: {}", question);
                // 获取当前用户的对话历史
                ArrayList<Map<String, String>> messages = sessionHistory.getOrDefault(userId, new ArrayList<Map<String, String>>());
                // 添加用户的新问题到对话历史
                Map<String, String> userMessage = new HashMap<>();
                userMessage.put("role", "user");
                userMessage.put("content", question);
                Map<String, String> systemMessage = new HashMap<>();
                systemMessage.put("role", "system");
                systemMessage.put("content", "senfel的AI助手");
                messages.add(userMessage);
                messages.add(systemMessage);
                // 调用 DeepSeek API
                try (CloseableHttpClient client = HttpClients.createDefault()) {
                    HttpPost request = new HttpPost(dsUrl);
                    request.setHeader("Content-Type", "application/json");
                    request.setHeader("Authorization", "Bearer " + dsKey);
                    Map<String, Object> requestMap = new HashMap<>();
                    requestMap.put("model", "deepseek-chat");
                    requestMap.put("messages", messages);
                    requestMap.put("stream", true);
                    String requestBody = objectMapper.writeValueAsString(requestMap);
                    request.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8));
                    try (CloseableHttpResponse response = client.execute(request);
                         BufferedReader reader = new BufferedReader(
                                 new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8))) {
                        StringBuilder aiResponse = new StringBuilder();
                        String line;
                        while ((line = reader.readLine()) != null) {
                            if (line.startsWith("data: ")) {
                                System.err.println(line);
                                String jsonData = line.substring(6);
                                if ("[DONE]".equals(jsonData)) {
                                    break;
                                }
                                JsonNode node = objectMapper.readTree(jsonData);
                                String content = node.path("choices")
                                        .path(0)
                                        .path("delta")
                                        .path("content")
                                        .asText("");
                                if (!content.isEmpty()) {
                                    emitter.send(content);
                                    aiResponse.append(content); // 收集 AI 的回复
                                }
                            }
                        }

                        // 将 AI 的回复添加到对话历史
                        Map<String, String> aiMessage = new HashMap<>();
                        aiMessage.put("role", "assistant");
                        aiMessage.put("content", aiResponse.toString());
                        messages.add(aiMessage);
                        // 更新会话状态
                        sessionHistory.put(userId, messages);
                        log.info("流式回答结束, 问题: {}", question);
                        emitter.complete();
                    }
                } catch (Exception e) {
                    log.error("处理 DeepSeek 请求时发生错误", e);
                    emitter.completeWithError(e);
                }
            } catch (Exception e) {
                log.error("处理 DeepSeek 请求时发生错误", e);
                emitter.completeWithError(e);
            }
        });
        return emitter;
    }
}

最终效果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2372607.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

A2A与MCP定义下,User,Agent,api(tool)间的交互流程图

官方图&#xff1a; 流程图&#xff1a; #mermaid-svg-2smjE8VYydjtLH0p {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-2smjE8VYydjtLH0p .error-icon{fill:#552222;}#mermaid-svg-2smjE8VYydjtLH0p .error-tex…

蓝桥杯2025年第十六届省赛真题-水质检测

C语言代码&#xff1a; #include <stdio.h> #include <string.h>#define MAX_LEN 1000000int main() {char a[MAX_LEN 1], b[MAX_LEN 1];// 使用 scanf 读取字符数组scanf("%s", a);scanf("%s", b);int ans 0;int pre -1;int state -1;i…

[Windows] 东芝存储诊断工具1.30.8920(20170601)

[Windows] 东芝存储诊断工具 链接&#xff1a;https://pan.xunlei.com/s/VOPpMjGdWZOLceIjxLNiIsIEA1?pwduute# 适用型号 东芝消费类存储产品&#xff1a; 外置硬盘&#xff1a;Canvio 系列 内置硬盘&#xff1a;HDW****&#xff08;E300 / N300 / P300 / S300 / V300 / X30…

Linux网络编程day7 线程池and UDP

线程池 typedef struct{void*(*function)(void*); //函数指针&#xff0c;回调函数void*arg; //上面函数的参数 }threadpool_task_t; //各子线程任务的结构体/*描述线程池相关信息*/struct threadpool_t{pthread_mutex_t lock; …

ABB电机保护单元通过Profibus DP主站转Modbus TCP网关实现上位机通讯

ABB电机保护单元通过Profibus DP主站转Modbus TCP网关实现上位机通讯 在工业自动化领域&#xff0c;设备之间的通信至关重要。Profibus DP是一种广泛应用的现场总线标准&#xff0c;而Modbus TCP则是一种基于以太网的常见通信协议。将Profibus DP主站转换为Modbus TCP网关&…

迪士尼机器人BD-X 概况

这些机器人代表着迪士尼故事叙述与非凡创新的完美结合。它们不仅栩栩如生&#xff0c;还配备了先进的技术。 -迪士尼幻想工程研发部高级副总裁凯尔劳克林 幕景 BDX 机器人是由华特迪士尼公司的研究和幻想工程部门利用NVIDIA人工智能技术 (AI)开发的现实世界机器人&#xff0c;…

UE5骨骼插槽蓝图

首先在人物骨骼处添加插槽并命名&#xff0c;然后再选择添加预览资产把你要的模型&#xff08;静态网格体&#xff09;放上去。 选择绑定的骨骼再去右边相对位置、旋转等调整物体。 再去人物蓝图里面写就ok了

绘制拖拽html

<!DOCTYPE html> <html lang"zh-CN"> <head> <meta charset"UTF-8" /> <meta name"viewport" content"widthdevice-width, initial-scale1" /> <title>拖拽绘制矩形框 - 可移动可调整大小</ti…

ggplot2 | GO barplot with gene list

1. 效果图 2. 代码 数据是GO的输出结果&#xff0c;本文使用的是 metascape 输出的excel挑选的若干行。 # 1. 读取数据 datread.csv("E:\\research\\scPolyA-seq2\\GO-APA-Timepoint\\test.csv", sep"\t") head(dat)# 2. 选择所需要的列 dat.usedat[, c(…

系统思考:短期困境与长期收益

最近在项目中&#xff0c;一直有学员会提到一个议题&#xff0c;如何平衡当前困境和长期收益&#xff1f; 我的思考是在商业和人生的路上&#xff0c;我们常常听到“鱼和熊掌不可兼得”的说法&#xff0c;似乎短期利益和长期目标注定是对立的。但事实上&#xff0c;鱼与熊掌是…

Cjson格式解析与接入AI大模型

JSON格式的解析与构造 基本概念 JSON是JavaScript Object Notation的简称&#xff0c;中文含义为“JavaScript 对象表示法”&#xff0c;它是一种数据交换的文本格式&#xff0c;而不是一种编程语言。 JSON 是一种轻量级的数据交换格式&#xff0c;采用完全独立于编程语言的…

基于英特尔 RealSense D455 结构光相机实现裂缝尺寸以及深度测量

目录 一&#xff0c;相机参数规格 二&#xff0c;结合YOLO实例分割实现裂缝尺寸以及深度测量 2.1 应用场景 2.2 实现流程 2.3 效果展示 2.4 精度验证 2.5 实物裂缝尺寸以及深度测量效果展示 一&#xff0c;相机参数规格 英特尔 RealSense D455 是英特尔 RealSense D400 系…

Nacos源码—7.Nacos升级gRPC分析四

大纲 5.服务变动时如何通知订阅的客户端 6.微服务实例信息如何同步集群节点 6.微服务实例信息如何同步集群节点 (1)服务端处理服务注册时会发布一个ClientChangedEvent事件 (2)ClientChangedEvent事件的处理源码 (3)集群节点处理数据同步请求的源码 (1)服务端处理服务注册…

TIME - MoE 模型代码 3.2——Time-MoE-main/time_moe/datasets/time_moe_dataset.py

源码&#xff1a;GitHub - Time-MoE/Time-MoE: [ICLR 2025 Spotlight] Official implementation of "Time-MoE: Billion-Scale Time Series Foundation Models with Mixture of Experts" 这段代码定义了一个用于时间序列数据处理的 TimeMoEDataset 类&#xff0c;支…

【某OTA网站】phantom-token 1004

新版1004 phantom-token 请求头中包含phantom-token 定位到 window.signature 熟悉的vmp 和xhs一样 最新环境检测点 最新检测 canvas 下的 toDataURL方法较严 过程中 会用setAttribute给canvas 设置width height 从而使toDataURL返回不同的值 如果写死toDataURL的返回值…

OrangePi Zero 3学习笔记(Android篇)2 - 第一个C程序

目录 1. 创建项目文件夹 2. 创建c/cpp文件 3. 创建Android.mk/Android.bp文件 3.1 Android.mk 3.2 Android.bp 4. 编译 5. adb push 6. 打包到image中 在AOSP里面添加一个C或C程序&#xff0c;这个程序在Android中需要通过shell的方式运行。 1. 创建项目文件夹 首先需…

DeepResearch深度搜索实现方法调研

DeepResearch深度搜索实现方法调研 Deep Research 有三个核心能力 能力一&#xff1a;自主规划解决问题的搜索路径&#xff08;生成子问题&#xff0c;queries&#xff0c;检索&#xff09;能力二&#xff1a;在探索路径时动态调整搜索方向&#xff08;刘亦菲最好的一部电影是…

【论文阅读】基于客户端数据子空间主角度的聚类联邦学习分布相似性高效识别

Efficient distribution similarity identification in clustered federated learning via principal angles between client data subspaces -- 基于客户端数据子空间主角度的聚类联邦学习分布相似性高效识别 论文来源TLDR背景与问题两个子空间之间的主角&#xff08;Principa…

Elasticsearch知识汇总之ElasticSearch部署

五 ElasticSearch部署 部署Elasticsearch&#xff0c;可以在任何 Linux、MacOS 或 Windows 机器上运行 Elasticsearch。在Docker 容器 中运行 Elasticsearch 。使用Elastic Cloud on Kubernetes 设置和管理 Elasticsearch、Kibana、Elastic Agent 以及 Kubernetes 上的 Elasti…

ROBOVERSE:面向可扩展和可泛化机器人学习的统一平台、数据集和基准

25年4月来自UC Berkeley、北大、USC、UMich、UIUC、Stanford、CMU、UCLA 和 北京通用 AI 研究院&#xff08;BIGAI&#xff09;的论文“ROBOVERSE: Towards a Unified Platform, Dataset and Benchmark for Scalable and Generalizable Robot Learning”。 数据扩展和标准化评…