基于 AI Agent 驱动的智能旅行搭子平台,为自由行用户提供 AI 旅行规划、智能问答、景点 / 餐厅 / 酒店探索与收藏、行程规划、图片纪念墙。
| 层级 | 技术 |
|---|---|
| 前端 | Vue 3 + TypeScript + Vite + Vue Router + Pinia + Element Plus + AMap JS API |
| 后端 | NestJS + Fastify + TypeScript + Prisma |
| 数据库 | PostgreSQL 16 |
| 缓存/限流 | Redis 7 |
| 鉴权 | JWT (Access + Refresh Token), HttpOnly Cookie |
| AI 大模型 | OpenAI 兼容 API(流式响应 + SSE) |
| 外部数据 | 高德 POI(兜底检索) + 和风天气 |
| 部署 | Docker Compose |
TuLing/
├── client/ # Vue 3 前端
│ ├── e2e/ # Playwright E2E 测试
│ └── src/
│ ├── pages/
│ │ ├── login.vue / register.vue # 鉴权
│ │ ├── home.vue # 首页(旅行快照 + 热门目的地)
│ │ ├── chat.vue # 智能问答
│ │ ├── spots/ # 景点(列表 / 详情 / 评价中心)
│ │ ├── restaurants/ # 餐厅(列表 / 详情)
│ │ ├── hotels/ # 酒店(列表 / 详情)
│ │ ├── favorites.vue # 统一收藏中心(景点 / 美食 / 酒店)
│ │ ├── trips.vue # 行程规划(AMap + LLM 文案)
│ │ └── albums.vue # 图片纪念墙 + 相册集
│ ├── layouts/ # AppLayout, AuthLayout
│ ├── router/ # Vue Router + 路由守卫
│ ├── stores/ # Pinia auth store
│ └── shared/
│ ├── api/ # Axios 封装 + Token 刷新 + SSE 流式客户端
│ ├── components/ # NodeDetailDialog / RouteMapDialog
│ └── types/ # TypeScript 类型定义
├── server/ # NestJS 后端
│ ├── prisma/ # Prisma Schema + 数据迁移
│ ├── uploads/ # 用户上传图片落盘目录(运行时生成)
│ └── src/
│ ├── auth/ # AuthModule(注册/登录/登出/验证码/Token刷新)
│ ├── chat/ # ChatModule(会话管理/消息流/智能总结/推荐)
│ ├── user/ # UserModule
│ ├── spot/ # 景点(搜索 + 高德兜底 / 收藏 / 评价)
│ ├── restaurant/ # 餐厅(搜索 + 高德兜底 / 收藏 / 评价)
│ ├── hotel/ # 酒店(搜索 + 高德兜底 / 收藏 / 评价)
│ ├── trip/ # 行程(增删改查 + AI 文案)
│ ├── memory/ # 图片纪念墙(上传 / 列表 / 批量删除 / 移动)
│ ├── album/ # 相册集(增删查)
│ ├── weather/ # 和风天气代理
│ ├── prisma/ # PrismaModule(数据库服务)
│ ├── redis/ # RedisModule(缓存/验证码/限流)
│ ├── provider/ # ProviderModule(LLM 抽象层 + OpenAI 兼容适配器)
│ └── common/ # Guard, Decorator, Filter, Interceptor
├── scripts/ # 运维脚本(setup/start/stop/cleanup/prod)
├── uml/ # 用例图(PlantUML 源 + 渲染产物)
├── openspec/ # OpenSpec 规格文档
└── docker-compose.yml # PostgreSQL + Redis
- Node.js >= 20
- Docker Desktop
macOS / Linux:
bash scripts/setup.sh # 首次:安装依赖 + 初始化环境
bash scripts/start.sh # 启动所有服务(开发模式)Windows PowerShell:
.\scripts\setup.ps1 # 首次:安装依赖 + 初始化环境
.\scripts\start.ps1 # 启动所有服务(开发模式)启动后:
- 前端页面: http://localhost:5173
- 后端 API: http://localhost:3000
- 按
Ctrl+C停止服务
setup 脚本只完成依赖安装与数据库迁移,不会自动播种业务数据。如果不导入,景点 / 餐厅 / 酒店三个列表页都会是空的。首次启动后请执行一次:
macOS / Linux:
cd server && npm run seed:sampleWindows PowerShell:
cd server; npm run seed:sampleWindows CMD:
cd server && npm run seed:sample该脚本读取仓库内置的 server/prisma/sample-data.json,向景点 / 餐厅 / 酒店三张表各插入 25 条离线示例数据,不依赖任何外部 Key。脚本采用 name + city 幂等更新,重复执行不会产生脏数据。
后续如需用真实高德数据刷新这份样本,先在已有数据库环境中导入真实数据,再执行
npm run export:sample-data即可重新生成sample-data.json并提交。
数据准备好后,访问首页会先跳转到登录页。可以新注册账号,也可以直接用 demo 账号进入。
- 访问 http://localhost:5173/register
- 依次填:邮箱 → 点「获取验证码」(60 秒冷却) → 用户名(≥2 字) → 密码(≥8 位,含字母和数字) → 勾选同意协议
- 提交后自动登录并跳转首页
验证码怎么收?
-
若
server/.env配了SMTP_*:验证码会通过邮件发送 -
若未配置 SMTP(默认):验证码会打印在后端控制台,搜
[DEV] Captcha for即可,格式:[Nest] LOG [AuthService] [DEV] Captcha for foo@example.com: 482931验证码 5 分钟内有效。
执行一次 seed:reviews 即可自动建出 6 个 demo 账号(同时会插入 50 条评价数据,便于演示):
cd server && npm run seed:reviews此脚本要求数据库中至少已有景点数据,因此请确保先跑过
seed:sample。
可用账号(统一密码 demo1234):
| 邮箱 | 昵称 |
|---|---|
demo.amy@tuling.app |
旅行的Amy |
demo.luke@tuling.app |
背包客Luke |
demo.linlin@tuling.app |
林林爱拍照 |
demo.kai@tuling.app |
凯子在路上 |
demo.cici@tuling.app |
西西Travel |
demo.yibo@tuling.app |
一波看世界 |
注意:登录限流为 同 IP 连续失败 5 次 → 锁定 15 分钟,调试时请避免反复试错。
将前端构建产物与后端打包为单端口(3000)服务,适合部署:
macOS / Linux:
bash scripts/start-prod.shWindows PowerShell:
.\scripts\start-prod.ps1Windows CMD:
scripts\start-prod.bat启动后访问 http://localhost:3000 即可使用完整应用。
| 脚本 | macOS / Linux | Windows (PowerShell) | Windows (CMD) | 用途 |
|---|---|---|---|---|
| 一键部署 | bash scripts/setup.sh |
.\scripts\setup.ps1 |
scripts\setup.bat |
安装依赖、初始化 .env、迁移数据库 |
| 一键启动(开发) | bash scripts/start.sh |
.\scripts\start.ps1 |
scripts\start.bat |
拉起 Docker + 后端(dev) + 前端(dev) |
| 一键启动(生产) | bash scripts/start-prod.sh |
.\scripts\start-prod.ps1 |
scripts\start-prod.bat |
构建前端 + 后端生产模式(单端口 3000) |
| 一键停止 | bash scripts/stop.sh |
.\scripts\stop.ps1 |
scripts\stop.bat |
停止服务,保留所有数据 |
| 一键清理 | bash scripts/cleanup.sh |
.\scripts\cleanup.ps1 |
scripts\cleanup.bat |
停止服务 + 删除数据和产物(不可逆) |
setup脚本本身不会播种业务数据,需要的话请参考上方「导入示例数据」或下方「种子脚本一览」。
所有种子脚本都在 server/ 目录下执行:cd server && npm run <script>。
| 命令 | 用途 | 外部依赖 | 数据规模 |
|---|---|---|---|
seed:sample |
推荐:景点 / 餐厅 / 酒店各 25 条离线示例数据 | 无 | 75 条 |
export:sample-data |
读取当前数据库 Top 25 导出为 sample-data.json(用于刷新仓库样本) |
无 | — |
seed:spots |
全量内置景点(数据硬编码,但图片是失效占位 URL) | 无 | ~30 条 |
seed:wuhan |
武汉景点专项(含真实 POI / 图片) | 需要 AMAP_WEB_KEY |
武汉景点 |
seed:restaurants:wuhan |
武汉餐厅专项 | 需要 AMAP_WEB_KEY |
武汉餐厅 |
seed:hotels:wuhan |
武汉酒店专项 | 需要 AMAP_WEB_KEY |
武汉酒店 |
seed:reviews |
给当前数据库的景点批量补 50 条评价(缺用户时自动建 demo 账号) | 无(需先有景点) | 50 条评价 |
seed:refill-images |
给已有景点用高德 POI 照片补全 images 字段 |
需要 AMAP_WEB_KEY |
更新已有数据 |
日常评审 / 演示走
seed:sample即可;带wuhan/refill-images后缀的脚本是历史阶段性产物,需要自行准备高德 Web 服务 Key。
展开手动步骤
docker compose up -dcd server
cp .env.example .env
npm install
npx prisma migrate dev
npm run seed:sample各 seed 脚本的差异详见上方「种子脚本一览」。
seed:sample不依赖任何外部 Key,适合首次跑通。
cd server
npm run start:dev
# → http://localhost:3000cd client
npm install
npm run dev
# → http://localhost:5173参考 server/.env.example,关键配置项:
| 变量 | 说明 | 默认值 |
|---|---|---|
DATABASE_URL |
PostgreSQL 连接串 | postgresql://postgres:postgres@localhost:5432/tuling |
REDIS_HOST / REDIS_PORT |
Redis 连接 | localhost / 6379 |
JWT_ACCESS_SECRET |
JWT Access Token 密钥 | 需自行设置 |
JWT_REFRESH_SECRET |
JWT Refresh Token 密钥 | 需自行设置 |
CORS_ORIGINS |
允许的跨域来源(逗号分隔) | http://localhost:5173 |
LLM_API_URL |
LLM API 地址(OpenAI 兼容) | 需自行设置 |
LLM_API_KEY |
LLM API 密钥 | 需自行设置 |
LLM_MODEL |
LLM 模型名称 | gpt-3.5-turbo |
AMAP_KEY |
高德服务端 Key(POI 兜底检索) | 需自行设置 |
VITE_AMAP_KEY |
高德 JS API Key(前端地图) | 需自行设置(client/.env) |
QWEATHER_KEY |
和风天气 Key | 需自行设置 |
SMTP_* |
邮箱验证码 SMTP 配置 | 需自行设置 |
注意: 未配置 LLM / 高德 / 和风时,对应模块会降级或返回占位提示,不影响其余功能。
| 方法 | 路径 | 说明 | 鉴权 |
|---|---|---|---|
| POST | /api/auth/captcha |
发送邮箱验证码 | 公开 |
| POST | /api/auth/register |
注册新用户 | 公开 |
| POST | /api/auth/login |
账号密码登录 | 公开 |
| POST | /api/auth/logout |
退出登录 | 需登录 |
| POST | /api/auth/refresh |
刷新 Token | 公开 |
| GET | /api/auth/me |
获取当前用户信息 | 需登录 |
| 方法 | 路径 | 说明 | 鉴权 |
|---|---|---|---|
| POST | /api/chat/sessions |
创建新会话 | 需登录 |
| GET | /api/chat/sessions |
获取会话列表(分页) | 需登录 |
| GET | /api/chat/sessions/:id |
获取会话详情(含历史消息) | 需登录 |
| DELETE | /api/chat/sessions/:id |
删除会话 | 需登录 |
| POST | /api/chat/messages |
发送消息(SSE 流式响应) | 需登录 |
| POST | /api/chat/sessions/:id/summary |
生成会话智能总结(SSE) | 需登录 |
| GET | /api/chat/recommendations |
获取推荐提问列表 | 需登录 |
| 方法 | 路径 | 说明 | 鉴权 |
|---|---|---|---|
| GET | /api/spots |
搜索景点(keyword/city/tags/sort),无结果自动降级到高德 | 公开 |
| GET | /api/spots/:id |
景点详情 | 公开 |
| GET | /api/spots/favorites |
当前用户的收藏列表 | 需登录 |
| POST | /api/spots/:id/favorite |
收藏 | 需登录 |
| DELETE | /api/spots/:id/favorite |
取消收藏 | 需登录 |
| GET | /api/spots/:id/reviews |
景点评价列表 | 公开 |
| POST | /api/spots/:id/reviews |
提交评价 | 需登录 |
| GET | /api/spots/my-reviews |
我发布的评价 | 需登录 |
| DELETE | /api/spots/my-reviews/:reviewId |
删除自己的评价 | 需登录 |
| GET | /api/spots/favorites-reviews |
收藏景点的最新评价 | 需登录 |
| 方法 | 路径 | 说明 | 鉴权 |
|---|---|---|---|
| GET | /api/restaurants |
搜索餐厅(keyword/city/tags/cuisine) | 公开 |
| GET | /api/restaurants/:id |
餐厅详情 | 公开 |
| GET | /api/restaurants/favorites |
我收藏的餐厅 | 需登录 |
| POST | /api/restaurants/:id/favorite |
收藏 / 取消收藏 | 需登录 |
| DELETE | /api/restaurants/:id/favorite |
取消收藏 | 需登录 |
| GET | /api/restaurants/:id/reviews |
评价列表 | 公开 |
| POST | /api/restaurants/:id/reviews |
提交评价 | 需登录 |
| GET | /api/restaurants/my-reviews |
我的评价 | 需登录 |
| DELETE | /api/restaurants/my-reviews/:reviewId |
删除我的评价 | 需登录 |
| 方法 | 路径 | 说明 | 鉴权 |
|---|---|---|---|
| GET | /api/hotels |
搜索酒店(keyword/city/tags/starLevel) | 公开 |
| GET | /api/hotels/:id |
酒店详情 | 公开 |
| GET | /api/hotels/favorites |
我收藏的酒店 | 需登录 |
| POST | /api/hotels/:id/favorite |
收藏 | 需登录 |
| DELETE | /api/hotels/:id/favorite |
取消收藏 | 需登录 |
| GET | /api/hotels/:id/reviews |
评价列表 | 公开 |
| POST | /api/hotels/:id/reviews |
提交评价 | 需登录 |
| GET | /api/hotels/my-reviews |
我的评价 | 需登录 |
| DELETE | /api/hotels/my-reviews/:reviewId |
删除我的评价 | 需登录 |
| 方法 | 路径 | 说明 | 鉴权 |
|---|---|---|---|
| POST | /api/trips |
创建行程 | 需登录 |
| GET | /api/trips |
行程列表(分页) | 需登录 |
| GET | /api/trips/:id |
行程详情 | 需登录 |
| PUT | /api/trips/:id |
更新行程 | 需登录 |
| DELETE | /api/trips/:id |
删除行程 | 需登录 |
| POST | /api/trips/narrative |
AI 文案生成(按日叙述 / 节点点评) | 公开 |
| 方法 | 路径 | 说明 | 鉴权 |
|---|---|---|---|
| POST | /api/memories |
上传图片(multipart/form-data,支持 title/location/takenAt/albumId) | 需登录 |
| GET | /api/memories |
图片列表(支持 albumId=<uuid> 过滤指定相册,albumId=none 仅未分类) |
需登录 |
| DELETE | /api/memories/:id |
删除单张图片 | 需登录 |
| POST | /api/memories/bulk-delete |
批量删除(一次至多 100 张) | 需登录 |
| POST | /api/memories/move |
批量移动到相册(albumId: null 表示移到未分类) |
需登录 |
| 方法 | 路径 | 说明 | 鉴权 |
|---|---|---|---|
| GET | /api/albums |
我的相册列表(含封面 + 照片数) | 需登录 |
| POST | /api/albums |
新建相册集 | 需登录 |
| DELETE | /api/albums/:id |
删除相册(级联删除相册内所有图片) | 需登录 |
| 方法 | 路径 | 说明 | 鉴权 |
|---|---|---|---|
| GET | /api/weather/forecast |
多日天气预报(location / days) |
公开 |
- Access Token:15 分钟有效,承载于 HttpOnly Cookie
- Refresh Token:7 天有效,SHA-256 哈希存储,可服务端吊销
- 全局 Guard +
@Public()白名单模式 - 前端 Axios 拦截器自动刷新 Token(并发请求排队合并)
- 注册 / 登录(邮箱验证码、JWT 双 Token、退出登录、全局鉴权守卫)
- 首页快照(行程 / 收藏 / 相册 / 会话总数 + 热门目的地 + 旅行灵感)
- 智能问答(LLM 流式对话、SSE 实时推送、多轮会话管理、对话历史、智能总结、推荐提问)
- 景点探索(高德 POI 兜底检索 + 关键词降级模糊匹配 + 标签多选筛选 + 收藏 + 评价)
- 餐厅 / 酒店模块(同景点能力,含菜系 / 星级 / 价位筛选)
- 统一收藏中心(景点 / 美食 / 酒店一处管理,Tab 切换 + URL 参数同步)
- 行程规划(AMap 地图渲染 + 高德路径规划 + 景点 / 餐饮 / 住宿混合编排 + LLM 文案润色 + 天气信息)
- 图片纪念墙(多格式上传 + 按时间 / 相册查看 + 批量删除 / 移动到相册)
- 相册集(自建相册、相册级联删除磁盘文件、上传时归入指定相册)
- 天气服务(行程页天气卡片、和风天气代理)
- 网络部署(CORS 动态配置、Vite 局域网绑定、生产模式单端口托管、局域网 IP 展示)
- 运维脚本(一键部署 / 启动 / 停止 / 清理,支持 Bash / PowerShell / CMD 三种终端)
# E2E 测试(需要前后端运行中)
cd client && npx playwright test --config playwright.config.ts