拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于Spring Boot+Thymeleaf的旅游景点酒店预订网站设计与实现

简介这是一套面向计算机专业本科生的毕业设计实战项目基于SpringBootThymeleafMySQL构建的旅游景点与酒店预订网站完整覆盖电商类系统核心业务场景适用于课程设计、毕设选题及Java全栈能力进阶学习。资源包共487个文件含51个Java后端逻辑文件、34个HTML模板页、67个JS交互脚本、37个CSS样式文件、101张JPG/PNG/GIF景点与酒店图片以及关键的SQL建库脚本、Redis配置、RabbitMQ消息配置等整体压缩包仅15.13MB轻量易部署。已有163人下载学习可直接运行并二次开发——不仅提供完整前后端源码与数据库脚本还集成阿里云SMS短信注册、QQ邮箱找回密码、OSS图片存储、LayuiBootstrap双前端框架、ECharts数据可视化及Restful风格API设计等企业级实践模块结构清晰、注释规范是理解半前后分离架构与Spring生态整合的优质参考案例。1. 项目概述1.1 为什么要做这个旅游景点酒店预订网站这几年毕业设计选Java Web方向的同学不少但大多数人都在纠结做什么题目。管理系统、商城、博客都做烂了答辩老师一眼就能看出你是在网上找的模板还是自己真写的。旅游景点酒店预订这个方向我个人比较推荐因为它足够贴近真实业务场景又比纯电商系统少了很多复杂的支付、库存、物流逻辑适合在毕业设计的时间周期内完整落地。而且它的核心链路非常清晰用户查看景点、搜索酒店、下订单、管理订单、管理员维护数据这套流程几乎是酒店旅游行业的通用模型拿到答辩现场也说得清楚。当然选这个题目还有一个很现实的原因它好演示。答辩时老师最怕看到你PPT上贴一堆代码真正打开系统却什么都点不动。这个网站天然具备完整的交互流程从注册登录、浏览商品到下单支付模拟、订单状态变化都能在浏览器里一步步展示。哪怕老师要求现场操作你也能很从容地跑完整条链路这在答辩环节是非常加分的。1.2 项目整体功能预览这个系统我分了前台和后台两块。前台面向普通游客提供了景点列表、景点详情、酒店列表、酒店详情、关键词搜索、用户注册登录、个人信息维护、酒店预订下单、订单查询与取消等功能。后台面向管理员提供景点管理、酒店管理、订单管理、用户管理等功能。有一点需要注意用户和管理员我建议放在同一张表里通过角色字段区分。如果你拆成两张表登录逻辑要写两套菜单权限也要套两套组件麻烦不说答辩时还容易被追问“为什么这么设计”。数据层面设计了四张核心表景点表、酒店表、订单表和用户表。景点表和酒店表都不复杂无非是名称、描述、图片地址、价格这些常规字段订单表相对费一点心思因为要同时关联用户、酒店、房间类型这些信息。整体技术栈就是三件套Spring Boot做后端框架Thymeleaf做模板引擎MySQL存数据。下面我重点说这几个技术选型背后的考量为什么不用JSP为什么不用Vue前后端分离这些都是答辩时老师大概率会追问的点。2. 技术选型与核心设计思路2.1 为什么是Spring Boot而不是SSH或SSM如果你去翻以前的老教程会发现很多旅游网站项目还在用SSHStruts2 Spring Hibernate甚至Servlet JSP那套玩法。不是说那些技术本身有问题而是今天再用它们写毕业设计性价比实在太低了。Spring Boot最大的价值在于它把大量的配置自动化了。传统SSM项目你要写web.xml、spring-mvc.xml、mybatis-config.xml、applicationContext.xml还要处理一堆jar包版本冲突。Spring Boot只要一个pom.xml加上application.yml就能把项目跑起来。这就意味着你可以把精力花在业务逻辑上而不是折腾配置。Spring Boot在毕业设计里的另一个隐形优势是方便部署。传统的SSM项目你要打成war包丢进Tomcat的webapps目录Spring Boot可以直接打成可执行的jar包服务器上装个JDK就能跑演示的时候用java -jar命令一下就能启动。你想想答辩现场那点时间这个方法能帮你省下多少手脚。2.2 Thymeleaf和JSP、Freemarker怎么选JSP是真的不建议再用在新项目里了。性能放一边最大的痛点是它要在运行时由JSP引擎编译成Servlet启动慢、报错信息不直观。Spring Boot对JSP的支持也很别扭需要引入额外的依赖而且打包成jar之后JSP页面默认访问不了你得改成war包放外部Tomcat绕了一圈又回去了。Freemarker和Thymeleaf都是可以在Spring Boot里直接跑起来的模板引擎性能上Freemarker略好一点但实际那点差异在你的项目体量下根本感知不出来。我更推荐Thymeleaf原因有三点第一Thymeleaf的语法更接近HTML所有的动态内容都在标签属性里控制页面直接用浏览器打开也能看到静态效果前端调试起来友好很多第二Spring Boot对Thymeleaf的支持非常完善官方文档和网上的例子多基本你遇到的每个问题都能搜到答案第三Thymeleaf的表达式语言和Spring MVC的Model传递天然契合后端往前端传数据几乎不用做额外的转换。Thymeleaf的核心语法其实很好上手常用的就那几个th:text用来输出文本th:each用来循环列表th:if判断条件th:href拼链接。掌握了这些整个页面的动态渲染你就已经会了八成。2.3 为什么不用前后端分离很多同学会纠结要不要上Vue Spring Boot前后端分离给自己毕业设计增加一点分量。我的看法是如果你对前端有扎实的基础做前后端分离没问题。但如果你只是会基本的HTML/CSS/JavaScript那服务端渲染是更稳妥的选择。前后端分离意味着你要同时维护两套工程解决跨域问题、处理Token认证、设计RESTful接口文档还得会调试前后端联调那一套流程。工作量至少增加三分之一。而Thymeleaf服务端渲染的方式后端写好Controller直接返回视图名数据都在Model里页面通过Thymeleaf语法直接取出来一个人从头写到尾非常顺。而且老师检查项目的时候看到的是完整的Controller返回视图逻辑从请求到页面渲染的链路一目了然也更容易讲清楚。这不是技术落后是明确的目标导向。3. 数据库设计与脚本编写细节3.1 核心表结构怎么设计数据库是整个系统的基础表设计不好后面写代码处处难受。我先说订单表结构的设计思路。订单表我命名为travel_order字段包括id主键、order_no订单号、user_id用户ID、hotel_id酒店ID、room_type房间类型、check_in_date入住日期、check_out_date离店日期、total_price总金额、status订单状态、create_time创建时间。订单号我推荐用时间戳加随机数的方式生成这样的好处是唯一且不可猜测看起来也像真实业务系统中的订单号。不要用数据库自增ID直接当订单号那样既暴露了订单量也不够专业。用户表我设计了username、password、phone、email、role这几个核心字段role用Integer类型0表示普通用户1表示管理员。password必须存加密后的值不能存明文。我在项目里用的BCrypt加密这是Spring Security里自带的一套加密算法即使你的项目没有集成Spring Security也可以单独引入spring-security-crypto依赖来用非常方便。景点表和酒店表结构类似大致有name、description、image_url、price、address这些字段。酒店表额外加了一个hotel_type酒店类型比如经济型、舒适型、豪华型方便筛选。3.2 数据库脚本的编写要点数据库脚本这个部分看起来简单其实有一些细节需要注意。如果你准备用Navicat或者MySQL Workbench直接建表导出SQL文件的时候要注意选择正确的导出选项里面会附带很多CREATE TABLE和INSERT语句。但我更推荐你自己手写一份建表脚本这样你对表结构的关系更清晰而且答辩时老师问起某张表的设计缘由你张口就能答上来。脚本里需要注意几个点字符集要统一设置为utf8mb4如果你用了默认的latin1或者utf8中文和特殊字符比如表情符号就有可能出现乱码或者插入失败的情况。排序规则选择utf8mb4_general_ci即可。表的引擎用InnoDB这是支持事务和外键的引擎对订单这种需要保证数据一致性的表很重要。字段长度要合理description用TEXT类型其他短文本用VARCHAR(50)或VARCHAR(255)就够。id用BIGINT自增主键不要用INT防止以后数据量大了不够用。外键这个东西我建议你建也建议你在脚本里保留。虽然很多生产环境的项目会逻辑删除外键来提高性能但毕业设计需要展示你对数据库设计的理解有外键约束反而是一个加分项。比如订单表的user_id关联用户表的idhotel_id关联酒店表的id删除用户或者酒店的时候数据库层面会自动限制防止产生孤儿数据。3.3 初始化数据的坑写INSERT初始化数据的时候要注意图片路径的问题。我最初写的是http://localhost:8080/images/hotel1.jpg这样写死的本地路径结果部署到服务器上演示时就全部裂图了。后面改成了相对路径/images/hotel1.jpg图片统一放在src/main/resources/static/images目录下这个目录是Spring Boot的默认静态资源路径项目打成jar包后图片也会被正确打包进去不需要额外配置。景点介绍和酒店描述的初始化数据最好写得丰富一些。有些同学为了省事description字段就填一句话“这是一个很好的酒店”放到页面上非常难看而且老师一看就知道你是在敷衍。我是模仿携程和美团上的文案写的大概三四行的描述包含酒店的特色、周边景区、交通情况等。这种细节的东西才是让你和那些网上下载代码的同学拉开差距的地方。4. 核心功能实现与关键代码解析4.1 项目初始化与基础配置创建Spring Boot项目我用的是Spring Initializrstart.spring.io。版本这里有一个容易踩坑的地方Spring Boot 3.x和2.x的差别非常大3.x要求JDK 17以上并且很多第三方组件的兼容性还不够稳定。对于毕业设计来说我强烈建议选择Spring Boot 2.7.x系列这个版本是2.x最后的稳定版本兼容JDK 8和JDK 11网上能找到的资料和踩坑解决方案也是最多的。pom.xml里需要引入的依赖就几个spring-boot-starter-web、spring-boot-starter-thymeleaf、mybatis-spring-boot-starter我用的是MyBatis作为持久层框架、mysql-connector-java、lombok如果你习惯写getter/setter可以不用。不要为了显得高大上引入一堆用不到的依赖依赖越多版本冲突的概率就越大启动报错排查起来也越麻烦。application.yml的基础配置如下server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/travel_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 thymeleaf: cache: false mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.travel.entity有一个重点需要提醒你datasource的url里必须加上serverTimezoneAsia/Shanghai。如果你不加这个参数8.0版本的MySQL驱动在连接数据库时会报时间序列化的错误严重的话项目直接启动失败。这不是你的代码写错了是完全新手必踩的坑。4.2 首页热门景点和酒店推荐怎么实现首页是一个网站的门面也是答辩老师打开系统后看到的第一个页面。我花了比较多心思在首页上。打开首页后页面上展示的是几条景点推荐和酒店推荐列表这是通过Controller在请求首页时从数据库查出所有数据然后取前四条展示出来的。Controller public class IndexController { Autowired private HotelService hotelService; Autowired private ScenicService scenicService; GetMapping(/) public String index(Model model) { ListHotel hotelList hotelService.getAllHotels(); ListScenic scenicList scenicService.getAllScenics(); model.addAttribute(hotelList, hotelList); model.addAttribute(scenicList, scenicList); return index; } }这里有一个关于取前四条数据的细节。直接在SQL里用LIMIT 4没问题但如果你先查出所有数据再在Java层截断数据量小的时候看不出差别数据量大了性能会明显下降。我推荐用SQL直接限制查询条数这样数据库只需要查询四条记录而不是查完所有数据再丢弃。Thymeleaf模板的写法大概是这样的div classhotel-card th:eachhotel : ${hotelList} img th:src${hotel.imageUrl} th:alt${hotel.name} h3 th:text${hotel.name}酒店名称/h3 p th:text${hotel.address}酒店地址/p p classpricespan th:text${hotel.price}399/span 起/p /div4.3 搜索功能与分页查询搜索是用户使用频率最高的功能之一。用户会在搜索框输入关键词比如“杭州”或者“西湖”系统需要根据关键词同时匹配景点名称和酒店名称甚至匹配地址也行。我的实现方式是写了一个通用的查询接口前端通过GET请求传递keyword参数后端在Service层根据关键词去拼接SQL查询条件。Override public ListHotel searchHotels(String keyword) { return hotelMapper.searchHotels(keyword); }对应的Mapper XML文件里这么写select idsearchHotels resultTypecom.example.travel.entity.Hotel SELECT * FROM hotel WHERE name LIKE CONCAT(%, #{keyword}, %) OR address LIKE CONCAT(%, #{keyword}, %) ORDER BY create_time DESC /select分页功能我二话没说直接用的PageHelper这是MyBatis生态里非常成熟的一个分页插件。使用方式很简单在查询之前调用PageHelper.startPage(pageNum, pageSize)后面紧跟的查询方法就会自动被加上LIMIT条件。然后用PageInfo包装结果前端就能拿到当前页数据和总页数。PageHelper.startPage(pageNum, 6); ListHotel list hotelMapper.getAllHotels(); PageInfoHotel pageInfo new PageInfo(list); model.addAttribute(pageInfo, pageInfo);4.4 用户登录注册与拦截器机制登录注册是几乎所有系统都需要的功能。注册的逻辑比较简单前端提交用户名、密码、手机号后端先查询用户名是否已存在如果已存在就返回提示不存在就调用加密方法处理密码然后插入用户表。登录的时候先根据用户名查询用户记录然后用BCrypt的matches方法比对密码是否匹配。public User login(String username, String password) { User user userMapper.findByUsername(username); if (user ! null BCryptPasswordEncoder.matches(password, user.getPassword())) { return user; } return null; }登录状态我用的是Session保存登录成功之后把用户对象放到Session里。这里不需要引入Spring Security和Shiro那些完整的权限框架毕竟毕业设计的重点是业务流程不是安全体系。但要拦截未登录用户的访问不能让他们直接访问个人中心和下单接口。我的做法是写一个HandlerInterceptorpublic class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); User user (User) session.getAttribute(user); if (user null) { response.sendRedirect(/login); return false; } return true; } }然后在配置类里注册这个拦截器并指定拦截路径。注意必须放行登录页、注册页、静态资源和首页这些不需要登录就能访问的路径不然会死循环重定向。4.5 酒店预订流程的实现预订是整个项目最核心的流程。用户在酒店详情页选择入住日期和离店日期系统会根据房价和入住天数自动计算总价用户点击提交订单后系统生成一条状态为“待付款”的订单记录。为了让毕业设计流程完整我加了一个模拟支付的步骤——生成订单后跳转到支付页面用户点击“模拟支付”按钮订单状态更新为“已支付”。关于总价计算的细节很多人会直接在JS里算完把价格传给后端这是不安全的做法。用户可以通过浏览器开发者工具把价格改掉。正确的做法是后端根据酒店的单价和入住天数自己计算总价前端只负责展示。天数计算我用的是两行Java代码long days (checkOutDate.getTime() - checkInDate.getTime()) / (1000 * 60 * 60 * 24); BigDecimal totalPrice hotel.getPrice().multiply(BigDecimal.valueOf(days));注意日期计算要考虑到当天入住当天离店的情况天数不能为0。还有一个细节是跨月、跨年的日期计算如果你用Calendar类很容易踩坑建议直接使用LocalDate或着Java 8的时间API它们对日期运算的支持好得多。订单状态的维护我用了一个整型字段0待付款、1已付款、2已取消。前端对应的状态文本用Thymeleaf条件判断来显示不是从数据库直接读中文文本这样以后改状态名称很方便。5. 常见问题与排查技巧实录5.1 项目启动失败的常见原因毕业设计阶段遇到的最多的问题就是项目启动失败。我总结了一下百分之八十都是下面这几种情况数据库没启动。很多人写完代码启动项目看到红字就慌了其实你只需要去任务管理器里看MySQL服务是否在运行。Windows下最稳妥的方式是打开“服务”窗口找到MySQL服务确认状态是“正在运行”。Linux下用systemctl status mysql查看。如果是服务没启动压根不用改代码启动服务再跑项目就通了。端口被占用。Spring Boot默认端口是8080如果你的机器上有其他程序占用了这个端口启动日志里会明确提示Port 8080 was already in use。解决办法很简单要么找到占用端口的进程杀掉要么在application.yml里把server.port改成8081。如果你用的是IDEA可以直接在Run Configuration里加上--server.port8081作为程序参数不用改配置文件。数据库连接配置错误。这是最容易忽略的问题。root密码写错了、数据库名不存在、url里忘记写characterEncoding和serverTimezone这些都会导致启动时报Failed to configure a DataSource或者Access denied for user。真遇到这种情况不要瞎猜打开浏览器用Navicat测试一下同样的账号密码能不能连上数据库很快就定位到问题了。缺少依赖。如果你是从别人那边拿的代码经常遇到本地Maven仓库里没有项目依赖的jar包。解决办法是在IDEA右侧Maven面板里执行reload all maven projects让它重新下载一遍依赖。网络不好的情况下多试几次或者用阿里云的镜像仓库加速这个配置在maven的settings.xml里。5.2 Thymeleaf模板渲染踩过的坑Thymeleaf报错信息经常是SpelEvaluationException或者TemplateInputException新手看了完全摸不着头脑。经过多次踩坑我发现最常见的原因是实体类属性名和模板里写的属性名拼写不一致。实体类里写了imageUrl模板里写了$ {hotel.imageUrl }如果实体类属性是private String imageUrlgetter方法叫getImageUrlThymeleaf能正确访问。但如果你把getter方法写成了getimageUrl或者类型不匹配就会报错。建议属性名用驼峰模板引用时严格一致。另一个坑是th:each循环里访问外层对象。比如在酒店列表里每个卡片都要显示酒店对应的景点名称而景点信息是单独的对象。这种情况下你需要把景点信息先封装成Map或者修改实体类增加一个关联字段而不是试图在循环里去查询数据库。最笨也最稳妥的方式是在Controller里把景点名称查好放到一个Map里然后把这个Map也放进Model模板里用$ {scenicMap[hotel.scenicId]}来取。Thymeleaf的缓存问题也值得一提。开发调试阶段你改了HTML页面刷新浏览器经常看不到变化。原因就是Thymeleaf默认开启了缓存。我在application.yml里加了一句spring.thymeleaf.cache: false关闭开发环境的模板缓存改完页面刷新就能看到最新效果。这点别看小能极大提升你的开发效率。5.3 数据库中文乱码问题中文乱码是最常见的数据库问题之一但也是最容易解决的。乱码的根源是客户端连接数据库时的编码和数据库本身的编码不一致。你需要在三个地方保证字符集统一。数据库创建的时候指定字符集CREATE DATABASE travel_db DEFAULT CHARACTER SET utf8mb4。建表时也指定字符集CREATE TABLE hotel (...) ENGINEInnoDB DEFAULT CHARSETutf8mb4。JDBC连接字符串里加上characterEncodingutf8。三处都配置好之后重新建库、重新导入数据中文就不会乱码了。如果你已经导入了数据才暴露乱码问题不要急着改配置先把现有数据清理掉重新建库导入一次。乱码问题常常在数据导入阶段就已经弄脏了光改配置救不回来已经存入的脏数据还不如推倒重来干净。5.4 打包部署的坑与经验开发阶段一切正常打包部署的时候却出了状况这也是很多同学会遇到的。我那次遇到的问题是项目在IDEA里运行没问题用mvn package打成jar包后java -jar运行却提示找不到主类。排查了半天发现是pom.xml里缺少spring-boot-maven-plugin插件。这个插件负责把项目打包成可执行的Fat Jar缺少它打包出来的jar根本不能独立运行。解决办法是在pom.xml里加上build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build还有一个经典问题jar包打出来了但图片加载不出来。原因就是你静态资源路径引用不对前面提到过图片资源放在static目录下同时注意模板中的路径要以/开头写成/images/x.jpg。部署到Linux服务器上时如果你的jar包是通过nohup java -jar xxx.jar 方式启动的日志会输出到nohup.out文件里后面排查问题都要靠它路径别乱删。6. 项目讲解与答辩技巧6.1 功能演示的最佳路线毕业设计答辩时功能演示环节是真正的重头戏。演示的时候不要从首页开始一个个点那会显得没有逻辑。我的建议是设计一条演示主线先介绍系统的前台打开首页展示热门景点和酒店推荐接着搜索关键词表现系统具备检索能力然后注册一个新用户登录后挑选一个酒店下单模拟支付最后进入个人中心查看订单记录。这条主线走完系统的核心功能基本都展示到了老师在下面也能跟上你的节奏。后台管理的演示我放在最后。管理员登录后可以看到所有订单信息也可以进行景点的增删改查。这里有一个小技巧提前准备一条要添加的新景点数据演示时现场添加展示完整的前台和后台联动的效果。现场写数据、现场展示比你截图说明更能证明这是你自己写的项目。6.2 被问到设计思路时怎么回答答辩环节老师一定会问“为什么这么设计”的问题。最容易被问到的几个为什么选择Thymeleaf表之间是什么关系为什么用Session不用JWT密码是怎么加密的。每一种你都要能说出自己的想法哪怕只是简单的考虑也比支支吾吾强很多。这里给你提供一个话术参考。比如问到为什么用Session因为这是一个传统的服务端渲染项目Thymeleaf页面天然适合Session方案不需要额外处理跨域和Token刷新问题对项目的规模来说Session已经足够安全。问到订单状态怎么设计我用的是整型状态码而不是直接在数据库存中文这样扩展性更好以后如果需要增加“已入住”“退款中”等状态不需要修改表结构只需要在代码里加一个常量映射就行。这种回答能体现你的逻辑思考能力哪怕这个设计其实是最基础的做法。6.3 后续还能怎么扩展如果你的项目做完以后还有精力我建议增加几个小功能来提升项目的完整度。一个是图片上传功能让管理员在后台可以直接上传景点图片而不是只能依靠预设的图片路径这个功能会涉及到文件上传和静态资源映射说出去也是一个亮点。另一个是数据统计功能后台首页展示订单量、用户量、收入这些汇总数据可以用简单的SQL聚合查询实现再配合图表插件展示成柱状图和折线图这个加分的分量还是很足的。我实际测试过在原有的订单表基础上做一个近七天的订单量统计核心SQL就一条GROUP BY的查询外加一个前端图表组件的HTML片段半天时间就可以搞定。但如果我当初答辩前做了这个功能演示的时候会觉得整个项目档次高了一个级别。7. 写在最后这个旅游景点酒店预订网站项目从数据库设计到功能实现再到部署上线我前前后后花了大约三周时间。其中第一周基本在看文档、搭环境、确认技术方案真正写代码的时间集中在后两周。如果你也是零基础起步我建议预留一个月的时间每天保持三到四个小时的投入时间是够用的。最想给你的一条建议是不要复制粘贴网上现成的毕业设计代码哪怕最终功能的思路是参考别人的也一定要自己一行行把代码敲出来。只有自己亲手写过你才知道Controller和Service之间怎么解耦才知道Thymeleaf的th:each嵌套有多容易搞混变量名才知道MySQL的外键约束在删数据的时候会跳出来提醒你。这些经验和教训才是你做这个毕业设计真正得到的东西。工作之后回头看你才会发现这段经历有多值钱。另外演示数据库的时候尽量带上几组真实、美观的初始数据。景点介绍和酒店描述写详细一点价格设置成带小数的格式看起来更接近真实系统。这些小细节会在一定程度上影响答辩老师对你项目完成度的判断。祝你顺利通过答辩也祝你在这个项目里真正体会到Java Web开发的乐趣。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门