内容
# 技术商店 AI 聊天机器人
一款基于文本转 SQL 的聊天机器人,理解关于商店数据的自然语言问题 — 产品、销售、收入、客户 — 并使用实时数据库查询回答问题。无需 SQL 知识。
使用 Next.js 16 · Spring Boot 3.5 · Spring AI 1.0 · GPT-4o · MCP Toolbox · PostgreSQL 16 · Docker Compose 构建。
---
## 目录
- [架构](#架构)
- [数据库设计](#数据库设计)
- [查询流程](#查询流程)
- [服务和端口](#服务和端口)
- [凭证](#凭证)
- [快速入门](#快速入门)
- [停止堆栈](#停止堆栈)
- [环境变量](#环境变量)
- [项目结构](#项目结构)
- [尝试事项](#尝试事项)
- [本地开发](#本地开发)
- [安全](#安全)
---
## 架构
```mermaid
graph TD
subgraph 浏览器
UI["Next.js 16 聊天界面<br/>localhost:3000"]
end
subgraph Docker 网络
FE["Next.js 服务器<br/>(API 代理路由)<br/>:3000"]
BE["Spring Boot 3.5<br/>+ Spring AI 1.0<br/>:8080"]
MCP["谷歌 MCP 工具箱<br/>SSE 端点<br/>:5000"]
PG[("PostgreSQL 16<br/>:5432")]
end
subgraph 外部
GPT["OpenAI API<br/>GPT-4o"]
end
UI -->|HTTP| FE
FE -->|/api/** 代理| BE
BE -->|聊天完成| GPT
BE -->|SSE / 工具调用| MCP
MCP -->|SQL – 只读角色| PG
BE -->|JPA – 聊天历史| PG
```
---
## 数据库设计
### store_db — 业务数据
```mermaid
erDiagram
category {
序列 id PK
字符串 名称
文本 描述
}
product {
序列 id PK
字符串 名称
文本 描述
字符串 SKU UK
整数 类别 ID FK
布尔型 是否活跃
时间戳 创建时间
}
price {
序列 id PK
整数 产品 ID FK
数值 金额
字符 货币
日期 生效开始
日期 生效结束 "NULL = 当前价格"
}
customer {
序列 id PK
字符串 姓
字符串 名
字符串 电子邮件 UK
字符串 电话
字符串 城市
字符串 国家
时间戳 创建时间
}
sale {
序列 id PK
整数 客户 ID FK
字符串 状态 "已完成 | 已取消"
数值 总金额
时间戳 创建时间
}
sale_item {
序列 id PK
整数 销售 ID FK
整数 产品 ID FK
整数 数量
数值 单位价格 "购买时的快照"
}
category ||--o{ product : "有"
product ||--o{ price : "定价通过"
product ||--o{ sale_item : "出现在"
customer ||--o{ sale : "下订单"
sale ||--o{ sale_item : "包含"
```
两点值得注意:`effective_to IS NULL` 表示当前活动价格,收入查询应始终过滤 `sale.status = 'completed'` 以排除已取消的订单。
### chat_db — 会话历史
```mermaid
erDiagram
conversation {
大序列 id PK
字符串 标题
时间戳 创建时间
时间戳 更新时间
}
chat_message {
大序列 id PK
大整数 会话 ID FK
字符串 角色 "用户 | 助手"
文本 内容
时间戳 创建时间
}
conversation ||--o{ chat_message : "包含"
```
---
## 查询流程
```mermaid
sequenceDiagram
actor 用户
participant UI as Next.js 界面
participant BE as Spring Boot
participant GPT as GPT-4o
participant MCP as MCP 工具箱
participant DB as PostgreSQL (store_db)
用户->>UI: "什么是按收入排名前 3 的产品?"
UI->>BE: POST /api/chat { 会话 ID, 消息 }
BE->>BE: 从 chat_db 加载聊天历史
BE->>GPT: 系统提示 + 历史记录 + 用户消息
GPT->>BE: 工具调用 → 获取数据库模式
BE->>MCP: 获取数据库模式
MCP->>DB: 从 information_schema + pg_description 选择
DB-->>MCP: 带有 COMMENT 元数据的完整模式
MCP-->>BE: 模式结果
BE-->>GPT: 模式结果
GPT->>BE: 工具调用 → 执行 SQL(SELECT ...)
BE->>MCP: 执行 SQL
MCP->>DB: 选择(只读角色 — 在数据库级别阻止 DDL/DML)
DB-->>MCP: 查询结果
MCP-->>BE: 结果行
BE-->>GPT: 结果行
GPT-->>BE: 自然语言答案(Markdown)
BE->>BE: 在 chat_db 中持久化用户 + 助手消息
BE-->>UI: { 回复, 会话 ID }
UI-->>用户: 渲染的 Markdown 响应
```
---
## 服务和端口
| 服务 | 网址 | 端口 |
|---------|-----|------|
| 聊天界面 | [http://localhost:3000](http://localhost:3000) | 3000 |
| 后端 API | [http://localhost:8080/api/conversations](http://localhost:8080/api/conversations) | 8080 |
| MCP 工具箱 SSE | [http://localhost:5000/mcp/store-tools/sse](http://localhost:5000/mcp/store-tools/sse) | 5000 |
| PostgreSQL | `localhost:5432` | 5432 |
所有四个端口都映射到主机上,因此您可以使用任何 DB 客户端或直接访问 REST API。
---
## 凭证
`.env.example` 中的默认值 — 适用于本地开发,在共享或部署前请更改。
**管理员用户** (Spring Boot + 配置)
| | |
|-|-|
| 主机 | `localhost:5432` |
| 用户 | `appuser` |
| 密码 | `apppassword` |
| 数据库 | `store_db`, `chat_db` |
**只读用户** (仅限 MCP 工具箱)
| | |
|-|-|
| 主机 | `localhost:5432` |
| 用户 | `mcp_readonly` |
| 密码 | `mcp_readonly_pass` |
| 数据库 | `store_db` |
| 权限 | 仅 SELECT — 在数据库级别阻止写入 |
**OpenAI** — 您需要提供自己的 API 密钥 (`sk-…`)。模型为 `gpt-4o`。
要使用 DBeaver、TablePlus 或类似工具连接:
```
主机: localhost
端口: 5432
数据库: store_db
用户: appuser
密码: apppassword
SSL: 禁用
```
---
## 快速入门
您只需要 Docker Desktop 和 OpenAI API 密钥。不需要在本地安装 Java 或 Node.js。
```bash
git clone https://github.com/<您的用户名>/db-agent.git
cd db-agent
cp .env.example .env
# 打开 .env 并设置 OPENAI_API_KEY=sk-您的密钥-此处
docker compose up --build
```
第一次构建需要 3–5 分钟,因为 Maven 和 npm 下载依赖项。之后,缓存层使重启速度加快。
打开 [http://localhost:3000](http://localhost:3000) 并开始提问。
要在后台运行:`docker compose up --build -d`
检查状态:`docker compose ps`
关注特定服务的日志:
```bash
docker compose logs -f backend
docker compose logs -f frontend
docker compose logs -f mcp-toolbox
docker compose logs -f postgres
```
---
## 停止堆栈
```bash
# 停止容器,保持数据库数据
docker compose down
# 停止容器并删除所有数据(种子数据在下次启动时重新插入)
docker compose down -v
# 重启单个服务,代码更改后
docker compose up --build backend
```
---
## 环境变量
| 变量 | 默认值 | 描述 |
|----------|---------|-------------|
| `POSTGRES_USER` | `appuser` | PostgreSQL 管理用户 |
| `POSTGRES_PASSWORD` | `apppassword` | 管理密码 |
| `POSTGRES_STORE_DB` | `store_db` | 业务数据数据库 |
| `POSTGRES_CHAT_DB` | `chat_db` | 聊天历史数据库 |
| `MCP_READONLY_USER` | `mcp_readonly` | MCP 工具箱的只读角色 |
| `MCP_READONLY_PASSWORD` | `mcp_readonly_pass` | 只读角色密码 |
| `OPENAI_API_KEY` | *(必填)* | 您的 OpenAI API 密钥 |
| `BACKEND_PORT` | `8080` | Spring Boot 的主机端口 |
| `FRONTEND_CORS_ORIGIN` | `http://localhost:3000` | 允许的 CORS 来源 |
| `BACKEND_URL` | `http://backend:8080` | 前端容器中使用的后端 URL |
---
## 项目结构
```
db-agent/
├── .env.example
├── docker-compose.yml
│
├── database/
│ ├── store_db/
│ │ └── init.sql # DDL + 种子数据 + mcp_readonly 角色 + COMMENT ON 元数据
│ └── chat_db/
│ └── init.sql # conversation + chat_message 表
│
├── mcp-toolbox/
│ └── tools.yaml # MCP 源 + 3 个工具 + 工具集定义
│
├── backend/
│ └── agent-store-chat/ # Spring Boot 3.5 / Spring AI 1.0
│ ├── Dockerfile
│ ├── pom.xml
│ └── src/main/
│ ├── resources/application.yml
│ └── java/com/example/storechat/
│ ├── StoreChatApplication.java
│ ├── config/AiConfig.java
│ ├── entity/
│ ├── repository/
│ ├── dto/
│ ├── service/
│ │ ├── AgentService.java # AI 循环 + 系统提示
│ │ └── ChatHistoryService.java
│ └── controller/
│ ├── ChatController.java
│ ├── ConversationController.java
│ └── GlobalExceptionHandler.java
│
└── frontend/
└── chat-ui/ # Next.js 16, TypeScript, Tailwind CSS
├── Dockerfile
├── package.json
└── src/
├── app/
│ ├── api/[...path]/route.ts # 代理 → Spring Boot
│ ├── chat/[conversationId]/page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── components/
│ ├── sidebar/Sidebar.tsx
│ └── chat/
│ ├── ChatWindow.tsx
│ ├── MessageBubble.tsx
│ └── ChatInput.tsx
├── lib/
│ ├── api.ts
│ └── utils.ts
└── types/index.ts
```
---
## 尝试事项
- "什么是按收入排名前 3 的最佳-selling 产品?"
- "TechShop 在 2024 年第四季度总共赚了多少收入?"
- "哪位客户花费的总金额最多?"
- "UltraBook Pro 15 的当前价格是多少?"
- "每种电子产品售出了多少单位?"
- "列出所有已取消的订单,包含客户姓名和金额。"
- "按订单数量排名前 5 的客户是谁?"
---
## 本地开发
如果您想在不重建 Docker 镜像的情况下进行迭代:
**后端** (需要 Java 21 + Maven)
```bash
cd backend/agent-store-chat
export SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/chat_db
export SPRING_DATASOURCE_USERNAME=appuser
export SPRING_DATASOURCE_PASSWORD=apppassword
export SPRING_AI_OPENAI_API_KEY=sk-...
export MCP_TOOLBOX_URL=http://localhost:5000
mvn spring-boot:run
```
**前端** (需要 Node.js 20+)
```bash
cd frontend/chat-ui
echo "BACKEND_URL=http://localhost:8080" > .env.local
npm install
npm run dev
```
---
## 安全
LLM 无法写入或删除数据。三个独立的层来强制执行此操作:
```
第 1 层 — 系统提示
└─ LLM 被指示编写仅限 SELECT 的语句
第 2 层 — MCP 工具描述
└─ execute-sql 明确列出禁止操作
(INSERT, UPDATE, DELETE, TRUNCATE, DROP, ALTER, CREATE, GRANT, REVOKE)
第 3 层 — PostgreSQL 角色
└─ mcp_readonly 仅具有 CONNECT + SELECT
任何写入或 DDL 操作都在数据库级别被拒绝
无论 LLM 生成什么
```
---
MIT 许可证