Spring Boot + AI大模型:智能天气出行服务系统设计与实现

发布时间:2026/9/2 4:49:27
Spring Boot + AI大模型:智能天气出行服务系统设计与实现 之前在准备毕业设计时不少同学容易陷入一个常见困境课程学了 Spring Boot、数据库、前端三板斧但自己动手从零做一套完整系统时总是不知道从哪里切入。尤其是这两年 AI 大模型非常火如果能把它和传统的 Web 开发结合起来既能让项目更有亮点又能贴合“智能应用”的选题方向。本文分享一套完整的智能天气出行服务系统设计与实现方案基于 Spring Boot Thymeleaf AI 大模型接口覆盖需求分析、数据库设计、后端开发、页面渲染、大模型接入、项目演示等全流程。整套代码思路既能直接用于毕业设计也可以作为 Spring Boot 综合实战练手项目后端开发刚入门、想系统走一遍完整项目的同学都很适合。1. 项目背景与核心概念1.1 系统定位智能天气出行服务系统简单来说是一个“查天气 给出行建议”的 Web 应用。用户输入一个城市名称系统会先获取该城市的实时天气数据再结合日期、出发地、目的地等出行偏好调用 AI 大模型接口自动生成一份智能出行建议。建议内容可以是穿衣提醒、交通方式推荐、出行注意事项也可以根据天气情况给出景点安排建议。与普通天气网站相比这个系统的差异点不在于“天气数据本身”而在于“天气数据到出行决策的转换”。传统天气站点只展示温度、风力、湿度本系统则通过 Prompt 工程把结构化天气数据拼装成自然语言提示词交给大模型生成更人性化、更具体的出行指导。这也是整个项目最能体现“AI 应用”价值的地方。1.2 为什么适合作为毕业设计从选题层面看这类系统具备比较完整的业务闭环有用户、有页面、有外部接口调用、有数据持久化、有 AI 能力集成。演示时既能看到前端交互效果也能展示后端接口设计还能现场演示大模型生成建议内容丰富度足够。从技术层面看它没有停留在简单的增删改查上而是涉及Spring Boot 三层架构与依赖注入Thymeleaf 服务端页面渲染MySQL 数据库设计与 MyBatis-Plus 操作RestTemplate / HttpClient 调用第三方 HTTP 接口JSON 数据解析与结构化处理AI 大模型 API 的 Prompt 设计与返回解析异常处理、日志记录、参数校验等工程规范。这些内容基本覆盖了 Java 后端岗位日常开发的核心技能点写进简历和项目文档里都比较有价值。1.3 技术难点拆解把这个项目拆开来看需要重点解决的难点有三个第一个是天气数据的获取。这里涉及外部 API 调用需要考虑接口地址、请求参数、返回格式、超时处理、异常回退等。如果像写课程作业那样只做一个假数据页面项目会显得单薄但如果完全依赖外网接口线上演示时又可能因为网络问题翻车所以我会在文中给出 Mock 兜底方案。第二个是 AI 大模型接口的集成。大模型接口本质上是 HTTP POST 请求难点在于 Prompt 设计如何把天气数据变成一段高质量提示词让模型返回结构清晰、内容可用的建议。第三个是前端页面与后端的数据流转。使用 Thymeleaf 时Controller 通过 Model 携带数据模板通过 th:text、th:if 等语法渲染。这个流程虽然比前后端分离简单但非常直观适合展示 Spring Boot 服务端渲染的完整链路。2. 技术栈与系统架构设计2.1 技术选型与版本说明本文示例环境以 Java 8、Spring Boot 2.7.x、MyBatis-Plus 3.5.x、MySQL 8.x 为准。这套组合在目前高校毕业设计中非常常见稳定性好社区资料多遇到问题容易检索到解决方案。你本机使用的版本不一定和示例完全一致这不影响阅读。Spring Boot 3.x 对 JDK 版本要求更高如果使用 3.x注意部分依赖坐标和配置项会有差异本文会尽量标注容易踩坑的地方。整体技术栈如下层次技术说明前端页面Thymeleaf HTML CSS服务端模板渲染Web 框架Spring Boot提供 MVC 与自动配置数据访问MyBatis-Plus简化数据库操作数据库MySQL 8.x持久化业务数据天气数据源和风天气或其他天气 API提供实时天气 JSON 数据AI 能力大模型 APIOpenAI 兼容格式生成智能出行建议构建工具Maven依赖管理与打包2.2 系统功能模块设计根据毕设演示需求我将系统划分为以下功能模块用户模块注册、登录保证每个用户的出行记录是隔离的属于完整系统的基础模块。天气查询模块输入城市名返回实时天气、温度、风力、湿度等结构化数据。系统内部先通过城市编码接口拿到城市 ID再查询天气数据。AI 出行建议模块将天气数据、日期、出发地与目的地拼接成 Prompt调用大模型接口生成自然语言建议并将建议内容保存到数据库。历史记录模块用户可以查看自己最近生成的出行建议记录。这个模块能配合数据库设计一起讲解答辩时很好展示。个人设置模块保存用户常驻城市、常用出行偏好后续生成建议时自动携带这些信息提升建议的个性化程度。2.3 核心业务流程系统核心流程如下用户在前端页面输入城市、选择日期、填写出发地和目的地Controller 接收请求参数调用天气服务获取实时天气数据天气服务优先调用真实天气 API如果配置了 Mock 模式或调用失败则按预设规则返回模拟数据系统把天气数据与用户出行信息组合成 PromptAI 服务调用大模型接口获得出行建议文本查询结果保存到 travel_note 表页面渲染天气卡片和 AI 建议卡片展示给用户。整个流程的关键在于第 4 步和第 5 步。Prompt 如果写得太随意模型返回的内容会非常发散如果结构清晰、限制明确返回结果的质量会明显提升。2.4 项目目录结构采用标准的 Maven 工程结构包名建议使用 com.example.smarttravel。核心目录如下src/main/java/com/example/smarttravel ├── SmartTravelApplication.java ├── config │ └── RestTemplateConfig.java ├── controller │ ├── UserController.java │ └── WeatherController.java ├── entity │ ├── User.java │ ├── TravelNote.java │ └── AiSuggestionLog.java ├── mapper │ ├── UserMapper.java │ ├── TravelNoteMapper.java │ └── AiSuggestionLogMapper.java ├── service │ ├── UserService.java │ ├── WeatherService.java │ └── AiTravelService.java ├── service/impl │ ├── UserServiceImpl.java │ ├── WeatherServiceImpl.java │ └── AiTravelServiceImpl.java ├── dto │ ├── WeatherInfo.java │ └── TravelQueryParam.java └── utils └── JsonUtils.java src/main/resources ├── application.yml ├── mapper └── templates ├── index.html ├── login.html ├── weather/query.html └── weather/result.html这样分层的好处是职责清晰Controller 只负责参数接收与视图跳转Service 负责业务逻辑第三方接口调用放在 Service 内部实现后续替换天气供应商或大模型厂商时只改 Service 层即可。3. 数据库设计与初始化3.1 表结构设计本系统主要设计三张业务表用户表、出行记录表、AI 建议日志表。用户表 sys_user 负责存储用户登录信息与常用城市。出行记录表 travel_note 用来保存每次查询的输入参数与天气结果方便前端展示历史记录。AI 建议日志表 ai_suggestion_log 单独保存大模型返回的建议文本。将建议单独成表的好处是后续如果想对比不同模型、不同 Prompt 的效果可以直接在日志表里查询不污染业务数据。3.2 初始化 SQL执行以下 SQL 创建数据库和表。数据库名使用 smart_travel。CREATE DATABASE IF NOT EXISTS smart_travel DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE smart_travel; CREATE TABLE sys_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主键, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT 密码建议保存加密后的密文, nickname VARCHAR(50) COMMENT 昵称, phone VARCHAR(20) COMMENT 手机号, city VARCHAR(50) COMMENT 常驻城市, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间 ) COMMENT 用户表; CREATE TABLE travel_note ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主键, user_id BIGINT NOT NULL COMMENT 用户ID, start_city VARCHAR(50) COMMENT 出发城市, target_city VARCHAR(50) COMMENT 到达城市, travel_date VARCHAR(20) COMMENT 出行日期, weather_desc VARCHAR(100) COMMENT 天气描述, temperature VARCHAR(20) COMMENT 温度区间, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 查询时间 ) COMMENT 出行查询记录表; CREATE TABLE ai_suggestion_log ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主键, user_id BIGINT NOT NULL COMMENT 用户ID, travel_note_id BIGINT COMMENT 关联出行记录ID, prompt_text TEXT COMMENT 发送给大模型的提示词, suggestion_text TEXT COMMENT 大模型返回的建议内容, model_name VARCHAR(50) COMMENT 使用的模型名称, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 生成时间 ) COMMENT AI建议日志表;表名使用 sys_user 而不是 user是为了避免 user 在某些数据库版本或查询语句中与保留字冲突。实际项目中这是一个很好的防守习惯尤其在答辩演示时SQL 能一次跑通非常重要。4. Spring Boot 后端核心代码实现4.1 配置文件 application.yml在 resources 目录下创建 application.yml。配置信息包括数据源、MyBatis-Plus、天气 API、AI 接口以及 Mock 开关。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/smart_travel?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 thymeleaf: cache: false prefix: classpath:/templates/ suffix: .html jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto # 天气服务配置 weather: mock-enabled: true api-key: your_weather_api_key base-url: https://devapi.qweather.com city-lookup-path: /v2/city/lookup now-path: /v7/weather/now # AI 大模型配置 ai: api-key: your_ai_api_key # 以 OpenAI 兼容接口为例国内部分模型服务也支持该协议 base-url: https://api.openai.com/v1/chat/completions model: gpt-3.5-turbo max-tokens: 800 temperature: 0.7 timeout-seconds: 30这里将天气和 AI 的密钥都放到配置文件中实际部署时建议放到环境变量或配置中心。项目文档中要明确写出“敏感信息不应硬编码在源码中”这一条属于加分项。4.2 实体类与 Mapper以 TravelNote 为例实体类使用 Lombok 简化 getter/setter。// 文件路径src/main/java/com/example/smarttravel/entity/TravelNote.java package com.example.smarttravel.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.time.LocalDateTime; Data TableName(travel_note) public class TravelNote { TableId(type IdType.AUTO) private Long id; private Long userId; private String startCity; private String targetCity; private String travelDate; private String weatherDesc; private String temperature; private LocalDateTime createTime; }Mapper 接口直接继承 MyBatis-Plus 的 BaseMapper单表 CRUD 完全不需要手写 SQL。// 文件路径src/main/java/com/example/smarttravel/mapper/TravelNoteMapper.java package com.example.smarttravel.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.smarttravel.entity.TravelNote; import org.apache.ibatis.annotations.Mapper; Mapper public interface TravelNoteMapper extends BaseMapperTravelNote { }4.3 天气服务实现天气服务层需要完成两件事查询城市编码、查询实时天气。这里提供一个简化实现重点展示 RestTemplate 请求与 JSON 解析逻辑。先定义天气信息的 DTO 类// 文件路径src/main/java/com/example/smarttravel/dto/WeatherInfo.java package com.example.smarttravel.dto; import lombok.Data; Data public class WeatherInfo { private String city; private String text; private String temp; private String feelsLike; private String windDir; private String windScale; private String humidity; private String updateTime; }然后在天气服务中实现请求逻辑// 文件路径src/main/java/com/example/smarttravel/service/impl/WeatherServiceImpl.java package com.example.smarttravel.service.impl; import com.example.smarttravel.dto.WeatherInfo; import com.example.smarttravel.service.WeatherService; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.Map; Service public class WeatherServiceImpl implements WeatherService { Resource private RestTemplate restTemplate; Resource private ObjectMapper objectMapper; Value(${weather.mock-enabled}) private Boolean mockEnabled; Value(${weather.api-key}) private String apiKey; Value(${weather.base-url}) private String baseUrl; Value(${weather.city-lookup-path}) private String cityLookupPath; Value(${weather.now-path}) private String nowPath; Override public WeatherInfo getWeatherByCity(String city) { if (Boolean.TRUE.equals(mockEnabled)) { return buildMockWeather(city); } try { // 第一步查询城市编码 String cityLookupUrl baseUrl cityLookupPath ?location city; String cityLocationId getCityLocationId(cityLookupUrl); // 第二步查询实时天气 String nowUrl baseUrl nowPath ?location cityLocationId; return fetchNowWeather(nowUrl, city); } catch (Exception e) { // 第三方接口异常时自动降级为 Mock 数据 return buildMockWeather(city); } } private String getCityLocationId(String url) { HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set(X-QW-Api-Key, apiKey); HttpEntityString entity new HttpEntity(headers); ResponseEntityString response restTemplate.exchange(url, HttpMethod.GET, entity, String.class); try { JsonNode root objectMapper.readTree(response.getBody()); JsonNode location root.path(location); if (location.isArray() location.size() 0) { return location.get(0).path(id).asText(); } } catch (Exception e) { throw new RuntimeException(解析城市编码失败, e); } throw new RuntimeException(未查询到城市编码); } private WeatherInfo fetchNowWeather(String url, String city) { // 请求逻辑与 getCityLocationId 类似解析 now 节点后封装为 WeatherInfo return buildMockWeather(city); } private WeatherInfo buildMockWeather(String city) { // 为了演示稳定性Mock 数据固定返回便于本地开发 WeatherInfo info new WeatherInfo(); info.setCity(city); info.setText(多云); info.setTemp(18); info.setFeelsLike(17); info.setWindDir(东北风); info.setWindScale(2级); info.setHumidity(55%); info.setUpdateTime(2025-01-01 08:00:00); return info; } }上面的代码将核心思路展示清楚外部接口异常时走 Mock 降级避免因为第三方服务不稳定导致整个系统不可用。实际项目中你可能需要根据所选天气 API 的接入文档调整请求头和返回字段这一点我建议在代码注释里保留提示答辩时也能体现你的工程意识。4.4 AI 出行建议服务实现AI 服务是本系统的亮点模块。我选择调用 OpenAI 兼容格式的大模型接口这样国内很多模型服务只要支持兼容协议就能直接替换系统扩展性更好。核心实现包括三部分构建 Prompt、构造 HTTP 请求、解析返回结果。// 文件路径src/main/java/com/example/smarttravel/service/impl/AiTravelServiceImpl.java package com.example.smarttravel.service.impl; import com.example.smarttravel.dto.WeatherInfo; import com.example.smarttravel.service.AiTravelService; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; Service public class AiTravelServiceImpl implements AiTravelService { Resource private RestTemplate restTemplate; Resource private ObjectMapper objectMapper; Value(${ai.api-key}) private String apiKey; Value(${ai.base-url}) private String baseUrl; Value(${ai.model}) private String model; Value(${ai.max-tokens}) private Integer maxTokens; Value(${ai.temperature}) private Double temperature; Override public String generateTravelAdvice(WeatherInfo weather, String startCity, String targetCity, String travelDate) { String prompt buildPrompt(weather, startCity, targetCity, travelDate); return callAiApi(prompt); } private String buildPrompt(WeatherInfo weather, String startCity, String targetCity, String travelDate) { StringBuilder sb new StringBuilder(); sb.append(你是一位资深的出行规划助手请根据以下天气信息和出行信息为用户提供实用的出行建议。\n); sb.append(建议内容需要包括穿衣搭配建议、交通方式推荐、出行注意事项、当天活动安排建议。\n); sb.append(要求语言简洁、条理清晰使用中文回答总字数控制在300字以内。\n\n); sb.append(【出行信息】\n); sb.append(出发地).append(startCity).append(\n); sb.append(目的地).append(targetCity).append(\n); sb.append(出行日期).append(travelDate).append(\n\n); sb.append(【目的地天气】\n); sb.append(天气现象).append(weather.getText()).append(\n); sb.append(温度).append(weather.getTemp()).append(℃体感温度).append(weather.getFeelsLike()).append(℃\n); sb.append(风力).append(weather.getWindDir()).append(weather.getWindScale()).append(\n); sb.append(湿度).append(weather.getHumidity()).append(\n\n); sb.append(请按以上要求生成建议。); return sb.toString(); } private String callAiApi(String prompt) { HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setBearerAuth(apiKey); MapString, Object body new HashMap(); body.put(model, model); body.put(max_tokens, maxTokens); body.put(temperature, temperature); ListMapString, String messages new ArrayList(); MapString, String systemMessage new HashMap(); systemMessage.put(role, system); systemMessage.put(content, 你是一个专业的出行规划助手。); messages.add(systemMessage); MapString, String userMessage new HashMap(); userMessage.put(role, user); userMessage.put(content, prompt); messages.add(userMessage); body.put(messages, messages); HttpEntityMapString, Object request new HttpEntity(body, headers); ResponseEntityString response restTemplate.exchange(baseUrl, HttpMethod.POST, request, String.class); try { JsonNode root objectMapper.readTree(response.getBody()); return root.path(choices).get(0).path(message).path(content).asText(); } catch (Exception e) { throw new RuntimeException(解析 AI 返回结果失败, e); } } }这段代码里有一个非常关键的细节Constructor 组装 JSON 结构时messages 是一个数组数组内每个元素包含 role 和 content 字段。如果你在这里遗漏了 role 字段或者把 messages 写成了对象而不是数组大模型接口会直接返回 400 错误。这是新手接入大模型 API 时最高频的报错之一。4.5 Controller 编写与参数传递Controller 主要负责整合天气服务和 AI 服务并把结果放入 Model 供 Thymeleaf 渲染。// 文件路径src/main/java/com/example/smarttravel/controller/WeatherController.java package com.example.smarttravel.controller; import com.example.smarttravel.dto.WeatherInfo; import com.example.smarttravel.entity.TravelNote; import com.example.smarttravel.entity.User; import com.example.smarttravel.service.AiTravelService; import com.example.smarttravel.service.TravelNoteService; import com.example.smarttravel.service.WeatherService; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import javax.annotation.Resource; import javax.servlet.http.HttpSession; Controller RequestMapping(/travel) public class TravelController { Resource private WeatherService weatherService; Resource private AiTravelService aiTravelService; Resource private TravelNoteService travelNoteService; GetMapping(/query) public String queryPage() { return travel/query; } PostMapping(/generate) public String generateAdvice(RequestParam String targetCity, RequestParam(required false) String startCity, RequestParam(required false) String travelDate, HttpSession session, Model model) { User currentUser (User) session.getAttribute(loginUser); if (currentUser null) { return redirect:/user/login; } if (startCity null || startCity.isEmpty()) { startCity currentUser.getCity() null ? 北京 : currentUser.getCity(); } WeatherInfo weather weatherService.getWeatherByCity(targetCity); String suggestion aiTravelService.generateTravelAdvice(weather, startCity, targetCity, travelDate); // 保存记录 TravelNote note new TravelNote(); note.setUserId(currentUser.getId()); note.setStartCity(startCity); note.setTargetCity(targetCity); note.setTravelDate(travelDate); note.setWeatherDesc(weather.getText()); note.setTemperature(weather.getTemp() ℃); travelNoteService.saveTravelNote(note); model.addAttribute(weather, weather); model.addAttribute(suggestion, suggestion); model.addAttribute(startCity, startCity); model.addAttribute(targetCity, targetCity); model.addAttribute(travelDate, travelDate); return travel/result; } }这里需要解释一下为什么用PostMapping提交表单。虽然 Thymeleaf 页面中表单请求也可以用 GET但生成 AI 建议这个操作会对数据库写入记录属于有副作用的操作按照 HTTP 语义应该使用 POST浏览器刷新时也会提示“重新提交表单”从数据安全角度更合理。5. 前端页面与 Thymeleaf 渲染5.1 页面整体设计前端项目使用 Thymeleaf 模板不需要单独搭建 Vue 或 React 工程这对计算机毕业设计来说大大降低了工作量。页面大致分成三个登录/注册页、查询页、结果展示页。查询页是核心页面包含一个表单提交区域和两个展示区域左侧显示天气信息卡片右侧显示 AI 出行建议。5.2 查询页模板在src/main/resources/templates/travel/query.html中编写查询表单!DOCTYPE html html langzh xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title智能天气出行服务/title link relstylesheet th:href{/css/style.css} /head body div classcontainer h2智能天气出行服务/h2 p classsubtitle输入目的地获取基于实时天气的 AI 出行建议/p form classquery-form th:action{/travel/generate} methodpost div classform-item label目的地城市/label input typetext nametargetCity placeholder请输入城市名称如杭州 required /div div classform-item label出发城市/label input typetext namestartCity placeholder默认读取个人设置中的常驻城市 /div div classform-item label出行日期/label input typedate nametravelDate /div button typesubmit生成出行建议/button /form div classtip p演示提示如果天气 API 未配置真实密钥系统将自动启用模拟天气数据。/p /div /div /body /html这里要注意th:action{/travel/generate}的作用是让 Thymeleaf 自动拼接当前项目的上下文路径。如果你在本地直接运行默认会渲染成/travel/generate如果部署到 Tomcat 并设置了 context-path则能自动带上项目名前缀不会出现路径 404 的问题。5.3 结果展示页模板在travel/result.html中展示天气和 AI 建议。关键语法是th:if判空和th:text文本渲染。!DOCTYPE html html langzh xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title出行建议结果/title link relstylesheet th:href{/css/style.css} /head body div classcontainer result-container h2出行建议结果/h2 div classresult-grid div classcard weather-card h3目的地天气/h3 p th:if${weather} span th:text${weather.city}杭州/span span th:text${weather.text}多云/span /p ul th:if${weather} li温度span th:text${weather.temp} ℃/span/li li体感温度span th:text${weather.feelsLike} ℃/span/li li风力span th:text${weather.windDir} ${weather.windScale}/span/li li湿度span th:text${weather.humidity}/span/li /ul /div div classcard ai-card h3AI 出行建议/h3 p th:if${suggestion} classai-text th:text${suggestion} 大模型返回的建议内容 /p p th:if${!suggestion} classerror-text建议生成失败请检查 AI 接口配置。/p /div /div div classinfo-line span出发地span th:text${startCity}/span/span span目的地span th:text${targetCity}/span/span span日期span th:text${travelDate}/span/span /div a classbtn-back th:href{/travel/query}继续查询/a /div /body /html在 Thymeleaf 中th:text${weather.temp} ℃可以正常拼接字符串。如果某个字段为空可以直接使用th:if${weather.temp ! null}做条件判断尽量避免在页面上展示 null 字样。这里也顺带解释一个新手常犯的错误Thymeleaf 的属性是写在 HTML 标签内部的如果你在 div 标签外单独写th:if模板解析会直接报错。所有 Thymeleaf 指令必须依附在具体标签上。5.4 页面样式与交互优化为了在答辩演示时视觉效果好可以适度编写一些简洁的 CSS 卡片样式。这里不做完整 CSS 展示但建议遵循以下几个原则整体页面宽度控制在 1000px 左右居中显示。卡片使用圆角、浅色边框、轻微阴影。结果页的天气卡片和 AI 卡片采用两列布局移动端则自适应单列。按钮颜色统一主操作按钮使用醒目色返回按钮使用弱化色。这些设计不需要很强的前端功底但能让界面看起来像一个完整的“系统”而不是裸 HTML。6. 运行验证与效果说明6.1 启动项目运行前需要准备MySQL 执行初始化 SQL在 application.yml 中修改数据库账号密码如果没有天气 API 和 AI API 密钥保持 mock-enabled: true启动 Spring Boot 项目。在项目根目录执行 Maven 命令mvn spring-boot:run或者将项目打包后运行mvn clean package -DskipTests java -jar target/smart-travel-0.0.1-SNAPSHOT.jar启动成功后控制台出现 Spring Boot 启动日志浏览器访问http://localhost:8080/travel/query6.2 演示流程建议登录系统后进入查询页。输入目的地“杭州”出发城市留空出行日期选择当天。点击“生成出行建议”系统会先查询杭州天气然后调用 AI 接口生成建议。如果使用 Mock 天气模式页面显示的是模拟的多云天气数据AI 建议则基于这段数据生成。演示时你可以提前准备好文本描述说明“这里展示的是系统如何把天气参数传给大模型并返回结构化建议”。如果希望展示真实天气效果可以申请天气 API 免费额度将weather.mock-enabled改为 false并填入有效 API Key。如果希望展示大模型的个性化效果可以尝试将 Prompt 中的“总字数控制在 300 字以内”改为“请用表格形式输出”模型会返回不同风格的答案这可以作为答辩时的亮点互动。6.3 验证点与预期输出项目运行正常时浏览器页面可以看到天气卡片区域显示城市名称、天气现象、温度、体感温度、风向风力、湿度。AI 卡片区域显示自然语言建议内容包含穿衣推荐、交通建议、活动安排等。数据库 travel_note 表新增一条记录包含用户、出发地、目的地、天气描述和温度。数据库 ai_suggestion_log 表新增一条记录包含 prompt_text 和 suggestion_text方便在答辩时展示实际发送给大模型的 Prompt 内容这是项目技术含金量的直接证明。7. 常见问题与排查思路7.1 启动类常见报错问题现象常见原因解决思路启动时报数据源连接失败MySQL 未启动或账号密码错误检查 MySQL 服务状态核对 application.yml 配置启动报 mapper 找不到Mapper 接口没有加 Mapper 注解在 Mapper 接口上添加 Mapper 或启动类加 MapperScanThymeleaf 页面 404templates 目录下路径不对确认页面存放在 resources/templates 下且路径与 Controller 返回值一致页面中文乱码编码设置不一致数据库连接 URL 添加 characterEncodingutf8页面文件保存为 UTF-87.2 AI 接口调用报错问题现象常见原因解决思路返回 401 错误API Key 无效或未设置检查 application.yml 中 ai.api-key返回 400 错误messages 结构不对检查 messages 是否为数组role 字段是否合法返回超时网络原因或 timeout-seconds 过短适当增大超时时间检查网络连通性返回内容异常Prompt 约束不够调整 Prompt增加字数、格式、范围限制这里重点提示一个容易忽略的点如果你在本地开发时使用了代理工具可能会影响 RestTemplate 正常请求外部 API导致连接超时或证书报错。排查时要先关闭系统代理或在 RestTemplate 中显式配置网络代理再逐步排除其他问题。另外国内环境访问部分大模型接口可能存在网络不稳定问题建议在项目文档中注明你可以替换为其他兼容 OpenAI 协议的服务商。替换时只需修改 base-url、model 和 api-key代码不需要大改这也是本设计方案的一个优点。7.3 天气接口返回空数据天气服务依赖城市编码查询。如果输入的是英文城市名或者城市名称包含后缀如“杭州市”与“杭州”可能查询不到对应编码。建议在 Controller 或 Service 层对参数做简单清洗比如去除“市”“省”等行政后缀并统一为城市名称后再调用天气 API。private String normalizeCity(String city) { if (city null) { return ; } return city.replace(市, ).replace(省, ).trim(); }这种方式虽然简单但能有效改善用户体验。8. 最佳实践与毕设答辩建议8.1 工程规范建议第一密钥信息不要硬编码在 Java 类中。哪怕项目只是毕业设计代码评审和答辩老师也很可能注意到硬编码的 API Key。把它们放在 application.yml 或环境变量中属于基本的代码安全意识。第二第三方接口调用一定要设置超时时间。RestTemplate 默认情况下可能长时间等待响应这会导致页面卡死。建议在 RestTemplate 配置中显式设置连接超时和读取超时。// 文件路径src/main/java/com/example/smarttravel/config/RestTemplateConfig.java package com.example.smarttravel.config; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; Configuration public class RestTemplateConfig { Bean public RestTemplate restTemplate() { PoolingHttpClientConnectionManager connectionManager new PoolingHttpClientConnectionManager(); connectionManager.setMaxTotal(100); connectionManager.setDefaultMaxPerRoute(20); CloseableHttpClient httpClient HttpClients.custom() .setConnectionManager(connectionManager) .build(); HttpComponentsClientHttpRequestFactory factory new HttpComponentsClientHttpRequestFactory(httpClient); factory.setConnectTimeout(5000); factory.setReadTimeout(30000); return new RestTemplate(factory); } }这样即使某个外部接口响应很慢也不会拖垮整个系统。第三用户密码不能明文存储。本系统示例为了简洁没有引入加密逻辑但实际项目中至少应该使用 BCrypt 加密。Spring Security 的BCryptPasswordEncoder或者 Hutool 的 BCrypt 工具类都可以只要在注册时将明文密码转为密文在登录时用 matches 校验即可。8.2 答辩演示技巧毕设答辩时老师通常不会只看界面更关注系统设计和技术亮点。建议从三个角度展开数据流讲解从用户输入城市开始沿着 Controller → Service → 第三方 API → 数据库 → Thymeleaf 渲染这条链路完整讲一遍。这是最稳妥的展示方式。异常处理展示演示前可以把天气 API 密钥故意填错展示系统自动降级为 Mock 数据的过程。这个细节能证明你考虑了系统稳定性比单纯展示正常流程更有说服力。Prompt 设计讲解把 ai_suggestion_log 表中的 prompt_text 展示出来和最终返回的建议对照讲解说明你是如何设计提示词的这是整个项目中 AI 部分的专业体现。8.3 项目文档与源码配套毕业设计通常要求提交论文LW和答辩 PPT。建议项目文档中重点写清楚技术选型理由、数据库设计说明、核心功能实现、系统测试结果。源码中关键方法建议写上注释尤其是 AI 接口调用和天气数据解析这两块老师翻阅源码时通常会直接看这些位置。PPT 结构可以按照“选题背景 → 需求分析 → 系统设计 → 功能实现 → 系统演示 → 总结展望”展开演示部分预留 5 分钟重点展示查询页和结果页再补充一张数据库表关系截图即可。9. 学习路线与后续扩展完成这个项目后你可以继续在以下几个方向扩展让系统从“课程设计水平”提升到“企业级项目思路”第一个扩展方向是缓存优化。目前每次查询都会实时调用天气 API可以考虑使用 Redis 缓存城市天气数据设置 30 分钟过期时间降低第三方 API 调用量同时提升页面响应速度。第二个扩展方向是接口鉴权。当前系统使用 Session 保存登录状态如果未来想要开发小程序或 App 端可以引入 Spring Security 或 JWT 做统一认证让同一套后端逻辑可以支撑多端访问。第三个扩展方向是大模型能力增强。目前 AI 建议是一次性生成后直接展示可以考虑引入流式输出也就是打字机效果还可以在 Prompt 中增加历史出行记录让建议更具个性化。第四个扩展方向是前端升级。把 Thymeleaf 页面替换为 Vue Element UI 或 React 页面后端改造成 RESTful API 格式系统就从服务端渲染架构过渡到了前后端分离架构。这个升级路径非常适合作为论文的“下一步展望”部分。从课程学习角度来看这个项目已经把 Java Web 开发的核心链路串起来了数据库设计、后端接口、页面渲染、外部接口对接、AI 能力集成。完成一遍之后你对 Spring Boot 项目的理解会从“跟着教程跑通 demo”提升到“独立设计并实现一个完整系统”这种能力的提升是刷再多视频教程都换不来的。如果本文对你有帮助建议收藏备用实际操作时遇到报错可以回来对照排查。动手把项目跑起来比反复看十篇教程都有用。