2027届毕设加分利器:用Spring AI接入DeepSeek大模型,打造智能问答模块

2027届的毕设季已经拉开序幕。如果你还在做一个"用户管理+增删改查+分页查询"的管理系统,答辩时大概率会被评委一句"创新点在哪里"问住。今年最大的变化是:大模型API的价格已经低到学生党也能承受,而Spring官方推出的Spring AI框架让接入过程缩短到了几十行代码。一个能对话、能理解上下文的智能问答模块,正在从"锦上添花"变成"毕设标配"。
这篇文章手把手带你用Spring Boot 3 + Spring AI 1.0接入DeepSeek大模型API,实现一个带流式输出(打字机效果)的智能问答模块。所有代码均可直接运行,看完就能动手。
一、为什么大模型API是今年毕设的最佳加分项
先看三组数据,理解今年的趋势变了什么。
2026年春节期间,国产AI大模型迎来史上最密集的发布潮,字节跳动推出豆包大模型2.0系列,DeepSeek V4系列也已完成多轮价格下调,API调用成本降至"白菜价"。具体到DeepSeek的定价:
| 模型版本 | 输入(缓存命中) | 输入(缓存未命中) | 输出 |
|---|---|---|---|
| DeepSeek-V4-Pro | 0.025元/百万tokens | 3元/百万tokens | 6元/百万tokens |
| DeepSeek-V3 | 2元/百万tokens | 2元/百万tokens | 8元/百万tokens |
什么概念?一次普通问答大约消耗2000 tokens,用V3模型的成本不到0.02元。整个毕设开发期间调用量再大,总花费也很难超过10块钱。这意味着成本不再是接入AI的障碍。
第二个变化在框架层面。Spring官方在2025年正式发布了Spring AI 1.0 GA版本,到2026年已迭代至1.0.2,提供了清晰的ChatClient和EmbeddingClient API,原生支持流式响应。以前自己拼HTTP请求、处理SSE解析的脏活累活,现在一个starter依赖就搞定了。
第三个变化在选题层面。各大高校的毕设选题指导中,"人工智能与机器学习"方向占比持续上升,深度学习、智能推荐、NLP情感分析等方向成为热门。但纯算法方向对数学和模型训练要求高,多数同学吃不消。而"业务系统+大模型API"这种组合,既保留了完整的工程开发量,又通过AI模块拿到了创新点,性价比最高。
一句话总结今年的选型逻辑:用工程能力搭骨架,用AI模块长灵魂。
二、技术选型:为什么是Spring AI + DeepSeek
大模型接入方案不止一种,先做一轮横向对比,帮你避开选型阶段的纠结。
| 方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| Spring AI + DeepSeek API | 官方框架、代码简洁、支持流式、成本极低 | 依赖网络、有API延迟 | 毕设首选,开发快、答辩好讲 |
| 直接HTTP调用大模型API | 无额外依赖、灵活 | 需手动处理SSE、代码冗余 | 想展示底层原理的同学 |
| Ollama本地部署DeepSeek-R1 | 无API费用、数据不出本地 | 需要GPU、部署复杂、答辩演示风险高 | 有显卡且想写"本地部署"亮点的同学 |
| 接入智谱清言/通义千问API | 国产模型、中文能力强 | API格式与OpenAI略有差异 | 对中文场景有特殊要求的项目 |
本文选择Spring AI + DeepSeek组合,理由有三:DeepSeek兼容OpenAI接口格式,Spring AI原生支持,零额外适配;价格全行业最低,学生无负担;模型效果在国产大模型中第一梯队,答辩时有说服力。
三、后端实战:Spring Boot 3接入DeepSeek流式问答
3.1 项目初始化
用Spring Initializr创建项目,选择JDK 21(LTS版本,也是当前推荐),勾选Spring Web和Lombok依赖。JDK 17也可以,但21的虚拟线程特性在毕设里也是个加分点。
3.2 添加Spring AI依赖
在pom.xml中添加Spring AI的BOM和OpenAI starter。DeepSeek兼容OpenAI接口协议,所以直接用OpenAI的starter即可:
<properties>
<java.version>21</java.version>
<spring-ai.version>1.0.2</spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring AI OpenAI starter,DeepSeek兼容此协议 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<!-- Spring Web,提供REST接口 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- WebFlux,流式响应需要Reactor的Flux -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
3.3 配置DeepSeek连接
在application.yml中配置DeepSeek的API地址和密钥。先去DeepSeek开放平台注册账号,创建API Key,复制下来填入配置:
spring:
ai:
openai:
api-key: sk-你的deepseek密钥
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat
temperature: 0.7
max-tokens: 2048
这里有个关键点:base-url必须改成DeepSeek的地址,默认值是OpenAI的官方地址,不改会报401。model填deepseek-chat(对话模型)或deepseek-reasoner(推理模型,带思维链)。
3.4 编写流式问答Controller
Spring AI 1.0的核心入口是ChatClient,通过Builder模式构建请求,调用.stream()方法返回Flux<ChatResponse>,实现逐字输出:
package com.example.aidemo.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import java.util.Map;
@RestController
@RequestMapping("/api/ai")
public class ChatController {
private final ChatClient chatClient;
// 通过构造器注入,Spring AI会自动配置ChatClient.Builder
public ChatController(ChatClient.Builder builder) {
// 预设系统提示词,约束模型角色和回答风格
this.chatClient = builder
.defaultSystem("你是一个智能学习助手,用简洁清晰的中文回答问题。"
+ "回答控制在300字以内,适当使用分点说明。")
.build();
}
/**
* 流式问答接口,前端通过SSE接收逐字输出
* 返回 text/event-stream 格式,前端用EventSource对接
*/
@GetMapping(value = "/chat/stream", produces = "text/event-stream;charset=UTF-8")
public Flux<String> chatStream(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.stream()
.content();
}
/**
* 普通问答接口,一次性返回完整回答
* 适合不需要打字机效果的后台接口
*/
@PostMapping("/chat")
public Map<String, String> chat(@RequestBody Map<String, String> body) {
String message = body.get("message");
String response = chatClient.prompt()
.user(message)
.call()
.content();
return Map.of("answer", response);
}
}
上面这段代码包含两个接口:/api/ai/chat/stream是流式接口,用GET + SSE方式逐字返回;/api/ai/chat是普通接口,POST请求一次性返回完整回答。流式接口用了.stream().content(),返回的Flux<String>会被Spring WebFlux自动转成SSE事件流。
启动项目后可以直接用浏览器测试流式接口:
http://localhost:8080/api/ai/chat/stream?message=用一句话解释什么是RBAC权限控制
你会看到文字一个字一个字地出现,这就是流式输出的效果。
3.5 多轮对话上下文管理
单轮问答只能一问一答,缺乏上下文。毕设答辩时评委一定会问"能不能连续对话"。用Spring AI的Advisor机制可以实现多轮上下文:
package com.example.aidemo.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/api/ai")
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
// 创建内存级对话记忆,保留最近20条消息
InMemoryChatMemory memory = new InMemoryChatMemory();
this.chatClient = builder
.defaultSystem("你是一个智能学习助手,用简洁清晰的中文回答问题。")
.defaultAdvisors(MessageChatMemoryAdvisor.builder(memory).build())
.build();
}
/**
* 带会话ID的多轮流式对话
* conversationId用于区分不同用户的对话
*/
@GetMapping(value = "/chat/stream", produces = "text/event-stream;charset=UTF-8")
public Flux<String> chatStream(
@RequestParam String message,
@RequestParam(defaultValue = "default") String conversationId) {
return chatClient.prompt()
.user(message)
.advisors(a -> a.param("chat_memory_conversation_id", conversationId))
.stream()
.content();
}
}
MessageChatMemoryAdvisor会自动把历史消息注入到请求中,conversationId用于区分不同用户的对话。前端每次请求带上同一个conversationId,模型就能"记住"之前的对话内容。注意InMemoryChatMemory是存内存的,重启就没了,如果毕设需要持久化对话记录,可以自己实现ChatMemory接口把记录存到MySQL。
四、前端对接:Vue 3实现打字机效果
后端有了SSE接口,前端用Vue 3 + EventSource对接,实现经典的"打字机"效果。
<template>
<div class="chat-container">
<!-- 消息列表 -->
<div class="messages" ref="msgBox">
<div v-for="(msg, i) in messages" :key="i" :class="['msg', msg.role]">
<span class="role">{{ msg.role === 'user' ? '我' : 'AI' }}</span>
<p>{{ msg.content }}</p>
</div>
</div>
<!-- 输入框 -->
<div class="input-area">
<input
v-model="input"
@keyup.enter="send"
placeholder="输入你的问题..."
:disabled="loading"
/>
<button @click="send" :disabled="loading || !input">
{{ loading ? '回答中...' : '发送' }}
</button>
</div>
</div>
</template>
<script setup>
import { ref, reactive, nextTick } from 'vue'
const input = ref('')
const loading = ref(false)
const messages = ref([])
const msgBox = ref(null)
const send = async () => {
const text = input.value.trim()
if (!text || loading.value) return
// 添加用户消息
messages.value.push({ role: 'user', content: text })
input.value = ''
loading.value = true
// 添加AI消息占位,后续逐字填充
const aiMsg = reactive({ role: 'assistant', content: '' })
messages.value.push(aiMsg)
// 用EventSource连接SSE流式接口
const url = `/api/ai/chat/stream?message=${encodeURIComponent(text)}`
const eventSource = new EventSource(url)
eventSource.onmessage = (event) => {
// 每收到一个字符片段,追加到AI消息中
aiMsg.content += event.data
scrollToBottom()
}
eventSource.onerror = () => {
eventSource.close()
loading.value = false
}
eventSource.addEventListener('complete', () => {
eventSource.close()
loading.value = false
})
}
const scrollToBottom = async () => {
await nextTick()
if (msgBox.value) {
msgBox.value.scrollTop = msgBox.value.scrollHeight
}
}
</script>
<style scoped>
.chat-container { max-width: 680px; margin: 0 auto; padding: 20px; }
.messages { height: 400px; overflow-y: auto; border: 1px solid #e0e0e0; border-radius: 8px; padding: 16px; }
.msg { margin-bottom: 16px; }
.msg.user { text-align: right; }
.msg .role { font-weight: bold; color: #409eff; margin-right: 6px; }
.msg p { display: inline; line-height: 1.6; }
.input-area { display: flex; gap: 8px; margin-top: 12px; }
.input-area input { flex: 1; padding: 8px 12px; border: 1px solid #dcdfe6; border-radius: 4px; }
.input-area button { padding: 8px 20px; background: #409eff; color: #fff; border: none; border-radius: 4px; cursor: pointer; }
</style>
注意前端用Vite开发时需要配置代理,把/api转发到后端localhost:8080,否则会有跨域问题。在vite.config.js中添加:
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
})
五、进阶:从单轮问答到知识库问答(RAG)
如果只做一个大模型对话接口,答辩时可能被问"你的模型知道你们学校的信息吗"。大模型的训练数据有截止日期,对特定领域知识一无所知。这时候就需要RAG(检索增强生成)——先从你的知识库中检索相关内容,再喂给大模型让它基于这些内容回答。
Spring AI提供了VectorStore接口和EmbeddingClient,整套RAG流程可以这样搭:
// 1. 文档向量化并存入向量库
@Configuration
public class RagConfig {
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
return PgVectorStore.builder(jdbcTemplate, embeddingModel)
.dimensions(1536)
.build();
}
}
// 2. 检索增强问答Controller
@RestController
@RequestMapping("/api/ai")
public class RagController {
private final ChatClient chatClient;
private final VectorStore vectorStore;
public RagController(ChatClient.Builder builder, VectorStore vectorStore) {
this.chatClient = builder.build();
this.vectorStore = vectorStore;
}
@PostMapping("/rag")
public Map<String, String> ragChat(@RequestBody Map<String, String> body) {
String question = body.get("question");
String answer = chatClient.prompt()
.user(question)
// 从向量库检索相关文档,自动注入上下文
.advisors(new QuestionAnswerAdvisor(vectorStore))
.call()
.content();
return Map.of("answer", answer);
}
}
这段代码的原理是:QuestionAnswerAdvisor在请求发出前,先用EmbeddingModel把用户问题转成向量,在向量库中检索最相似的文档片段,然后把这些片段拼接到用户的提问中,大模型就能"看着"你的资料来回答了。
毕设中可以这样做:把学校的规章制度、课程大纲、常见问题FAQ整理成文档,导入向量库,系统就能回答"怎么选课""毕业学分要求"这类特定问题。这个功能在答辩时的杀伤力远超普通问答。
六、答辩加分话术:如何讲好AI模块的故事
技术做完了,答辩讲不好等于白做。下面是三个高频问题的应答思路。
问题一:“你的AI模块和直接用ChatGPT有什么区别?”
回答重点放在"工程化封装"上:你做的是把大模型能力集成到了具体业务系统中,包括多轮对话上下文管理、会话持久化、业务数据联动、前端流式交互体验优化。大模型是原材料,你的系统是加工厂,价值在于加工过程而非原材料本身。如果做了RAG,再加一句"我解决了大模型对垂直领域知识不了解的问题"。
问题二:“大模型的回答不准确怎么办?”
展示你的工程思考:通过系统提示词约束回答范围和风格,通过temperature参数控制创造性(事实类问题调低到0.3,创意类问题调高到0.8),通过RAG引入领域知识保证回答有据可查,通过对话历史记录方便人工审核和纠错。这些都是在"调API"之外做的工程工作。
问题三:“这个模块的成本怎么控制?”
用数据说话:展示DeepSeek的定价表,说明开发期间总调用成本不到10元;提到错峰优惠时段(00:30-08:30)价格再降50%-75%;说明生产环境可以加缓存层,对高频问题直接返回缓存结果,避免重复调用。
七、动手清单与时间安排
把上面的内容拆成可执行的步骤,按以下节奏推进,两周内就能完成AI模块开发。
| 阶段 | 任务 | 预计耗时 | 产出 |
|---|---|---|---|
| 第1天 | 注册DeepSeek账号、获取API Key、创建Spring Boot项目 | 半天 | 项目骨架 |
| 第2-3天 | 接入Spring AI、实现单轮问答接口、Postman测试 | 1天 | 可调用的API |
| 第4-5天 | 实现流式输出、Vue前端对接打字机效果 | 1.5天 | 前后端联调完成 |
| 第6-7天 | 实现多轮对话、上下文持久化到MySQL | 1天 | 对话记录功能 |
| 第8-10天 | RAG知识库搭建、文档导入、检索问答 | 2-3天 | 知识库问答 |
| 第11-12天 | 联调测试、异常处理、界面美化 | 1-2天 | 可演示的完整模块 |
有几个容易踩的坑提前提醒:第一,API Key不要硬编码在代码里,用环境变量或配置文件管理,提交Git时加入.gitignore;第二,流式接口在Nginx反代时需要关闭缓冲,加proxy_buffering off配置,否则前端收不到逐字输出;第三,DeepSeek的免费额度有限,开发时先用简单问题测试,避免烧完额度。
结语
今年毕设的竞争逻辑已经变了。评委看过太多千篇一律的CRUD管理系统,一个能流式对话、能理解上下文、能基于知识库回答问题的AI模块,足以让你的项目在答辩现场脱颖而出。更重要的是,这套技术栈——Spring AI + 大模型API + 向量检索——正是当前企业级AI应用开发的主流方案,做完这个毕设,你对AI工程化的理解会超过大多数同龄人。
代码已经给你了,接下来就是动手。遇到问题可以到公众号留言,我会逐条回复。
关注博主,每日推送选题指导、技术栈教学、AI模块接入、论文写作和答辩技巧。
参考来源:
参考来源
2025-2026年计算机毕业设计选题推荐 -计算机专业毕业设计题目大全,CSDN
计算机专业毕设选题清单:开题指导与选题建议,CSDN
【保姆级教程】2026届计算机毕业设计实现流程,看这篇就够了(1.2w字超详细流程),CSDN
2026届Java毕业设计选题推荐,1000道创新创意Java毕业设计题目推荐,避免踩坑,CSDN
两万字计算机毕设答辩相关问题汇总附赠350套ppt
更多推荐

所有评论(0)