AI全栈这个说法,最近在 Java 和前端圈子里热度很高。但很多人把它理解成“会调用一个大模型接口就算 AI 全栈”,这是把问题想简单了。真正的 AI 全栈,至少要打通“前端交互、后端服务、模型调用、业务规则、部署运维”这一整条链路。想验证自己是否具备这个能力,最直接的方式就是亲手做一个完整项目。我在梳理 AI 旅游智能推荐助手这个项目时,发现它特别适合用来练这一套能力:后端用 Spring Boot,前端用 Vue3,中间接入大模型推荐能力,最后把项目部署成一个可以访问的系统。下面按实际落地顺序拆一遍。
1. AI 全栈到底在练什么:不是“多学一个框架”,而是打通一条链路
1.1 AI 全栈工程师和普通前后端开发的区别
很多招聘信息里会出现“AI 全栈工程师”“大模型全栈工程师”这类 title。不同公司叫法不一样,但核心工作通常不是训练模型,而是把模型能力接进业务系统。
普通前后端开发关心的是接口、CRUD、权限、页面交互。AI 全栈开发还要多关心三件事:
- 大模型接口怎么调用,提示词怎么设计。
- 模型返回结果怎么校验、降级、缓存。
- 模型能力怎么和现有业务规则结合,而不是让模型直接接管一切。
放到 AI 旅游智能推荐助手这个项目里,就是你要能解释清楚:用户填了城市、天数、预算和偏好后,系统是怎么从景点库里选出候选景点,怎么生成推荐理由,怎么安排行程,又怎么在接口异常时兜底返回。
1.2 旅游推荐助手为什么适合作为转型项目
这个项目不是随便抓来的 demo,它覆盖了 Java 后端、Vue3 前端、AI 接口调用和部署四块内容。
从后端角度看,有用户登录、景点管理、推荐记录、收藏订单这类标准 CRUD。从 AI 角度看,有偏好解析、候选召回、打分排序、提示词生成、行程规划。从前端角度看,有登录页、推荐表单、结果时间线、个人中心。从部署角度看,有后端打包、前端构建、Nginx 或 Docker 配置。
一句话总结:这是一个足够小、但链条完整的项目。比起一堆零散笔记,它能让你在面试时有一条完整的讲述线。
1.3 关于“百万年薪”先泼一盆冷水
项目标题写“学完直接挑战百万年薪”,这是吸引点击的说法。真正找工作的时候,面试官不会只看你有没有做过这个项目,更会看你有没有把项目讲清楚,能不能处理里面的异常和边界。
我的建议是:把这个项目当成练手和面试话题,而不是一张保底 offer。它最能帮你的地方,是让你在“Java 八股文”之外有一个可以讨论的实战样本。
2. 落地之前,先把技术栈和环境确认清楚
2.1 建议的技术栈组合
先列一组稳定且常见的组合,不要一上来就追求最新版本。最新版不一定不好,但资料少、坑多,尤其对转型者不友好。
| 模块 | 选型 | 说明 |
|---|---|---|
| 后端 | Java + Spring Boot | 用 Spring MVC 暴露 REST 接口 |
| 数据库 | MySQL | 保存用户、景点、推荐记录 |
| 缓存 | Redis | 缓存热门景点和重复推荐结果 |
| ORM | MyBatis-Plus 或 Spring Data JPA | 二选一即可,重点是别把 SQL 写在 Controller 里 |
| 前端 | Vue3 + Vite | 组合式 API,适合实践 defineProps、defineEmits |
| UI 组件 | Element Plus 或 Naive UI | 加快表单和结果页开发 |
| HTTP 客户端 | Axios | 前后端交互的标准方案 |
| 接口文档 | springdoc-openapi | 也就是 Swagger 风格的接口网页 |
| 部署 | Docker + Nginx | 前后端分离后最常用的部署方式 |
如果不想引入太重的前端组件库,也可以用原生 CSS 加组合式 API。重点是先跑通,再优化。
2.2 本地开发环境和最低配置
这个项目的核心不是训练模型,所以对 GPU 没有硬性要求。大模型部分可以走远程 API,本机只负责业务逻辑和调用。
本地建议准备这些环境:
- JDK 17 或 JDK 21。新版 Spring Boot 对 JDK 版本有要求,装太老会直接编译失败。
- Maven 3.8 以上,用来管理后端依赖和打包。
- Node.js 18 以上,用来启动 Vue3 开发服务器。
- MySQL 8.x,Redis 6.x 或 7.x。
- 一个可用的 IDE,IDEA 或 VS Code 都行。
如果你的机器内存只有 8G,先不要同时开 IDEA、MySQL、Redis、Vue dev server 和大模型相关工具。建议按顺序启动,关掉不用的软件。项目本身不重,但开发环境的资源占用很容易让人误判成项目问题。
2.3 第一次启动前的目录和端口规划
前后端分离项目最常见的坑,是两边各跑各的,最后前后端无法交互。
我建议在项目根目录下分两个子目录:
ai-travel-assistant/ ├── backend/ // Spring Boot 工程 ├── frontend/ // Vue3 工程 └── docs/ // 接口文档、部署文档、测试用例端口规划也要提前定好。后端用 8080,前端开发服务器用 5173,Nginx 部署后对外用 80。前端访问后端接口时,如果本地端口不同,就要处理跨域。
注意:先不要急着写大量业务代码,先把 Spring Boot 空工程和 Vue3 空工程都启动起来,确认端口正常,再开始加业务。
3. 后端:Spring Boot 把“推荐助手”拆成可测试的接口
3.1 先做基础数据和用户模块
推荐助手要跑起来,首先要有一个可用的景点库。不需要一开始就录入全国数据,我建议先放一个城市的基础数据,比如杭州的 20 个景点,字段包括:景点名称、城市、标签、评分、门票价格、建议游玩时间、地理位置、图片 URL。
这些数据可以直接用 SQL 脚本初始化,也可以在后台管理页面里录入。从项目完成度来看,做一个简单的后台接口会比纯 SQL 更有说服力。
用户模块可以做得克制一点:注册、登录、获取当前用户信息、修改偏好。不要为了炫技加一堆不落地的功能。登录方式用 JWT 或 Session 都可以,但你要能解释清楚 token 存在哪里、什么时候过期、怎么刷新。
3.2 推荐接口的输入和输出
推荐接口是这个项目的核心,我建议设计成这个样子:
POST /api/recommend请求体:
{ "city": "杭州", "days": 3, "budget": 2500, "tags": ["历史", "美食"], "pace": "适中", "companion": "朋友" }响应体:
{ "spots": [ { "name": "西湖", "reason": "适合朋友一起散步,周边餐饮选择多", "duration": "4小时", "cost": 0 } ], "route": [ { "day": 1, "spots": ["西湖", "河坊街", "南宋御街"] } ], "reason": "整体路线以老城区为主,兼顾历史街区和本地美食。", "budgetEstimate": 2300, "tips": ["提前预约西湖游船", "河坊街晚上人较多"] }接口设计的关键不是一次返回很多字段,而是让前端拿到后能直接渲染。比如“每日行程”固定为数组,前端就可以用 timeline 组件循环展示。
3.3 推荐逻辑:规则打分加大模型生成
这里要区分清楚:大模型负责什么,业务规则负责什么。
最稳妥的做法是先用规则引擎做候选推荐。比如用户选“历史”和“美食”,系统就对景点列表做打分:
- 标签匹配加 20 分。
- 评分在 4.5 以上加 10 分。
- 单日门票价格超过预算除以天数的扣分。
- 建议游玩时间过长或过短扣分。
- 景点之间距离太远扣分。
按分数排序后,取前 5 到 8 个景点作为候选。这些候选数据再交给大模型,让它生成推荐理由、每日行程和注意事项。
这样做有三个好处:
第一,不依赖大模型也不会完全跑偏。第二,候选结果可控,模型不会凭空编造不存在的景点。第三,接口哪怕是超时或模型解析失败,规则层还能兜底返回结构化结果。
大模型调用部分,可以用一个简单的提示词模板:
String prompt = """ 你是一位熟悉国内旅游的规划助手。 用户偏好:%s 候选景点:%s 请生成: 1. 推荐理由。 2. 每日行程,最多 %d 天。 3. 预算估计。 4. 注意事项。 要求以 JSON 格式输出,不要输出多余内容。 """.formatted(userPreferences, candidateSpots, days);注意,大模型并不是每次都会严格输出 JSON。所以后端一定要做结果解析校验,解析失败时降级到规则推荐,而不是直接报错。
3.4 接口安全:API Key 不能出现在前端
调用大模型最关键的一点,是 API Key 绝对不能放在 Vue3 代码里。前端代码打包后是静态文件,任何一个用户都能在开发者工具里看到请求内容,Key 一旦泄露就会被盗用。
正确做法是把模型调用放在后端:
- 后端配置环境变量保存 API Key。
- 前端只调用后端自己的
/api/recommend。 - 后端收到请求后,再去调用大模型接口。
- 前端永远感知不到模型地址和 Key。
这里可以再补一层:用户提交的内容不要直接拼进提示词,先做长度限制和内容过滤。否则很可能会出现超长输入、注入提示词、输出不符合预期的问题。
3.5 缓存、异步和日志:批量任务前必须想清楚
推荐接口不会像普通 CRUD 那么快,因为它涉及候选计算和模型调用。如果用户在短时间内重复提交相同请求,每次都去调大模型,不仅慢,还容易把模型调用的预算打爆。
我一般会这样做:
- 把用户的请求参数转成固定字符串,作为 Redis 缓存的 key。
- 缓存时间设置成 30 分钟或 1 小时。
- 如果用户请求相同,直接返回缓存。
- 对于批量离线推荐任务,使用异步线程池或消息队列处理,避免请求阻塞。
日志也要比普通项目多打一层。每次推荐请求要记录:用户 ID、城市、天数、预算、候选景点数量、模型返回耗时、是否走了降级逻辑。这些日志在排查问题上非常有用。
spring: datasource: url: jdbc:mysql://localhost:3306/ai_travel?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password data: redis: host: localhost port: 6379 ai: api-key: ${AI_API_KEY} api-url: ${AI_API_URL} model: ${AI_MODEL:default-model} timeout-seconds: 304. 前端:Vue3 不只做页面,还要把状态、交互和异常处理做完整
4.1 页面模块和路由设计
Vue3 前端不需要做得很复杂,但要清晰。建议至少包含这几个页面:
- 首页:展示推荐入口和热门城市。
- 登录注册页:用户名密码登录。
- 推荐表单页:城市、天数、预算、标签选择。
- 推荐结果页:展示推荐景点、每日路线、预算和时间线。
- 个人中心:查看收藏和最近推荐记录。
路由可以使用 Vue Router。页面状态建议用 Pinia 保存用户信息和最近一次推荐结果,避免页面跳转后数据丢失。
组件层面再做拆分,比如:
- RecommendForm:负责收集用户偏好。
- SpotCard:展示单个景点。
- RouteTimeline:展示每日行程。
- BudgetSummary:展示预算评估。
拆分组件不是为了好看,而是为了让你能讲清楚父子组件之间的数据流。Vue3 面试题里常问的 defineProps、defineEmits,在这个项目里都会用到。
4.2 用 Axios 对接后端,注意 loading 和错误状态
很多人写前端接口时只写了成功路径,忘记 loading 和错误状态。这样在演示时一旦模型接口超时,页面就一直转圈,体验很差。
推荐写法是封装一个统一 request:
// src/api/request.js import axios from 'axios' const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || '/api', timeout: 30000 }) request.interceptors.request.use((config) => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) request.interceptors.response.use( (response) => response.data, (error) => { if (error.response && error.response.status === 401) { window.location.href = '/login' } return Promise.reject(error) } ) export default request然后在推荐页里这样调用:
<script setup> import { ref } from 'vue' import { recommend } from '@/api/recommend' const loading = ref(false) const errorMessage = ref('') const form = ref({ city: '杭州', days: 3, budget: 2000, tags: ['历史', '美食'] }) async function handleSubmit() { if (!form.value.city) { errorMessage.value = '请先选择城市' return } loading.value = true errorMessage.value = '' try { result.value = await recommend(form.value) } catch (e) { errorMessage.value = '推荐服务暂时不可用,请稍后重试' } finally { loading.value = false } } </script>这里最关键的不是代码量,而是流程完整:校验输入、开启 loading、捕获错误、关闭 loading。很多前端新手漏掉 finally,导致出错后按钮一直禁用。
4.3 推荐结果展示和参数调整
推荐结果页不要只展示一个 JSON 字符串,要把它转成用户能看懂的组件。
比如每日路线用时间线组件展示:
- 第 1 天:西湖、河坊街、南宋御街。
- 第 2 天:灵隐寺、龙井村。
- 第 3 天:西溪湿地、返程。
每个景点卡片里显示推荐理由、建议游玩时间、门票价格。预算部分用进度条展示,让用户一眼看出是否超预算。
同时可以在结果页增加“重新生成”按钮。不同用户会输入不同偏好,生成结果差异会很明显,这本身也是 AI 项目适合展示的地方。
4.4 前后端联调和跨域配置
本地开发时前端跑在 5173,后端跑在 8080,这是不同端口,所以必须处理跨域。
后端可以配置全局 CORS:
@Configuration public class CorsConfig { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://localhost:5173") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .allowCredentials(true); } }; } }生产环境不建议太依赖跨域配置,更好的办法是用 Nginx 做反向代理。前端请求/api时,Nginx 转发到后端 8080,这样前端对外只访问同一个域名,就不会有跨域问题。
注意:跨域报错时,先确认是浏览器拦截还是后端没响应。打开开发者工具 Network 面板,如果能看到请求已经发出但看不到返回,基本就是后端或代理配置问题。
5. 部署和验收:从“本地能跑”到“像样交付”
5.1 构建后端和前端产物
本地跑通不代表项目完成。面试时如果只能演示npm run dev和java -jar,会显得项目还停留在开发阶段。建议至少完成一次正式构建。
后端打包:
cd backend mvn clean package -DskipTests java -jar target/ai-travel-assistant.jar前端构建:
cd frontend npm install npm run build构建后frontend/dist就是静态文件目录。把 dist 目录放到 Nginx 的 html 目录,再把 API 反向代理到后端,就是一个可访问的完整系统。
5.2 使用 Docker Compose 部署前后端
如果考虑到简历上体现 Docker 能力,可以把部署也做一下。一个简化的 docker-compose 示例:
version: "3.8" services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: ai_travel ports: - "3306:3306" redis: image: redis:7 ports: - "6379:6379" backend: build: ./backend ports: - "8080:8080" environment: SPRING_PROFILES_ACTIVE: prod AI_API_KEY: ${AI_API_KEY} AI_API_URL: ${AI_API_URL} depends_on: - mysql - redis frontend: build: ./frontend ports: - "80:80" depends_on: - backend注意,Docker Compose 文件里不要直接写死 AI_API_KEY,建议用环境变量替换。部署文档里要写清楚外部需要准备哪些变量,否则换一台机器跑不起来。
5.3 验收清单:拿什么标准判断项目合格
不要只看“能打开页面”就算完成。我建议按下面这个清单逐项验收:
- 用户能否注册、登录,刷新页面后登录状态是否保留。
- 推荐表单缺少必填项时是否有明确提示。
- 输入“杭州 3 天 2500 预算”能否稳定返回 3 到 5 个景点。
- 每日行程是否按天数拆分,有没有把景点排到日期外面。
- 大模型接口故意关掉时,系统是否还能返回规则推荐结果。
- 连续提交 10 次相同请求,Redis 缓存是否生效,模型调用次数是否减少。
- 前端部署到 Nginx 后,刷新页面是否 404。
- 容器重启后,数据库数据和 Redis 缓存是否恢复或重建。
6. 转型和面试:项目亮点怎么说,踩坑怎么避免
6.1 项目讲法:从“功能列表”改成“问题链路”
面试时不要一上来就背功能列表。比较自然的讲法是:
- 先说项目背景:用户在选择旅行目的地和路线时,信息分散,所以做一个智能推荐助手。
- 再说系统边界:后端处理用户、景点、推荐记录,前端负责交互,AI 负责生成个性化理由和行程。
- 然后讲技术难点:模型输出不稳定、候选结果不可控、接口超时、缓存策略。
- 最后讲你的处理:规则打分保证候选可控,提示词加 JSON 解析,模型失败时降级,Redis 缓存热数据。
这个过程比单纯说“我会 Spring Boot 和 Vue3”更有说服力。
6.2 面试高频问题
根据 Spring Boot、Vue3、AI 全栈这三个方向,下面几个问题很容易被问到:
- Spring Boot 自动配置原理是什么。
- 前后端分离后,如何管理接口版本、错误码和登录鉴权。
- Vue3 的响应式原理和 Vue2 有什么区别。
- 组件通信方式有哪些,为什么不用全局变量。
- 大模型返回结果怎么保证格式稳定。
- 推荐系统里的冷启动怎么处理。
- Redis 缓存穿透、缓存击穿、缓存雪崩分别怎么解决。
这些问题不需要背标准答案,结合项目回答最能体现真实水平。
6.3 常见启动和运行问题排查
下面是我在这个类型项目里经常遇到的坑,按排查顺序列出来。
| 现象 | 优先排查点 |
|---|---|
| Spring Boot 启动失败 | 数据库账号密码、MySQL 是否启动、Redis 是否启动、端口是否被占用 |
| Maven 编译报错 | JDK 版本、Lombok 版本、Spring Boot 版本是否兼容 |
| Java 报 OutOfMemoryError | 构建或运行 JVM 堆是否太小,尝试调大-Xmx |
| 前端 npm install 报错 | Node 版本、npm 镜像源、package.json 依赖冲突 |
| 接口 404 | 请求路径是否带/api,Controller 映射是否匹配 |
| 跨域报错 | 后端 CORS、Nginx 反向代理、前端 baseURL |
| 推荐结果长时间不返回 | 大模型接口超时、日志是否卡在远程请求、候选 SQL 是否慢 |
| 模型返回内容无法解析 | 提示词约束不够、返回内容被截断、解析逻辑太严格 |
“Spring Boot 版本太高”这个问题也值得单独说。很多人在搭建项目时直接选了最新版,结果插件、依赖或教程里的写法不兼容。如果不是特别清楚新版特性,建议先用稳定版把项目跑通,再考虑升级。
6.4 我的建议
把这个项目做完,不等于你能拿到百万年薪 offer,但它确实能让你在转型 AI 全栈的路上有东西可讲。关键不在标题,而在你能否独立完成一条链路:Spring Boot 提供接口,Vue3 渲染页面,AI 生成个性化推荐,Docker 部署上线。
我建议你把节奏放稳一点。先跑通单条推荐请求,再处理批量任务;先接受默认配置,再优化模型输出和缓存;先完成基础页面,再考虑组件拆分和权限控制。很多问题看起来是功能不支持,实际是依赖版本、路径权限或输入格式没有处理好。
如果只是学习,默认配置够用。如果要把它写进简历,请务必补上接口文档、部署文档、测试样例和问题排查记录。到那时候,这个 AI 旅游智能推荐助手才真正成为你的项目,而不只是别人教程里的复刻品。