点餐系统装上了「DeepSeek 大脑」
基于 Spring AI + PgVector 的 RAG 落地指南
大家好,我是 Sail。
pgsql 版本的 AI 点餐系统 已经完成,代码也已经提交到仓库 🎉 目前支持:
- 🧠 本地 DeepSeek 1.5B 模型(960 显卡也能跑)
- ☁️ DeepSeek API 云端模式
前段时间我开源了 Sail-Food 扬帆点餐系统(基础版),收到了不少反馈:
- 👍 界面丝滑、部署简单
- 🎓 非常适合 毕设 / 商用 / 二次开发
- 🏪 有同学建议:搞个多商户版本
👉 多商户后面会看时间考虑补一个 PostgreSQL 版本
为什么写这篇文章?
今天不聊“AI 概念”, 而是 完整复盘一次:Java 生态下,RAG(检索增强生成)是如何真正落地的。
✅ 不是 Demo
✅ 不是 PPT 架构
✅ 是已经跑在真实业务里的 AI 点餐系统
💡 架构设计:为什么选 PostgreSQL,而不是 Milvus?
在设计 AI 功能时,我首先面临一个问题:
向量数据,存哪?
常见方案
text
MySQL(业务数据)
+ Milvus / Elasticsearch(向量)我最终的选择
👉 PostgreSQL All-in-One
架构师视角的思考
- ❌ 引入 Milvus = 新组件 + 新运维成本
- ❌ 小项目 / 中小系统 性价比极低
- ❌ 数据一致性、备份、迁移复杂
而 PostgreSQL:
- ✅ 原生
pgvector - ✅ 事务 + 向量一体化
- ✅ Java 生态支持成熟
- ✅ 一个数据库,解决所有问题
结论:
👉 业务规模没到“必须分库”的程度,PostgreSQL 是 RAG 的最优解
🧠 最终系统架构图
mermaid
graph LR
User[用户提问] --> App[后端 SpringBoot]
App --> DeepSeek_1.5B[意图识别<br/>(本地 / 云端)]
DeepSeek_1.5B -- 是点餐 --> OrderService[下单逻辑]
DeepSeek_1.5B -- 是咨询 --> RAG[RAG 检索服务]
RAG -- 语义检索 --> PG[PostgreSQL<br/>(pgvector)]
PG -- 返回相似菜品 --> RAG
RAG -- 组装 Prompt --> DeepSeek_V3[DeepSeek V3 API]
DeepSeek_V3 -- 生成回答 --> User🛠️ 核心代码实战
1️⃣ 依赖引入(Maven)
全面拥抱 Spring AI 官方生态 👇
xml
<!-- PGVector -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store</artifactId>
</dependency>
<!-- Ollama(本地模型) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
<!-- DeepSeek API(OpenAI 兼容) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>2️⃣ 数据向量化(Embedding)
我们需要把 菜品信息转成向量 存入数据库。
⚠️ 关键技巧:不要只存菜名!
要拼装成一段 “人话描述”。
java
@Async
@Override
public void syncAllProductsToVectorStore() {
List<WyProducts> products =
wyProductsMapper.selectWyProductsList(new WyProducts());
// 清空旧向量
jdbcTemplate.execute("DELETE FROM vector_store");
List<Document> documents = new ArrayList<>();
for (WyProducts p : products) {
String aiDesc = String.format(
"菜名:%s,价格:%s元,分类:%s,介绍:%s",
p.getProductName(),
p.getPrice(),
p.getWyProductCategories().getCategoryName(),
p.getDescription() == null ? "暂无详细介绍" : p.getDescription()
);
Map<String, Object> metadata = Map.of(
"id", p.getProductId(),
"price", p.getPrice()
);
documents.add(new Document(aiDesc, metadata));
}
// 写入向量库
vectorStore.add(documents);
log.info("AI 数据同步完成,共 {} 条", documents.size());
}3️⃣ 智能问答实现(Intent + RAG)
核心思路:
- 先让 AI 判断:是不是在点餐
- 如果能解析出 订单 JSON → 直接下单
- 否则 → 走 RAG 问答流程
java
public OrderIntentDTO parseOrder(String userMessage) {
PromptTemplate promptTemplate =
new PromptTemplate(INTENT_PROMPT);
Prompt prompt = promptTemplate.create(
Map.of("user_input", userMessage)
);
String jsonResult = chatClient.prompt(prompt)
.options(ChatOptionsBuilder.builder()
.withTemperature(0.0)
.build())
.call()
.content();
try {
jsonResult = jsonResult
.replace("```json", "")
.replace("```", "")
.trim();
OrderIntentDTO intent =
JSON.parseObject(jsonResult, OrderIntentDTO.class);
// 防止把“抽象词”当成菜名
if ("ORDER".equals(intent.getType())
&& intent.getItems() != null) {
boolean isInvalid = intent.getItems().stream()
.anyMatch(item ->
INVALID_DISH_NAMES.contains(item.getName()));
if (isInvalid) {
intent.setType(IntentType.QA);
intent.setItems(null);
}
}
return intent;
} catch (Exception e) {
return null;
}
}📱 效果展示
场景一:AI 自动点餐
- 用户自然语言描述
- AI 自动识别菜品
- 直接加入购物车
用户只需要点击「结算」即可完成下单
场景二:AI 咨询菜品
- 「推荐点清淡的」
- 「适合小孩吃的菜」
- 「有没有辣的?」
👉 全部通过 RAG + PgVector 实时检索回答
🧵 总结
通过 PostgreSQL + Spring AI + DeepSeek:
- 🍽️ 点餐系统从「CRUD 工具」
- 🚀 进化成「真正懂用户的 AI 助手」
这不仅是功能升级,更是:
Java 开发者迈向 AI 全栈的一次完整实践
🔮 下一步计划
- ⏳ 快过年了,先歇一歇
- 🔥 年前补一个 Redis + Lua 库存扣减
- 🏪 后续考虑 多商户版本(PG 方案)
👇 源码获取
👉 Gitee: https://gitee.com/wyabsdai/sail-food
我是 Sail, 37 岁高级架构师。
不写代码就得去摆摊了 😂
👉 点个关注支持一下吧!