Spring Boot旅游指南系统实战:从零搭建可部署Web应用

发布时间:2026/9/14 11:19:28

Spring Boot旅游指南系统实战:从零搭建可部署Web应用 简介这是一套基于SpringBoot开发的旅游出行指南系统完整源码面向Java后端与全栈初学者适用于课程设计、毕业设计及旅游类Web应用快速原型开发。系统采用B/S架构前后端分离设计涵盖微信小程序UniApp/Vue、SpringBoot后端、MySQL数据库及MyBatisPlus持久层支持用户管理、图片与视频素材上传展示等核心功能。压缩包含721个文件主体为187个Java业务逻辑类、131个Vue页面组件、161个SVG图标资源及74个JPG/36个PNG媒体素材辅以JS交互脚本、CSS样式、YML配置及BAT启动脚本总大小24.02MB目录结构完整含详细文档如摘要、技术介绍、系统分析与实现章节。目前已有79人学习下载代码注释较充分模块划分清晰可直接导入IDE运行调试是理解旅游服务类系统从需求分析到前后端联调落地的典型实践案例。1. 一个能跑起来的旅游出行指南网站为什么非得用 Spring Boot 而不是纯 Servlet 或 JSP你手头有一份标着“基于 SpringBoot 的旅游出行指南系统代码”的压缩包解压后看到pom.xml里写着spring-boot-starter-web、spring-boot-starter-thymeleaf、spring-boot-starter-data-jpa还有application.yml和一堆Controller类——这不是教学 Demo而是一个真实可部署的轻量级旅游信息网站支持景点分类浏览、城市筛选、图文详情页、用户收藏、后台管理入口。它不依赖 Tomcat 手动部署 WAR 包也不用写web.xml配置过滤器链mvn spring-boot:run启动后http://localhost:8080就直接打开首页连静态资源路径都自动映射好了。这类系统适合中小型旅行社做官网导流、高校课程设计展示、或自由开发者接单交付原型——它把 Java Web 开发中重复率最高的配置、路由、数据绑定、模板渲染全封装掉了让你专注在「怎么展示九寨沟的开放时间」「如何按海拔排序高原景点」这些业务逻辑上。如果你还在用传统 Servlet 写doGet()处理 URL 参数、手动拼 HTML 字符串返回页面那这份 Spring Boot 代码就是你跳过十年胶水代码的快捷通道。2. 从空目录到首页可访问Spring Boot 旅游指南系统的最小可运行骨架2.1 初始化项目结构与核心依赖选择依据新建 Maven 项目时pom.xml必须包含三类基础 Starterspring-boot-starter-web提供内嵌 Tomcat 和 REST 接口能力这是所有 Web 系统的底座spring-boot-starter-thymeleaf替代 JSP 实现服务端模板渲染它天然支持 HTML 原生语法前端改.html文件无需重启对旅游网站这种强内容展示型应用极其友好spring-boot-starter-data-jpa封装 Hibernate让景点、城市、攻略等实体用Entity注解就能映射数据库避免手写 JDBC 模板和 SQL 字符串拼接——尤其当你要查「云南大理下关区海拔低于2000米的4A级景区」时JPQL 查询比原生 SQL 更易维护。提示不要引入spring-boot-starter-security到初始版本。旅游指南是公开信息站强行加登录拦截反而阻碍首页加载等后台管理模块如景点编辑开发完成后再按需启用。以下是最简pom.xml关键片段Spring Boot 3.2.x 兼容 Jakarta EE 9parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.7/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope /dependency /dependenciesh2是内存数据库开发阶段免去 MySQL 安装和建库操作启动即用。正式部署时只需替换为mysql-connector-j并修改application.yml中的spring.datasource.url即可。2.2 主启动类与全局配置文件的必填项src/main/java/com/example/travel/TravelGuideApplication.java是整个系统的入口SpringBootApplication public class TravelGuideApplication { public static void main(String[] args) { SpringApplication.run(TravelGuideApplication.class, args); } }SpringBootApplication自动启用组件扫描、自动配置和 Spring MVC无需额外 XML。但必须配合application.yml才能生效server: port: 8080 servlet: context-path: /travel spring: datasource: url: jdbc:h2:mem:traveldb driver-class-name: org.h2.Driver username: sa password: jpa: database-platform: org.hibernate.dialect.H2Dialect hibernate: ddl-auto: create-drop show-sql: true properties: hibernate: format_sql: true thymeleaf: cache: false enabled: true prefix: classpath:/templates/ suffix: .html encoding: UTF-8 servlet: content-type: text/html关键参数说明server.servlet.context-path: /travel表示所有接口前缀为/travel/避免与本地其他服务冲突jpa.hibernate.ddl-auto: create-drop在每次启动时重建表结构适合开发期快速迭代上线必须改为validatethymeleaf.cache: false关闭模板缓存改完 HTML 立刻生效省去反复重启spring.datasource.url使用 H2 内存模式jdbc:h2:mem:traveldb表示数据库名为traveldb关闭时自动销毁无残留。2.3 第一个控制器让首页http://localhost:8080/travel/返回景点列表创建src/main/java/com/example/travel/controller/HomeController.javaController public class HomeController { GetMapping(/) public String home(Model model) { // 模拟从数据库查出热门景点实际应注入 JPA Repository ListScenicSpot spots Arrays.asList( new ScenicSpot(九寨沟, 四川阿坝, 世界自然遗产以高山湖泊群著称, 5), new ScenicSpot(西湖, 浙江杭州, 人间天堂苏堤春晓、断桥残雪, 5), new ScenicSpot(鼓浪屿, 福建厦门, 万国建筑博览钢琴之岛, 4) ); model.addAttribute(spots, spots); return index; // 对应 templates/index.html } }对应src/main/resources/templates/index.html!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title旅游出行指南/title /head body h1热门景点推荐/h1 div th:eachspot : ${spots} h3 th:text${spot.name}景点名称/h3 p span th:text${spot.location}所在地/span/p p span th:text${spot.description}简介/span/p p⭐ span th:text${spot.rating} /5评分/span/p hr/ /div /body /html启动应用后访问http://localhost:8080/travel/页面将渲染出三个景点卡片。注意 Thymeleaf 的th:text会自动转义 HTML 字符防止 XSS若需插入富文本如攻略中的br改用th:utext。3. 数据驱动用 JPA 实体建模景点、城市与用户行为3.1 三张核心表的设计逻辑与注解含义旅游指南系统的核心数据模型围绕「地点」展开需至少三张表scenic_spot景点存储名称、位置、描述、评分、所属城市 IDcity城市存储城市名、省份、经纬度、是否热门旅游地user_favorite用户收藏记录用户 ID 与景点 ID 的多对多关系此处简化为无用户登录的匿名收藏用 IP 地址代替 user_id。对应 Java 实体类如下使用 Lombok 简化 getter/setterEntity Table(name scenic_spot) Data public class ScenicSpot { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(nullable false) private String name; Column(nullable false) private String location; // 格式四川阿坝 Column(columnDefinition TEXT) private String description; private Integer rating; // 1-5 分 ManyToOne(fetch FetchType.LAZY) JoinColumn(name city_id) private City city; Column(name created_at, updatable false) CreationTimestamp private LocalDateTime createdAt; } Entity Table(name city) Data public class City { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(unique true, nullable false) private String name; // 如杭州 private String province; // 如浙江 private Double latitude; private Double longitude; Column(name is_popular) private Boolean isPopular false; } Entity Table(name user_favorite) IdClass(UserFavoriteId.class) Data public class UserFavorite { Id private String ipAddress; // 匿名用户用 IP 标识 Id private Long spotId; // 关联景点 ID Column(name favorited_at) CreationTimestamp private LocalDateTime favoritedAt; } // 复合主键类 Data public class UserFavoriteId implements Serializable { private String ipAddress; private Long spotId; }关键注解说明ManyToOne(fetch FetchType.LAZY)表示查询景点时不立即加载城市信息避免 N1 查询问题CreationTimestamp由 Hibernate 自动填充创建时间无需手动 setIdClass用于复合主键比EmbeddedId更直观且UserFavoriteId必须实现SerializablecolumnDefinition TEXT显式指定 MySQL 的 TEXT 类型避免 H2 默认的 VARCHAR(255) 截断长描述。3.2 Repository 接口定义与 JPQL 查询实战创建ScenicSpotRepository接口继承JpaRepository即可获得findAll()、findById()等 20 方法Repository public interface ScenicSpotRepository extends JpaRepositoryScenicSpot, Long { // 按城市名模糊查询景点JPQL Query(SELECT s FROM ScenicSpot s JOIN s.city c WHERE c.name LIKE %:cityName%) ListScenicSpot findByCityName(Param(cityName) String cityName); // 按评分降序取前 10原生 SQL兼容性更强 Query(value SELECT * FROM scenic_spot ORDER BY rating DESC LIMIT 10, nativeQuery true) ListScenicSpot findTopRated(); // 按位置关键词搜索H2 支持 FULLTEXT不支持改用 LIKE ListScenicSpot findByNameContainingOrDescriptionContaining(String keyword, String keyword2); }在 Controller 中调用Controller RequestMapping(/spots) public class SpotController { private final ScenicSpotRepository spotRepository; public SpotController(ScenicSpotRepository spotRepository) { this.spotRepository spotRepository; } GetMapping(/top10) public String top10(Model model) { model.addAttribute(spots, spotRepository.findTopRated()); return top10; } GetMapping(/search) public String search(RequestParam String q, Model model) { // 注意此处 q 直接传入生产环境必须校验长度并过滤 SQL 关键字 model.addAttribute(spots, spotRepository.findByNameContainingOrDescriptionContaining(q, q)); model.addAttribute(keyword, q); return search-result; } }注意findByNameContainingOrDescriptionContaining自动生成的 SQL 是WHERE name LIKE ? OR description LIKE ?两个参数值相同。若要支持多关键词如“西湖 断桥”需改用Query手写 JPQL 并拆分字符串。3.3 初始化测试数据用data.sql在启动时预置 20 个景点在src/main/resources/data.sql中插入初始数据H2 启动时自动执行INSERT INTO city (name, province, latitude, longitude, is_popular) VALUES (杭州, 浙江, 30.2641, 120.1551, true), (成都, 四川, 30.5728, 103.7234, true), (昆明, 云南, 25.0443, 102.7052, true); INSERT INTO scenic_spot (name, location, description, rating, city_id, created_at) VALUES (西湖, 浙江杭州, 苏堤春晓、断桥残雪、雷峰夕照, 5, 1, NOW()), (灵隐寺, 浙江杭州, 始建于东晋江南著名古刹, 4, 1, NOW()), (宽窄巷子, 四川成都, 清代古街道融合老成都风貌, 4, 2, NOW()), (春熙路, 四川成都, 成都最繁华商业街网红打卡地, 3, 2, NOW()), (滇池海埂大坝, 云南昆明, 冬季红嘴鸥聚集地免费观鸥, 5, 3, NOW()); -- 此处省略其余 15 条...Spring Boot 默认启用spring.sql.init.modeembedded只要data.sql存在且数据库为空就会在首次启动时执行。验证方法启动后访问http://localhost:8080/h2-consoleH2 控制台输入JDBC URL: jdbc:h2:mem:traveldb即可看到已建好的表和数据。4. 页面交互升级Thymeleaf 表单提交与收藏功能落地4.1 构建景点详情页与收藏按钮的完整流程详情页SpotDetailController需同时处理 GET 请求展示和 POST 请求收藏Controller RequestMapping(/spots) public class SpotDetailController { private final ScenicSpotRepository spotRepository; private final UserFavoriteRepository favoriteRepository; public SpotDetailController(ScenicSpotRepository spotRepository, UserFavoriteRepository favoriteRepository) { this.spotRepository spotRepository; this.favoriteRepository favoriteRepository; } GetMapping(/{id}) public String detail(PathVariable Long id, Model model) { ScenicSpot spot spotRepository.findById(id) .orElseThrow(() - new RuntimeException(景点不存在)); model.addAttribute(spot, spot); // 查询该景点被收藏次数简化版实际应加 Redis 缓存 long count favoriteRepository.countBySpotId(id); model.addAttribute(favoriteCount, count); return spot-detail; } PostMapping(/{id}/favorite) public String favorite(PathVariable Long id, HttpServletRequest request) { String ip getClientIpAddress(request); // 防止重复收藏检查该 IP 是否已收藏此景点 if (!favoriteRepository.existsById(new UserFavoriteId(ip, id))) { UserFavorite favorite new UserFavorite(); favorite.setIpAddress(ip); favorite.setSpotId(id); favoriteRepository.save(favorite); } return redirect:/spots/ id; } private String getClientIpAddress(HttpServletRequest request) { String xForwardedFor request.getHeader(X-Forwarded-For); if (xForwardedFor ! null !xForwardedFor.isEmpty()) { return xForwardedFor.split(,)[0].trim(); } return request.getRemoteAddr(); } }对应templates/spot-detail.html!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title th:text${spot.name} - 旅游出行指南景点详情/title /head body h1 th:text${spot.name}景点名称/h1 p span th:text${spot.location}所在地/span/p p span th:utext${spot.description}简介/span/p p⭐ span th:text${spot.rating} /5评分/span/p p❤️ 已被 span th:text${favoriteCount}0/span 人收藏/p !-- 收藏表单 -- form th:action{/spots/ ${spot.id} /favorite} methodpost button typesubmit收藏此景点/button /form a href/spots返回列表/a /body /html关键点说明th:utext用于渲染含 HTML 标签的description如br换行表单th:action动态生成/spots/1/favorite路径避免硬编码getClientIpAddress()优先读取X-Forwarded-For头反向代理场景否则用getRemoteAddr()existsById()检查复合主键是否存在比先查再 save 更高效。4.2 静态资源与图片路径的正确配置方式旅游网站必然需要景点图片。Spring Boot 默认将src/main/resources/static作为静态资源根目录因此图片存放在src/main/resources/static/images/scenic/下HTML 中引用为img th:src{/images/scenic/hangzhou-west-lake.jpg} /{}是 Thymeleaf 的 URL 表达式自动添加context-path即/travel最终解析为/travel/images/scenic/...。若需上传图片不应直接存到static目录重启应用会丢失而应创建UploadService将文件保存到服务器磁盘如/var/www/travel-images/在application.yml中配置spring.web.resources.static-locationsfile:/var/www/travel-images/前端通过POST /api/upload接口上传后端返回相对路径如/uploads/20240701_abc.jpg存入数据库。提示开发阶段可临时用spring.resources.static-locationsclasspath:/static/,file:./uploads/将./uploads目录设为额外静态路径便于快速测试。4.3 城市筛选导航栏的动态生成逻辑首页需显示「热门城市」标签栏点击跳转到该城市景点列表。这需要从City表查出is_popular true的城市并生成带参数的链接GetMapping(/) public String home(Model model) { ListCity popularCities cityRepository.findByIsPopularTrue(); model.addAttribute(popularCities, popularCities); ListScenicSpot hotSpots spotRepository.findTopRated(); model.addAttribute(spots, hotSpots); return index; }templates/index.html中添加导航div classcity-nav span热门城市/span th:block th:eachcity : ${popularCities} a th:href{/spots/city(name${city.name})} th:text${city.name}杭州/a span th:if${#lists.indexOf(popularCities, city) #lists.size(popularCities)-1}|/span /th:block /div对应新增 Controller 方法GetMapping(/spots/city) public String spotsByCity(RequestParam String name, Model model) { ListScenicSpot spots spotRepository.findByCityName(name); model.addAttribute(spots, spots); model.addAttribute(cityName, name); return city-spots; }city-spots.html模板复用index.html的循环结构仅标题改为h1 th:text${cityName} 景点推荐杭州景点推荐/h1。这样既避免重复代码又保证 URL 可被搜索引擎收录如/travel/spots/city?name杭州。5. 生产就绪打包部署、性能调优与常见报错排查5.1 生成可执行 JAR 包并部署到 Linux 服务器Maven 打包命令生成 fat jar含所有依赖mvn clean package -DskipTests输出文件位于target/travel-guide-0.0.1-SNAPSHOT.jar。上传至服务器后用以下命令后台运行nohup java -jar -Dspring.profiles.activeprod travel-guide-0.0.1-SNAPSHOT.jar app.log 21 参数说明-Dspring.profiles.activeprod激活生产配置需在src/main/resources/application-prod.yml中覆盖数据库连接为 MySQLnohup使进程脱离终端 app.log 21将标准输出和错误重定向到日志文件后台运行。验证服务是否启动curl -I http://localhost:8080/travel/ # 应返回 HTTP/1.1 200 OK若需开机自启创建 systemd 服务文件/etc/systemd/system/travel-guide.service[Unit] DescriptionTravel Guide Spring Boot App Afternetwork.target [Service] Typesimple Userdeploy WorkingDirectory/opt/travel ExecStart/usr/bin/java -jar /opt/travel/travel-guide-0.0.1-SNAPSHOT.jar Restartalways RestartSec10 [Install] WantedBymulti-user.target然后执行sudo systemctl daemon-reload sudo systemctl enable travel-guide sudo systemctl start travel-guide5.2 三个高频报错的定位与修复方案报错现象日志关键词根本原因解决方案Caused by: java.lang.ClassNotFoundException: javax.servlet.FilterClassNotFoundException: javax.servlet.*Spring Boot 3.x 迁移至 Jakarta EE 9包名从javax.*变为jakarta.*检查所有依赖是否为 Spring Boot 3.x 兼容版本特别是thymeleaf-extras-java8time需升级到4.0.0删除web.xml和servlet-api依赖Field userRepository in com.example.travel.controller.UserController required a bean of type com.example.travel.repository.UserRepository that could not be foundrequired a bean of type ... that could not be foundRepository 接口未被 Spring 扫描到确认EnableJpaRepositories注解存在通常SpringBootApplication已包含且接口包路径在主类同包或子包下检查Repository是否遗漏There is already an object named scenic_spot in the databaseobject named xxx in the databaseH2 数据库未清空ddl-auto: create-drop失效删除target/目录下h2临时文件或改用ddl-auto: update并手动执行DROP ALL OBJECTS特别注意ddl-auto: update的风险它只新增字段不会删除废弃列长期使用会导致表结构混乱。建议开发期用create-drop上线前用 Flyway 或 Liquibase 管理迁移脚本。5.3 JVM 参数调优与内存泄漏防护默认 JVM 参数对旅游网站足够但高并发时需调整添加-Xms512m -Xmx1024m固定堆内存避免频繁 GC添加-XX:UseG1GC启用 G1 垃圾收集器降低停顿时间添加-Dfile.encodingUTF-8防止 Linux 系统默认编码导致中文乱码。在application-prod.yml中开启 Actuator 端点监控内存management: endpoints: web: exposure: include: health,info,metrics,heapdump,threaddump endpoint: health: show-details: when_authorized访问http://your-server:8080/travel/actuator/heapdump可下载堆快照用 VisualVM 分析内存占用。重点检查Thymeleaf模板是否因cache: true导致大量Template对象驻留UserFavorite实体未加Transactional时save()调用可能引发 Session 泄漏静态资源如大图未启用 Nginx 缓存导致每次请求都走 Spring Boot。最后用jstat -gc pid实时观察 GC 情况若FGCTFull GC 时间持续增长说明存在内存泄漏需结合 heapdump 定位对象引用链。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/9/14 11:19:28

SEO流量变现魔法:如何利用反向链接打造赚钱机器

在当今竞争激烈的搜索引擎优化 (SEO) 环境中,反向链接仍然是提高网站排名和有机流量的关键因素之一。反向链接是指指向您网站的链接,来自其他网站。搜索引擎将反向链接视为对您网站内容质量和权威性的投票。 获取高质量反向链接对于提高网站排名和可见性…

2026/9/14 11:19:28

纯HTML随机点名器:TXT导入、离线运行、零依赖

简介:这是一份面向网页设计初学者与前端教学场景的HTMLJavaScript互动工具实战资源,专为教师课堂点名、活动主持或Web入门练习设计。资源通过纯前端技术实现txt名单导入与随机点名功能,无需后端支持,即开即用,兼顾实用…

2026/9/14 11:14:27

[python] ylgy攻略 用魔法打败魔法

文章目录1. 引言2. 源码分析:MatchPlayInfo 的生成逻辑3. 序列化过程详解protobuf 与 JSON 序列化对比4. 关键问题:数值大于 127 导致失败5. 实战:Python 脚本一键通关6. 总结1. 引言 通过逆向分析微信小程序「羊了个羊」的源码,…

2026/9/14 12:09:32

C++实现HTTP服务器及阿里云ECS部署实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/14 12:04:32

SpringBoot+Vue音乐推荐系统:协同过滤全流程实现

简介:这是一套基于Spring Boot与Vue实现的协同过滤音乐推荐系统,专为计算机专业本科生毕业设计、课程设计及期末大作业打造,兼顾算法原理理解与全栈工程实践,适合零基础开发者快速上手。资源包共893个文件,涵盖104个Ja…

2026/9/14 2:17:50

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/14 0:03:22

KCF目标跟踪算法与OTB工程实现:毕业设计实战解析

简介:这是一份基于KCF核相关滤波算法、融合尺度池与抗遮挡处理的目标检测跟踪MATLAB完整源码,主要面向计算机相关专业准备毕业设计、课程设计或期末大作业的学生,也适合需要项目实战练习的初学者。源码在OTB数据集上完成验证,能够…

2026/9/14 0:03:22

语音情感识别实战:Keras实现LSTM、CNN、SVM与MLP多模型对比

简介:面向语音情感识别入门与进阶开发者,这份基于Keras的项目源码完整实现了LSTM、CNN、SVM、MLP四种模型,兼容Python3.8与Keras/TensorFlow2环境。压缩包内含49个文件,大小约70.31MB,主体包括Python脚本、yaml/json配…

2026/9/14 11:59:31

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/12 14:32:17

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/14 11:22:57

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码