Spring Boot + Vue 实现 DeepSeek 对话效果详细步骤
一、整体架构设计
我们需要构建一个前后端分离的应用:
- 后端:Spring Boot 提供 API 接口,处理与 AI 模型的交互
- 前端:Vue 实现聊天界面,展示对话内容并发送用户输入
二、后端实现 (Spring Boot)
1. 创建 Spring Boot 项目
xml
<!-- pom.xml 主要依赖 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 如果需要持久化存储 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
2. 设计 API 接口
less
// 对话控制器
@RestController
@RequestMapping("/api/chat")
public class ChatController {
@Autowired
private ChatService chatService;
@PostMapping("/message")
public ResponseEntity<ChatResponse> sendMessage(@RequestBody ChatRequest request) {
ChatResponse response = chatService.processMessage(request);
return ResponseEntity.ok(response);
}
@GetMapping("/history/{sessionId}")
public ResponseEntity<List<ChatMessage>> getHistory(@PathVariable String sessionId) {
List<ChatMessage> history = chatService.getChatHistory(sessionId);
return ResponseEntity.ok(history);
}
}
3. 定义数据传输对象
arduino
public class ChatRequest {
private String sessionId;
private String message;
// getter, setter
}
public class ChatResponse {
private String message;
private String sessionId;
// getter, setter
}
public class ChatMessage {
private String role; // "user" 或 "assistant"
private String content;
// getter, setter
}
4. 实现对话服务
typescript
@Service
public class ChatService {
// 可以使用 Map 临时存储会话,生产环境建议使用数据库
private Map<String, List<ChatMessage>> chatSessions = new ConcurrentHashMap<>();
// 处理用户消息
public ChatResponse processMessage(ChatRequest request) {
String sessionId = request.getSessionId();
if (sessionId == null || sessionId.isEmpty()) {
sessionId = UUID.randomUUID().toString();
}
String userMessage = request.getMessage();
// 保存用户消息
ChatMessage userMsg = new ChatMessage("user", userMessage);
// 调用 AI 模型 API
String aiResponse = callAIApi(userMessage, sessionId);
// 保存 AI 回复
ChatMessage aiMsg = new ChatMessage("assistant", aiResponse);
// 更新会话历史
chatSessions.computeIfAbsent(sessionId, k -> new ArrayList<>()).add(userMsg);
chatSessions.get(sessionId).add(aiMsg);
return new ChatResponse(aiResponse, sessionId);
}
// 获取聊天历史
public List<ChatMessage> getChatHistory(String sessionId) {
return chatSessions.getOrDefault(sessionId, Collections.emptyList());
}
// 调用 AI 模型 API 的方法
private String callAIApi(String message, String sessionId) {
// 这里实现与 DeepSeek API 的实际交互
// 可以使用 RestTemplate 或 WebClient
// 示例代码:
try {
// 实际开发中替换为真实 API 调用
return "这是 AI 对 "" + message + "" 的回复";
} catch (Exception e) {
return "抱歉,我遇到了一些问题,请稍后再试。";
}
}
}
5. 配置 CORS
typescript
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:5173") // Vue 默认端口
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true);
}
}
三、前端实现 (Vue)
1. 创建 Vue 项目
perl
# 使用 npm
npm create vue@latest my-chat-app
cd my-chat-app
npm install
# 安装必要的依赖
npm install axios
2. 创建聊天组件
xml
<!-- ChatWindow.vue -->
<template>
<div class="chat-container">
<div class="chat-header">
<h2>AI 助手对话</h2>
</div>
<div class="chat-messages" ref="messageContainer">
<div v-for="(msg, index) in messages" :key="index"
:class="['message', msg.role === 'user' ? 'user-message' : 'assistant-message']">
<div class="message-content">
{{ msg.content }}
</div>
</div>
<div v-if="loading" class="message assistant-message">
<div class="message-content">
<span class="loading-dots">思考中<span>.</span><span>.</span><span>.</span></span>
</div>
</div>
</div>
<div class="chat-input">
<textarea
v-model="userInput"
placeholder="请输入您的问题..."
@keyup.enter.ctrl="sendMessage"
></textarea>
<button @click="sendMessage" :disabled="loading || !userInput.trim()">
发送
</button>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'ChatWindow',
data() {
return {
messages: [],
userInput: '',
loading: false,
sessionId: this.generateSessionId(),
apiBaseUrl: 'http://localhost:8080/api/chat'
};
},
methods: {
generateSessionId() {
return Math.random().toString(36).substring(2, 15);
},
async sendMessage() {
if (!this.userInput.trim() || this.loading) return;
const userMessage = this.userInput.trim();
this.messages.push({ role: 'user', content: userMessage });
this.userInput = '';
this.loading = true;
// 滚动到底部
this.$nextTick(() => {
this.scrollToBottom();
});
try {
const response = await axios.post(`${this.apiBaseUrl}/message`, {
sessionId: this.sessionId,
message: userMessage
});
this.messages.push({ role: 'assistant', content: response.data.message });
this.sessionId = response.data.sessionId;
} catch (error) {
console.error('Error sending message:', error);
this.messages.push({
role: 'assistant',
content: '抱歉,发生了错误,请稍后再试。'
});
} finally {
this.loading = false;
this.$nextTick(() => {
this.scrollToBottom();
});
}
},
scrollToBottom() {
if (this.$refs.messageContainer) {
this.$refs.messageContainer.scrollTop = this.$refs.messageContainer.scrollHeight;
}
},
loadHistory() {
axios.get(`${this.apiBaseUrl}/history/${this.sessionId}`)
.then(response => {
this.messages = response.data;
this.$nextTick(() => {
this.scrollToBottom();
});
})
.catch(error => {
console.error('Error loading history:', error);
});
}
},
mounted() {
// 在实际应用中,可以从 URL 参数或本地存储获取 sessionId
// this.loadHistory();
}
}
</script>
<style scoped>
.chat-container {
display: flex;
flex-direction: column;
height: 100vh;
max-width: 800px;
margin: 0 auto;
padding: 1rem;
}
.chat-header {
text-align: center;
padding: 1rem 0;
border-bottom: 1px solid #eee;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.message {
max-width: 75%;
padding: 0.75rem;
border-radius: 0.5rem;
margin-bottom: 0.5rem;
}
.user-message {
align-self: flex-end;
background-color: #e1f5fe;
}
.assistant-message {
align-self: flex-start;
background-color: #f5f5f5;
}
.chat-input {
display: flex;
padding: 1rem 0;
gap: 0.5rem;
}
.chat-input textarea {
flex: 1;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 0.5rem;
resize: none;
min-height: 60px;
}
.chat-input button {
padding: 0.75rem 1.5rem;
background-color: #2196f3;
color: white;
border: none;
border-radius: 0.5rem;
cursor: pointer;
}
.chat-input button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.loading-dots span {
animation: loading 1.4s infinite;
display: inline-block;
}
.loading-dots span:nth-child(2) {
animation-delay: 0.2s;
}
.loading-dots span:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes loading {
0%, 100% { opacity: 0.3; }
50% { opacity: 1; }
}
</style>
3. 在主应用中使用聊天组件
xml
<!-- App.vue -->
<template>
<div class="app">
<ChatWindow />
</div>
</template>
<script>
import ChatWindow from './components/ChatWindow.vue'
export default {
name: 'App',
components: {
ChatWindow
}
}
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
.app {
width: 100%;
height: 100vh;
}
</style>
四、前后端集成与部署
1. 开发环境配置
-
启动后端服务
arduino./mvnw spring-boot:run
-
启动前端开发服务器
arduinonpm run dev
2. 生产环境部署
-
构建前端应用
arduinonpm run build
-
配置后端服务提供静态文件
java// Spring Boot 配置类 @Configuration public class WebConfig implements WebMvcConfigurer { @Value("${frontend.resources.path:${user.home}/my-chat-app/dist}") private Resource frontendResources; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**") .addResourceLocations("file:" + frontendResources.getFile().getAbsolutePath() + "/") .resourceChain(true) .addResolver(new PathResourceResolver() { @Override protected Resource getResource(String resourcePath, Resource location) throws IOException { Resource requestedResource = location.createRelative(resourcePath); return requestedResource.exists() && requestedResource.isReadable() ? requestedResource : new ClassPathResource("/static/index.html"); } }); } }
-
将前端构建文件复制到后端资源目录
五、进阶优化
1. WebSocket 实现实时通信
less
// 后端 WebSocket 配置
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*")
.withSockJS();
}
}
// WebSocket 控制器
@Controller
public class WebSocketController {
@Autowired
private ChatService chatService;
@MessageMapping("/chat.sendMessage")
@SendToUser("/queue/reply")
public ChatMessage sendMessage(@Payload ChatMessage chatMessage, Principal principal) {
return chatService.processWebSocketMessage(chatMessage);
}
}
2. 使用 Redis 缓存会话历史
less
@Configuration
@EnableRedisHttpSession
public class SessionConfig {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
}
3. 前端连接 WebSocket
kotlin
// 在 ChatWindow.vue 中添加 WebSocket 连接
export default {
// ... 其他代码 ...
data() {
return {
// ... 其他数据 ...
stompClient: null
};
},
methods: {
// ... 其他方法 ...
connectWebSocket() {
const socket = new SockJS(this.apiBaseUrl.replace('/api', ''));
this.stompClient = Stomp.over(socket);
this.stompClient.connect({}, frame => {
console.log('Connected: ' + frame);
this.stompClient.subscribe(`/user/queue/reply`, response => {
const receivedMessage = JSON.parse(response.body);
this.messages.push(receivedMessage);
this.$nextTick(() => {
this.scrollToBottom();
});
});
}, error => {
console.error('WebSocket Error: ' + error);
// 重连逻辑
setTimeout(() => {
this.connectWebSocket();
}, 5000);
});
},
disconnectWebSocket() {
if (this.stompClient !== null) {
this.stompClient.disconnect();
}
},
sendWebSocketMessage() {
if (!this.userInput.trim() || this.loading) return;
const userMessage = this.userInput.trim();
this.stompClient.send("/app/chat.sendMessage", {}, JSON.stringify({
sessionId: this.sessionId,
message: userMessage
}));
this.messages.push({ role: 'user', content: userMessage });
this.userInput = '';
}
},
mounted() {
// ... 其他代码 ...
this.connectWebSocket();
},
beforeUnmount() {
this.disconnectWebSocket();
}
}
总结
通过上述步骤,你可以实现一个基于 Spring Boot 和 Vue 的对话系统,类似 DeepSeek 的交互效果。这个实现包含了:
- 后端 API 设计与实现
- 前端聊天界面设计
- 会话管理与历史记录
- WebSocket 实现实时通信(可选)
- 部署配置
根据实际需求,你可以进一步扩展功能,如支持 Markdown 渲染、代码高亮、对话导出等高级特性。