恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

AI全栈实战:Spring Boot + Vue3 打造AI旅游推荐助手

  • 首页
  • 资讯中心
  • /
  • AI全栈实战:Spring Boot + Vue3 打造AI旅游推荐助手

相关资讯

机械动力×列车:多人实况里的系统工程与协作哲学 2026/8/31 12:03:46
低成本自建PTP Grandmaster:从原理到实战的完整指南 2026/8/31 12:03:46
零基础Python入门:环境配置、VSCode搭建与海龟绘图实战 2026/8/31 11:58:46

最新资讯

多单元混合架构与六分频技术:全频音质拉满的声学设计逻辑
AtomCode 磁盘占用实测:别照搬清理命令,先看自己的占用分布
Vibe Coding上下文管理:从窗口到项目的三层策略
Vibe Coding 的命门:上下文管理实战
泰坦尼克号数据科学实战:从零入门特征工程与逻辑回归
AI桌宠技术落地指南:透明悬浮窗、大模型API与多实例部署

今日推荐

MCU无DAC如何用定时器+DMA 2D输出高保真任意波形
Cortex-M3 Flash下载失败?从编程错误标志到供电瞬态排查
STM32 TouchGFX屏幕切换Transition优化:原理、配置与排障实战

本周热门

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析
数字电路时序基石:深入理解建立时间与保持时间
蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

本月精选

如何用DamaiHelper实现演唱会门票的智能自动化抢购:完整技术解决方案指南
第4篇:59 倍性能差距的索引瓶颈定位——一次教科书级的全表扫描调优
终极歌词批量下载神器:5分钟解决离线音乐库歌词同步难题

AI全栈实战:Spring Boot + Vue3 打造AI旅游推荐助手

发布时间:2026/8/31 12:03:46
AI全栈实战:Spring Boot + Vue3 打造AI旅游推荐助手 在 2026 年聊“AI 全栈”很多前后端开发者第一反应是我是不是该把 Java 和 Vue 丢掉去学 Python、去训练大模型这个理解其实有偏差。真正吃香的 AI 全栈工程师不是“抛弃旧技术的新物种”而是“能在原有技术栈上让 AI 能力稳定落地的人”。换句话说Spring Boot Vue3 这套前后端分离体系在今天反而是 AI 应用落地最顺手的骨架。这篇文章就围绕一个可运行的“AI 旅游智能推荐助手”项目展开。它不训练模型也不停留在“套个网页壳子”的层面而是把 Spring Boot 后端、Vue3 前端、大模型 API、结构化输出、超时降级、Docker 部署这些环节完整打通。读完你可以得到一个能本地跑通的项目也能理解 AI 全栈开发在真实工程中到底是怎么咬合在一起的。先给一个明确判断AI 全栈不是让 Java 程序员去转行做大模型训练而是让程序员在原有技术栈上新增一层“AI 能力层”。这篇文章会按项目实战的节奏从需求分析、技术选型、前后端编码到联调验证、生产化建议一步一步拆给你看。1. AI 全栈到底是什么它不是“抛弃 Java”而是“让 Java 长出 AI 能力”“AI 全栈”这个词在社区里已经被说烂了但大多数人对它的理解是模糊的。如果不先把概念对齐后面写代码时会出现方向性错误。先看两个经常被混淆的角色角色核心工作典型技能业务目标大模型全栈工程师模型微调、训练、推理优化、部署Python、PyTorch、DeepSpeed、vLLM让模型本身更强、更快、更便宜AI 全栈开发工程师用大模型能力解决具体业务问题Java、Spring Boot、Vue3、API 集成、Prompt 工程让用户能用上 AI 功能并且稳定可用从上表可以看到AI 全栈开发工程师的重点并不在“模型内部”而在“模型外部”。它更像一个系统集成角色把大模型当成一个能力组件嵌进现有的 Web 业务系统。很多 Java 开发者容易陷入一个误区认为转型 AI 全栈必须从零开始学 Python 和机器学习。实际上工业界大量 AI 应用项目后端就是 Java前端就是 Vue3AI 部分通过 HTTP 调用大模型 API 完成。一个懂 Spring Boot、会写 Vue3、理解大模型接口和提示词工程的人反而是当前最稀缺的。再往前一步看AI 全栈项目比传统 Web 项目多了哪些环节主要就三处多了一个外部依赖大模型 API它不是本地函数有网络开销、有超时、有费用、有幻觉风险。多了一层文本处理你必须把用户输入组织成有效的 Prompt然后把模型输出解析成业务数据结构。多了一套降级策略大模型不稳定你的系统不能跟着不稳定必须有本地兜底方案。这三点就是 AI 全栈开发和传统前后端开发的本质区别也是本文后面所有代码设计的出发点。2. 项目需求分析AI 旅游智能推荐助手要解决什么问题选“旅游推荐”作为 AI 全栈实战项目有一个很重要的原因它同时具备业务价值和工程复杂度。从用户角度看旅游规划是一件决策链很长的事。用户要去成都玩 3 天预算中等喜欢美食和人文那么吃住行游怎么安排传统做法是打开一堆 App自己查攻略、比酒店、看路线信息过载严重。AI 旅游助手的价值就是把“用户自然语言描述的需求”转换成“一份结构化的行程推荐”。从技术角度看这个项目非常适合展示 AI 全栈开发的完整链路功能需求用户输入目的地、游玩天数、出行人数、预算等级、兴趣偏好。系统调用大模型生成推荐结果。推荐结果包含行程安排、景点列表、美食推荐、注意事项。大模型调用失败时系统返回本地规则生成的兜底推荐。前端页面完成表单交互和结果展示。非功能需求接口响应时间在可接受范围内超过阈值要快速失败。大模型 API Key 必须保存在服务端不能出现在前端代码中。推荐结果要结构化方便前端渲染和后续扩展。这个项目和传统“基于数据库的 CRUD 推荐”有一个本质区别传统的做法是预先在数据库里存好景点和线路用户输入关键词后做匹配而这个项目的核心是让大模型根据用户语义化的需求实时生成内容。前者是“查数据”后者是“生成内容”。当然更完善的生产级方案会把两者结合用 RAG 检索真实景点数据再交给大模型生成这一点我们在第 9 章展开讲。3. 技术选型与整体架构先看整体数据流这个图我建议你记住因为它就是 AI 全栈项目的最小骨架Vue3 前端页面 ↓ 用户填写表单发起 HTTP 请求 Spring Boot 后端接口 ↓ 组装 Prompt调用大模型 API 大模型 APIHTTP JSON ↓ 返回原始文本 Spring Boot 解析 JSON生成结构化结果 ↓ 响应 Vue3 前端 前端渲染行程推荐卡片技术选型如下技术栈选型说明后端框架Spring Boot 3.x需要 JDK 17生态成熟前端框架Vue3 Vite组合式 API工程化体验好HTTP 客户端Spring 自带 RestClientSpring Framework 6.1 起内置请求大模型 API 很方便JSON 解析JacksonSpring Boot 默认集成大模型 API兼容 OpenAI 协议的接口可以接入云服务商也可以接入私有化部署模型数据库暂不引入先跑通推荐链路后续可扩展 MySQL这里特别说一句版本问题。Spring Boot 3.x 要求 JDK 17 及以上所以不要再用 JDK 8。如果你看到网上很多教程还在用 JDK 8 Spring Boot 2.x也不用奇怪那是老项目常用的组合。新项目建议直接上 Spring Boot 3.x毕竟从长期维护和 Java 版本演进来看这是更稳的方向。前端选 Vue3 而不是 Vue2原因是 Vue3 的组合式 API 在逻辑复用和 TypeScript 支持上更好而且 Vue2 已经进入了维护末期新项目基本不会再选它。Vite 作为开发服务器冷启动速度比 Webpack 快很多对前后端联调体验提升明显。4. 环境准备与前置条件动手写代码之前先把环境准备好。这里给出一个可复用的版本组合具体版本号请以你下载时的最新稳定版为准。组件要求说明JDK17Spring Boot 3.x 的最低要求Maven3.8Java 项目构建工具Node.js18 或 20Vue3 Vite 开发环境要求IDEIntelliJ IDEA 或 VS CodeIDEA 适合后端VS Code Volar 插件适合前端大模型 API Key根据模型平台开通用于服务端调用大模型接口如果使用的是 IDEA建议安装 Lombok 插件并确认插件版本和 JDK 版本匹配。很多人在 IDEA 中遇到java: you arent using a compiler supported by lombok, so lombok will not work这类报错通常就是 IDEA Lombok 插件版本过旧或者项目编译用的 JDK 版本太高需要升级插件或统一 JDK 版本。大模型 API 的选择上你不需要在一开始就纠结“哪家最强”。只要它提供 OpenAI 兼容的/v1/chat/completions接口就可以用同一套代码接入。后续要切换模型只需要改配置项不需要改动代码。这里有一个实用建议先把最简单的接口跑通再逐层加业务逻辑。5. 后端实现Spring Boot 3 搭建 AI 推荐服务后端是整个项目的核心。这一节会分五个步骤从空项目到可调用的 AI 推荐接口每步都有完整代码。5.1 创建 Spring Boot 项目与依赖推荐使用 Spring Initializr 创建项目选择 Java 17、Maven、Spring Web、Validation 依赖。生成后修改pom.xml完整内容如下!-- 文件路径pom.xml -- ?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.5/version relativePath/ /parent groupIdcom.example/groupId artifactIdai-travel-assistant/artifactId version1.0.0/version nameai-travel-assistant/name descriptionAI 旅游智能推荐助手/description properties java.version17/java.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration excludes exclude groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /exclude /excludes /configuration /plugin /plugins /build /project这里有一个设计决定我没有引入spring-boot-starter-webflux而是使用 JDK 内置能力加 Spring 自带的 RestClient。这样依赖更少项目更轻。如果你后续需要更复杂的响应式流处理或高并发场景再引入 WebFlux 也不迟。5.2 配置大模型 API 参数大模型的 baseUrl、apiKey、模型名称这些信息一定要放到配置文件中而且要支持通过环境变量覆盖。这样代码仓库里不会出现真实密钥部署到生产环境时也可以灵活切换。# 文件路径src/main/resources/application.yml server: port: 8080 spring: application: name: ai-travel-assistant ai: model: # 大模型 API 基础地址请替换为实际模型平台地址 # 通常是 https://你的模型服务地址 base-url: ${AI_BASE_URL:https://api.example.com} # API Key推荐通过环境变量 AI_API_KEY 注入 api-key: ${AI_API_KEY:} # 模型名称不同平台名称不同请以实际为准 model-name: ${AI_MODEL_NAME:demo-model} temperature: 0.7 max-tokens: 2048 timeout-seconds: 30对应的配置绑定类// 文件路径src/main/java/com/example/aitravel/config/AiModelProperties.java package com.example.aitravel.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; Data Component ConfigurationProperties(prefix ai.model) public class AiModelProperties { private String baseUrl; private String apiKey; private String modelName; private double temperature 0.7; private int maxTokens 2048; private int timeoutSeconds 30; }5.3 定义请求参数与响应模型用户提交表单后前端会以 JSON 格式发送请求。后端的请求参数需要做校验避免脏数据进入大模型 Prompt。// 文件路径src/main/java/com/example/aitravel/model/RecommendRequest.java package com.example.aitravel.model; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import lombok.Data; Data public class RecommendRequest { NotBlank(message 目的地不能为空) private String destination; Min(value 1, message 游玩天数至少为1天) Max(value 30, message 游玩天数最多支持30天) private Integer days; Min(value 1, message 出行人数至少为1人) private Integer travelers; private String budget; private String interest; }响应模型要尽量贴合大模型的输出结构。这里定义四个部分行程安排、景点列表、美食推荐、注意事项。// 文件路径src/main/java/com/example/aitravel/model/RecommendResponse.java package com.example.aitravel.model; import lombok.Data; import java.util.List; Data public class RecommendResponse { private String destination; private Integer days; private ListDayPlan itinerary; private ListString attractions; private ListString foods; private ListString tips; Data public static class DayPlan { private Integer day; private String title; private String plan; } }统一返回结构// 文件路径src/main/java/com/example/aitravel/model/Result.java package com.example.aitravel.model; import lombok.Data; Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }5.4 Controller 层转发请求到 ServiceController 层保持轻薄只负责参数校验和响应包装。// 文件路径src/main/java/com/example/aitravel/controller/RecommendController.java package com.example.aitravel.controller; import com.example.aitravel.model.RecommendRequest; import com.example.aitravel.model.RecommendResponse; import com.example.aitravel.model.Result; import com.example.aitravel.service.TravelRecommendService; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api/recommend) CrossOrigin(origins http://localhost:5173) public class RecommendController { private final TravelRecommendService travelRecommendService; public RecommendController(TravelRecommendService travelRecommendService) { this.travelRecommendService travelRecommendService; } PostMapping public ResultRecommendResponse recommend(RequestBody Valid RecommendRequest request) { return Result.success(travelRecommendService.recommend(request)); } }关于CrossOrigin多说一句这是在开发阶段为了前端 Vite 默认端口5173临时加的跨域配置。生产环境更推荐通过 Nginx 或网关统一处理跨域而不是把CrossOrigin散落在 Controller 上。5.5 Service 层Prompt 组装、大模型调用、JSON 解析这是整个项目最有价值的部分。AI 全栈的核心逻辑都集中在这里。// 文件路径src/main/java/com/example/aitravel/service/TravelRecommendService.java package com.example.aitravel.service; import com.example.aitravel.config.AiModelProperties; import com.example.aitravel.model.RecommendRequest; import com.example.aitravel.model.RecommendResponse; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient; import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; Service public class TravelRecommendService { private final AiModelProperties aiModelProperties; private final RestClient restClient; private final ObjectMapper objectMapper; public TravelRecommendService(AiModelProperties aiModelProperties, ObjectMapper objectMapper) { this.aiModelProperties aiModelProperties; this.objectMapper objectMapper; this.restClient RestClient.builder() .baseUrl(aiModelProperties.getBaseUrl()) .defaultHeader(Authorization, Bearer aiModelProperties.getApiKey()) .build(); } public RecommendResponse recommend(RecommendRequest request) { try { String rawContent callLlm(request); return parseResponse(rawContent); } catch (Exception e) { // 大模型调用失败或解析失败时使用本地规则兜底 return localFallback(request); } } private String callLlm(RecommendRequest request) { String prompt buildPrompt(request); MapString, Object requestBody new HashMap(); requestBody.put(model, aiModelProperties.getModelName()); requestBody.put(temperature, aiModelProperties.getTemperature()); requestBody.put(max_tokens, aiModelProperties.getMaxTokens()); MapString, String message new HashMap(); message.put(role, user); message.put(content, prompt); requestBody.put(messages, List.of(message)); String responseBody restClient.post() .uri(/v1/chat/completions) .contentType(MediaType.APPLICATION_JSON) .body(requestBody) .retrieve() .body(String.class); JsonNode root objectMapper.readTree(responseBody); return root.path(choices).path(0).path(message).path(content).asText(); } private String buildPrompt(RecommendRequest request) { return 你是一位资深旅游规划师。请根据用户的出行需求生成一份结构化的旅游推荐方案。 用户需求 - 目的地%s - 游玩天数%d 天 - 出行人数%d 人 - 预算等级%s - 兴趣偏好%s 要求 1. 行程安排要具体到每一天给出上午、下午、晚上的活动建议。 2. 景点推荐要符合目的地特点避免明显的常识错误。 3. 美食推荐要包含当地特色同时考虑预算因素。 4. 最后给出 3 到 5 条出行注意事项。 5. 严格输出 JSON不要输出 Markdown不要输出解释文字。 JSON 格式如下 { destination: 目的地, days: 3, itinerary: [ { day: 1, title: 第一天主题, plan: 上午...下午...晚上... } ], attractions: [景点1, 景点2], foods: [美食1, 美食2], tips: [注意事项1, 注意事项2] } .formatted( request.getDestination(), request.getDays(), request.getTravelers(), request.getBudget(), request.getInterest() ); } private RecommendResponse parseResponse(String rawContent) throws Exception { String json extractJson(rawContent); JsonNode node objectMapper.readTree(json); RecommendResponse response new RecommendResponse(); response.setDestination(node.path(destination).asText()); response.setDays(node.path(days).asInt()); ListRecommendResponse.DayPlan itinerary new ArrayList(); JsonNode itineraryNode node.path(itinerary); if (itineraryNode.isArray()) { for (JsonNode item : itineraryNode) { RecommendResponse.DayPlan plan new RecommendResponse.DayPlan(); plan.setDay(item.path(day).asInt()); plan.setTitle(item.path(title).asText()); plan.setPlan(item.path(plan).asText()); itinerary.add(plan); } } response.setItinerary(itinerary); ListString attractions new ArrayList(); node.path(attractions).forEach(item - attractions.add(item.asText())); response.setAttractions(attractions); ListString foods new ArrayList(); node.path(foods).forEach(item - foods.add(item.asText())); response.setFoods(foods); ListString tips new ArrayList(); node.path(tips).forEach(item - tips.add(item.asText())); response.setTips(tips); return response; } private String extractJson(String rawContent) { String text rawContent.trim(); int start text.indexOf({); int end text.lastIndexOf(}); if (start 0 end start) { return text.substring(start, end 1); } return text; } private RecommendResponse localFallback(RecommendRequest request) { RecommendResponse response new RecommendResponse(); response.setDestination(request.getDestination()); response.setDays(request.getDays()); ListRecommendResponse.DayPlan itinerary new ArrayList(); for (int i 1; i request.getDays(); i) { RecommendResponse.DayPlan plan new RecommendResponse.DayPlan(); plan.setDay(i); plan.setTitle(第 i 天自由探索); plan.setPlan(建议先游览核心景点再根据兴趣选择当地特色体验。); itinerary.add(plan); } response.setItinerary(itinerary); response.setAttractions(List.of(当地核心景区以实际查询为准)); response.setFoods(List.of(当地特色餐厅以实际查询为准)); response.setTips(List.of(大模型服务暂时不可用以上为兜底推荐建议稍后重试。)); return response; } }这段代码里最值得关注的是buildPrompt和parseResponse两个方法。很多第一次做 AI 集成的人会忽略提示词的约束作用直接问“帮我推荐成都旅游”然后大模型返回一大段散文根本没法展示。这里我做了两个约束一是要求分字段输出二是给出了明确的 JSON 结构。这属于典型的“结构化输出”技巧能显著降低解析成本。另外localFallback方法非常重要。大模型接口可能因为超时、限流、模型服务异常等原因失败如果系统直接把异常抛给用户体验会很差。本地规则兜底方案虽然不如 AI 推荐智能但至少保证了接口永远有响应。6. 前端实现Vue3 完成推荐页面与交互后端接口已经就绪接下来用 Vue3 写一个能提交表单、展示结果的前端页面。6.1 创建 Vue3 项目使用 Vite 创建项目npm create vitelatest ai-travel-frontend -- --template vue cd ai-travel-frontend npm install npm install axios这里使用 axios 作为 HTTP 客户端它是目前 Vue 生态中最常用的请求库。如果你更习惯用原生的fetch也可以代码逻辑差别不大。6.2 封装 API 请求新建src/api/recommend.js// 文件路径src/api/recommend.js import axios from axios const request axios.create({ baseURL: /api, timeout: 35000 }) export function getTravelRecommend(data) { return request.post(/recommend, data) }这里把请求地址写成/api而不是完整的http://localhost:8080/api是为了让 Vite 开发服务器做代理转发避免开发阶段跨域问题。6.3 推荐页面组件新建src/views/TravelRecommend.vue!-- 文件路径src/views/TravelRecommend.vue -- template div classrecommend-container h2AI 旅游智能推荐助手/h2 p classsubtitle告诉我你想去哪里AI 帮你规划行程/p el-form :modelform label-width100px classrecommend-form el-form-item label目的地 el-input v-modelform.destination placeholder例如成都、杭州、西安 / /el-form-item el-form-item label游玩天数 el-input-number v-modelform.days :min1 :max30 / /el-form-item el-form-item label出行人数 el-input-number v-modelform.travelers :min1 :max20 / /el-form-item el-form-item label预算等级 el-select v-modelform.budget placeholder请选择预算 el-option label经济型 value经济型 / el-option label中等 value中等 / el-option label豪华型 value豪华型 / /el-select /el-form-item el-form-item label兴趣偏好 el-input v-modelform.interest placeholder例如美食、人文、自然风光 / /el-form-item el-form-item el-button typeprimary :loadingloading clickhandleRecommend 开始推荐 /el-button /el-form-item /el-form div v-ifloading classloading-text AI 正在努力规划行程请稍候... /div div v-ifresult classresult-content el-card v-forday in result.itinerary :keyday.day classday-card template #header span第 {{ day.day }} 天{{ day.title }}/span /template p{{ day.plan }}/p /el-card el-card classattr-card template #header span推荐景点/span /template ul li v-foritem in result.attractions :keyitem{{ item }}/li /ul /el-card el-card classfood-card template #header span美食推荐/span /template ul li v-foritem in result.foods :keyitem{{ item }}/li /ul /el-card el-card classtip-card template #header span出行提示/span /template ul li v-foritem in result.tips :keyitem{{ item }}/li /ul /el-card /div /div /template script setup import { ref } from vue import { getTravelRecommend } from ../api/recommend const loading ref(false) const result ref(null) const form ref({ destination: 成都, days: 3, travelers: 2, budget: 中等, interest: 美食、人文 }) async function handleRecommend() { if (!form.value.destination) { alert(请填写目的地) return } loading.value true result.value null try { const response await getTravelRecommend(form.value) if (response.data.code 200) { result.value response.data.data } else { alert(response.data.message) } } catch (error) { alert(推荐服务暂时不可用请稍后重试) } finally { loading.value false } } /script style scoped .recommend-container { max-width: 800px; margin: 0 auto; padding: 40px 20px; } .subtitle { color: #666; margin-bottom: 24px; } .loading-text { text-align: center; color: #409eff; padding: 24px 0; } .result-content { margin-top: 24px; } .day-card, .attr-card, .food-card, .tip-card { margin-bottom: 16px; } /style注意这段代码用到了 Element Plus 组件库需要先安装npm install element-plus然后在main.js中引入// 文件路径src/main.js import { createApp } from vue import ElementPlus from element-plus import element-plus/dist/index.css import App from ./App.vue const app createApp(App) app.use(ElementPlus) app.mount(#app)6.4 配置 Vite 代理修改vite.config.js// 文件路径vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这一步很关键。很多前后端分离项目第一次联调时出现“404”或者“CORS 跨域错误”都是因为代理没配好。配了代理之后前端请求/api/recommend会被转发到http://localhost:8080/api/recommend前后端联调时就不会再因为端口不同而跨域。7. 完整启动与验证后端和前端都写完了接下来看怎么启动和验证。7.1 启动后端先确保大模型 API 配置正确。在 IDEA 中运行主类之前可以通过环境变量注入 API Key也可以在 IDEA 的 Run Configuration 中配置环境变量。# 在项目根目录执行 mvn spring-boot:run看到类似下面的日志说明启动成功Tomcat started on port 8080 (http) with context path Started AiTravelAssistantApplication in 3.2 seconds7.2 启动前端另开一个终端进入前端项目目录cd ai-travel-frontend npm run dev看到类似下面的日志说明启动成功VITE v5.0.0 ready in 500 ms ➜ Local: http://localhost:5173/浏览器访问http://localhost:5173/填好表单后点击“开始推荐”。7.3 使用 curl 验证后端接口如果你不想打开页面也可以直接用 curl 验证后端接口curl -X POST http://localhost:8080/api/recommend \ -H Content-Type: application/json \ -d { destination: 成都, days: 3, travelers: 2, budget: 中等, interest: 美食、人文 }如果一切正常你会看到返回的 JSON 中包含itinerary、attractions、foods、tips字段。如果接口返回了兜底推荐数据请注意tips中会包含“大模型服务暂时不可用”的提示。这表示后端工作正常但不满足调用大模型 API 的配置条件需要检查 API Key 或网络。8. 常见问题与排查方法AI 全栈项目涉及前端、后端、外部 API 三个层面任何一个环节出问题都会导致整体不可用。下面把最常见的几类问题整理成表格方便你排查。问题现象可能原因排查方式解决方案后端启动失败提示 Lombok 编译错误Lombok 版本和 JDK 版本不匹配查看 maven 编译日志检查 Lombok 版本升级 Lombok 依赖版本或统一 IDEA 内置编译器版本后端启动报 OutOfMemoryError编译或运行时内存不足查看 IDEA 的 Build 日志检查 Maven 配置调整MAVEN_OPTS或 IDEA 编译堆内存前端请求/api/recommend返回 404Vite 代理未生效或后端接口路径不匹配检查vite.config.js代理配置确认后端接口路径统一 API 前缀重启 Vite 开发服务前端请求报 CORS 跨域错误前端端口和后端端口不一致且没有代理没有生效查看浏览器 Network 面板中的具体请求 URL优先使用 Vite 代理临时可在 Controller 加CrossOrigin大模型接口返回超时网络不稳或模型推理时间过长查看后端日志中的超时时间测试模型平台接口连通性适当调大超时时间同时确保有本地兜底逻辑大模型返回了 JSON 以外的文本Prompt 约束不足模型没有按 JSON 输出打印原始返回内容检查extractJson方法加强 Prompt 中“严格输出 JSON”的约束并增加重试逻辑Vue3 的 defineProps 或 defineEmits 报错使用了组合式 API 但缺少编译支持查看报错文件是否是.vue单文件组件确保使用script setup语法且 Vite 安装了官方 Vue 插件修改了配置但运行没生效配置文件缓存或环境变量未更新重启应用检查环境变量是否真正注入重启后端和前端服务避免开发服务热更新没有覆盖全部配置这里特别强调一下“大模型返回内容解析失败”的问题。即使你在 Prompt 里写了“严格输出 JSON”模型仍然可能返回带 Markdown 代码块的内容。所以extractJson方法里先用indexOf({)和lastIndexOf(})截取 JSON 子串这是应对大模型输出不规范的常用手段。9. 从 Demo 到生产AI 全栈项目的最佳实践项目跑通只是第一步。如果把 ChatGPT 风格的前台 Demo 直接搬到生产环境会遇到很多问题。这一节给出实际工程中必须考虑的 6 个优化方向。9.1 API Key 的安全管理这是最基础也最重要的一条。大模型 API 是按 Token 计费的如果 Key 泄露到前端代码或者提交到 Git 仓库会带来直接的经济损失和安全风险。正确的做法是API Key 只允许在服务端配置。代码仓库中只保留占位符通过环境变量注入真实 Key。生产环境中使用配置中心或密钥管理服务。定期轮换 Key发现泄露立即吊销。9.2 Prompt 版本控制与评估Prompt 不是一次写好的而是会持续迭代的。简单把 Prompt 写死在 Service 里后续想调整会很痛苦。可以做一个prompt表或配置文件记录每条推荐对应的 Prompt 版本和模型版本。这样即使后续发现某些场景推荐质量下降也能快速定位是 Prompt 调整导致的还是模型版本更换导致的。9.3 引入缓存降低成本和延迟旅游推荐场景有一个特点相同目的地、相近天数、相近预算的请求推荐结果往往可以复用。对于热门目的地可以缓存当天或者几小时内的推荐结果减少大模型调用次数。缓存方案可以是 Caffeine 本地缓存也可以是 Redis 分布式缓存。考虑到这个项目暂时没有引入数据库先用本地缓存起步完全足够。9.4 用 RAG 增强推荐准确性纯靠大模型生成的推荐存在信息滞后和幻觉问题。比如某个景点临时闭园大模型并不知道。生产级方案是引入 RAG把景点介绍、开放时间、交通信息、用户评价等数据向量化存入向量数据库用户请求时先检索相关片段再拼进 Prompt 让大模型生成。RAG 的引入会把项目从“AI 玩具”提升到“AI 产品”这也是简历上非常值得写的一笔。实现上可以选 Spring AI 的向量数据库支持也可以单独用向量库服务通过 HTTP 访问。9.5 限流与熔断外部大模型接口通常有 QPS 限制。当用户量上来之后如果不做限流很可能会触发平台的限流策略导致大量请求失败。推荐做法对每个用户做接口频率限制。大模型请求设置合理的超时时间。如果连续 N 次调用失败开启熔断直接走降级方案。在日志中记录调用耗时和 token 消耗。9.6 Docker 部署与环境变量注入项目开发完成后用 Docker 部署是最常见的生产方式。这里给出一个简化的后端 Dockerfile# 文件路径Dockerfile FROM eclipse-temurin:17-jre WORKDIR /app COPY target/ai-travel-assistant-1.0.0.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]构建和运行命令mvn clean package docker build -t ai-travel-assistant:latest . docker run -d -p 8080:8080 \ -e AI_BASE_URLhttps://你的模型平台地址 \ -e AI_API_KEY你的真实APIKey \ -e AI_MODEL_NAME你的模型名 \ ai-travel-assistant:latest这里再次体现了配置项通过环境变量注入的价值同一份 Docker 镜像只要在启动时传入不同的环境变量就可以切换不同的模型服务不需要重新构建镜像。10. 给想转型 AI 全栈的开发者几点实在建议最后聊一些代码之外的东西。第一先跑通最小闭环再考虑优化。不要把第一条链路做得太复杂。先用 Spring Boot 写一个接口用 RestClient 调用大模型 API用 Vue3 写在页面上哪怕只输出一段文本也算闭环。闭环建立之后后面的优化才有意义。第二把“结构化输出”和“降级策略”刻进习惯里。这是 AI 应用和普通 CRUD 项目最大的区别。普通接口只会成功或失败而 AI 接口存在“返回了但格式不对”“返回了但内容不对”“超时但不知道是否成功”这些中间状态。只有把这些状态都处理清楚系统才真正稳定。第三简历上不要只写“熟悉 AI 全栈”要把项目的完整链路写清楚。比如“基于 Spring Boot Vue3 大模型 API 实现旅游智能推荐助手使用 Prompt 结构化输出和本地降级策略接口可用性达到……”这类描述比抽象地讲“具有 AI 能力”有说服力得多。第四持续关注 Spring AI 和 LangChain4j 这类框架。它们把大模型调用封装成了更上层的 API对 Java 开发者来说能省去不少重复代码。不过我的建议是先把原生流程跑通理解底层协议和数据结构再用框架提升效率这样遇到问题时才知道去哪里排查。2026 年的 AI 全栈比的不再是谁“会用”大模型接口而是谁能把大模型稳定、安全、低成本地嵌进真实的业务系统里。Java 和 Vue3 不是负担反而是很多团队真正缺的工程化底座。从一个可以运行的 AI 旅游推荐助手开始逐步补齐缓存、检索、限流、观测这些生产环节这条路走通之后你手里拿到的就不只是一个 Demo而是一套可以持续迭代的 AI 应用开发方法论。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号