news 2026/9/8 14:13:42

JSP酒店管理系统开发实战:从表设计到权限控制与Excel导出

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JSP酒店管理系统开发实战:从表设计到权限控制与Excel导出

简介:一套基于JSP与Struts2框架的酒店管理系统项目,面向正在学习Java Web开发的学生或初级开发者,可用于理解MVC分层、业务逻辑与视图分离的实践应用。系统涵盖房间管理、预订管理、入住退房、客户管理和账单计算等核心模块,并配有SQL脚本与必要配置文件,便于快速搭建运行环境。压缩包共138个文件,以Java源码、Class编译文件、JSP页面为主,另含XML配置、CSS/JS样式脚本、TLD标签库定义及Eclipse项目文件等,整体体积约2.94MB,目录结构清晰,适合对照源码逐层分析。已有568人浏览学习,资源完整度较高,既能作为课程设计参考,也能帮助深入掌握JSP、Struts2与数据库交互的常见开发技巧。 做了大半个月的 JSP 酒店管理系统,最近终于把整个流程跑通了。这套东西在大学里几乎快被做烂了,但真的上手之后会发现,网上那些代码能直接抄的不多,能跑通又不翻车的更少。我这次把需求、表设计、权限控制、视频播放、 Excel 导出这些点一个个趟了一遍,写篇总结给准备做同类项目的人,免得你有样学样还踩坑。

这个项目其实能拆得很清晰:前端 JSP + Servlet,后端 MySQL,中间用 JDBC 连接,典型 JavaWeb 三层结构。你想要做毕设、期末课设,或者只是想练手熟悉 Servlet 和 JSP,这套代码的逻辑都是绕不开的。我这里不会只贴代码,还会把每一步为什么这么设计说明白,尤其是几个经常出问题的地方,比如 JSP 改了不生效、MP4 播放不出来、Excel 导出中文乱码,这些我都会放到后面单独讲。

1. 项目概述与需求拆解

1.1 为什么酒店管理系统总爱选 JSP 技术栈

每次看到题目带“JSP”就觉得是老项目,但放到现在看,JSP 反而是很多高校软件专业教学的“最佳载体”。原因其实很现实:它足够老,但老得有代表性。

JSP 本质上是 Servlet 的模板化扩展,第一次被访问时会转成 Servlet,再编译执行。所以 JSP 页面里既可以直接写 HTML,又可以在<% %>里写 Java 代码,这种“前后端不分家”的写法在真实企业里是灾难,但用来理解 Web 请求响应模型、Session 管理、表单提交这些基础逻辑,效率非常高。

我做这套系统时,技术选型上其实犹豫过要不要用 Spring Boot + Thymeleaf。后来想想,既然题目要求 JSP,倒不如老老实实把 Servlet、Filter、Listener 这些底层东西搞清楚。做完再回头看,你对 HTTP 协议、请求转发与重定向、Cookie 与 Session 的理解,比直接上手框架的人要扎实得多。这也是为什么即便现在新项目没人用 JSP,老系统维护和课设题却一直没断过。

1.2 核心角色与功能边界

做任何系统,先定角色。酒店管理系统我最终拆成三个角色:管理员、前台、普通客户(也可以叫会员)。不是角色越多越好,而是每个角色要有清晰的功能边界,不然你后面写权限控制时会疯掉。

我最后定的功能清单是这样的:

  • 管理员:员工管理、房型管理、价格设置、订单查看与统计、数据导出。
  • 前台:客户登记、开房、换房、退房结算、订单查询、入住记录维护。
  • 客户(前台代操作或自助注册):个人信息查看与修改、历史订单查询。

这里有一个容易忽略的点:房间状态。一开始我只设计成“空闲 / 已入住”,结果后来发现退房后房间需要打扫,于是又加了一个“脏房”状态。如果你想做得更完整,房间状态应该包括:空闲、已预订、入住中、脏房、维修中。状态之间的转换逻辑尽量写在 Service 层,不要散落在 JSP 页面里。

另外,权限控制我用最经典的 Filter + Session 实现:用户登录后把 user 对象放进 Session,Filter 里判断请求路径是否是受保护资源,是的话就检查 Session 是否存在 user,不存在就重定向到登录页。这个方案虽然简单,但足够应付项目演示。

2. 数据库与后端核心设计

2.1 数据表怎么拆才不会被怼

表结构是一个项目能不能拿得出手的关键。我踩过最大的坑就是把所有状态都塞进一张订单表,最后查询时 SQL 写得像蜘蛛网。这次我重新设计,核心表拆成五张:用户表、员工表、房型表、房间表、订单表(兼入住记录)。

房间表和房型表分开是有讲究的。房型存的是房型名称、床型、面积、门市价、会员价这些通用属性;房间表存的是房间号、楼层、所属房型 ID、当前状态。如果不拆,你在房型下加一个房间就得复制一堆字段,数据冗余不说,改价格的时候会疯掉。

核心表结构大致是这样的:

表名关键字段说明
t_userid, username, password, phone, real_name客户账号,密码需要 MD5 加密存储
t_employeeid, emp_no, password, emp_name, role, status员工账号,role 区分管理员和前台
t_room_typeid, type_name, bed_type, area, price, member_price房型与价格
t_roomid, room_no, floor, type_id, status房间状态:空闲/预订/入住/脏房/维修
t_orderid, order_no, user_id, room_id, check_in_date, check_out_date, total_price, status订单和入住记录,status:已预订/入住中/已退房/已取消

有一个细节:订单表里我存了room_idcheck_in_date/check_out_date,查询可用房间时,就需要找出这些时间段跟新订单时间重叠的房间并排除掉。判断日期重叠的 SQL 是:

SELECT * FROM t_room r WHERE r.status = '空闲' AND NOT EXISTS ( SELECT 1 FROM t_order o WHERE o.room_id = r.id AND o.status IN ('已预订', '入住中') AND o.check_in_date < #{newCheckOut} AND o.check_out_date > #{newCheckIn} );

日期右开还是左开闭合,建议根据你的业务定,我在演示项目里约定的是:入住当天下午两点算入住,退房当天中午十二点前算退房。这类细节写进项目说明里,会显得你很专业。

2.2 Servlet、Service、DAO 到底怎么分

很多初学者写 JSP 系统,喜欢直接在 JSP 页面里写 JDBC,连数据库。这样页面改版一次就要改无数行代码,后来维护真是欲哭无泪。我的分层思路很传统但好用:

  • JSP:只负责展示,页面里尽量用 EL 表达式和 JSTL 标签,不写 Java 脚本片段。
  • Servlet:负责接收请求、调用 Service、把结果放进 request 或 session、再转发或重定向到 JSP。
  • Service:业务逻辑层,比如预订时校验房间是否存在、状态是否空闲、计算总价。
  • DAO:数据库操作,一层只做 CRUD,代码要尽量薄。

一个关键点是请求转发和重定向的选择。新增、修改、删除这些操作,操作完建议使用response.sendRedirect()重定向,然后再通过查询接口去列表页。如果直接forward到 JSP,用户刷新页面时会重复提交表单,数据就有可能出现重复或逻辑混乱。

2.3 JDBC 连接与事务处理的坑

连接数据库我用的是 Druid 连接池,配置文件放在src下,通过Properties加载。阿里巴巴的这个连接池在监控和防注入上都比较省心,比直接DriverManager.getConnection()稳定很多。

事务边界我放在 Service 层。开房操作其实是一个组合动作:大小订单表记录、修改房间状态、如果客户余额不够需要充值。这些操作要么全成功,要么全回滚。我的代码逻辑大致是这样:

Connection conn = null; try { conn = JDBCUtils.getConnection(); conn.setAutoCommit(false); // 1. 插入订单记录 // 2. 更新房间状态 // 3. 更新用户余额 conn.commit(); } catch (Exception e) { conn.rollback(); throw new RuntimeException("开房失败", e); } finally { JDBCUtils.close(conn); }

事务不回滚的问题特别隐蔽。有时候插入成功但房间状态没改,你会觉得是代码写错了,其实是没加事务导致部分失败。排查这种问题时,先看数据库里脏数据,再回去看事务边界,很快能定位。

3. 关键页面与功能实现

3.1 登录、会话与权限控制的一次成型写法

登录页这块,我最开始直接用session.setAttribute("user", user),后面发现用户没登录也能直接访问订单管理页面,只好返工加 Filter。所以建议一开始就把 Filter 加上,免得后面补。

写一个AuthFilter,在web.xml里配置过滤路径:

<filter> <filter-name>AuthFilter</filter-name> <filter-class>com.example.filter.AuthFilter</filter-class> </filter> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

Filter 里的逻辑很直接:放行登录页、CSS、JS、图片等静态资源,其余请求检查 Session。不要忘了静态资源,否则你会发现登录页的样式全丢了,排查半天才发现是被 Filter 拦住。

还要注意 JSP 页面本身最好放在WEB-INF目录下,这样用户没法通过浏览器直接访问 JSP 文件,只能经过程序跳转。这个细节在老项目里不太常见,但面试时提出来很加分,也能避免绕过权限问题的风险。

3.2 个人中心页面:JSP 中展示用户信息的标准姿势

“JSP 个人信息展示页面”这个话题看起来简单,但很多新手会犯一个致命错误:在 JSP 里这么写<%= request.getSession().getAttribute("user") %>然后取属性,甚至直接用<%写一堆循环逻辑。页面一多,管理起来非常困难。

推荐的做法是配合 JSTL 和 EL 表达式。比如,用户登录后进入个人中心,后端 Servlet 设置:

request.setAttribute("user", user); request.getRequestDispatcher("/WEB-INF/jsp/user/profile.jsp").forward(request, response);

然后在 JSP 页面里展示:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <div class="card"> <p>姓名:${user.realName}</p> <p>手机号:${user.phone}</p> <p>注册时间:<fmt:formatDate value="${user.createTime}" pattern="yyyy-MM-dd HH:mm"/></p> </div>

这样写不仅页面干净,还能自动做 HTML 转义,降低 XSS 风险。如果你在 JSP 里直接用<%= user.getRealName() %>,变量值里如果含有脚本内容,很容易被注入。看似是展示问题,实际上是安全问题。

还有一个容易被忽略的地方:个人中心页面需要展示“我的订单”列表,这里一定要做分页。哪怕数据量只有几十条,也要把分页逻辑做出来,毕竟这是演示项目里一个非常加分的亮点。我用的方案是 PageBean 封装当前页、总记录数、总页数、每页记录数,Service 层计算起始下标,DAO 层用LIMIT ?, ?查当前页数据。

3.3 JSP 页面里播放 MP4,没那么简单

“JSP 实现 MP4 视频播放”这个需求一般是做酒店房间展示、宣传视频或操作引导用的。简单做法是<video src="video/intro.mp4" controls></video>,但实际跑起来会发现三个高频问题:跨域、路径错误、MIME 类型不对。

先说路径。如果你把视频放在webapp/video下,页面路径应该是相对于上下文根的。我推荐用${pageContext.request.contextPath}拼接,比如:

<video controls width="720" height="480"> <source src="${pageContext.request.contextPath}/video/intro.mp4" type="video/mp4"> 您的浏览器不支持 video 标签。 </video>

这里最容易翻车的是你直接把视频文件扔到了src/main/resources下,导致发布后的webapps目录里根本找不到这个文件。视频属于 Web 资源,要放在 WebContent/webapp 目录下。

第二个坑是 Tomcat 默认对未知 MIME 类型的文件处理很保守,有的版本不主动返回video/mp4,浏览器就会拒绝播放。解决办法是在web.xml里加 Spring(如果是 Spring MVC)的资源映射,或者直接用 Tomcat 的默认映射。我由于这套是原生 Servlet,就简单地保证文件后缀是.mp4,本地测试没遇到太大问题。如果你用的 IDE 内置浏览器,记得排除是浏览器兼容问题。

最后说下视频加载慢的问题。如果放的是几百 MB 的酒店宣传片,每次刷新都重新加载,体验会非常差。建议压缩转码,输出为 H.264 编码的 MP4,加上preload="metadata",这样页面加载时不会立刻下载完整视频。

3.4 数据导出 Excel,用 POI 五分钟搞定

“JSP 实现数据导出为 Excel”基本是报表模块的标配。我这次导出的内容是订单明细表,包含订单编号、客户姓名、房型、入住时间、退房时间、实付金额等字段。

我用 Apache POI 的XSSFWorkbook生成.xlsx文件,关键点不在生成 Excel,而在 HTTP 响应头设置。直接贴一下我的一个导出 Servlet 关键代码:

response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); String fileName = URLEncoder.encode("订单报表_" + DateUtils.getToday(), "UTF-8"); response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".xlsx"); XSSFWorkbook workbook = new XSSFWorkbook(); XSSFSheet sheet = workbook.createSheet("订单列表"); // 填充头部和数据行... workbook.write(response.getOutputStream()); workbook.close();

这里容易踩的坑有三个:

  • 第一,文件名里有中文,必须用URLEncoder.encode,否则下载下来的文件名是乱码。
  • 第二,写完数据后记得关闭 workbook,否则文件可能不完整或占用资源。
  • 第三,POI 的版本要注意和 JDK 匹配。我用的是 JDK 8 + POI 4.x,太高版本需要 JDK 9 以上,别因为版本不匹配浪费一下午。

导出时我顺手做了一件事:导出的数据先按退房时间倒序排序,这样财务拿到的报表最新记录都在前面。这种细节虽然不动筋骨,但演示时很戳老师或客户的点。

4. 部署、热更新与问题排查

4.1 最容易让人崩溃的问题:JSP 改了不生效

“JSP 改了不生效”绝对是做 JSP 项目最普遍的痛。你明明改了页面代码,刷新浏览器还是旧页面,第一反应是代码没保存,第二反应是 IDE 坏了,其实原因往往在下面几个地方。

第一个原因:Tomcat 的work目录缓存。JSP 第一次访问会被翻译成 Java 文件再编译成 class,这个结果默认缓存在 Tomcat 的work/Catalina/localhost目录下。如果 IDE 没有把改动同步到部署目录,等于服务器还在跑老版本的 class。解决方法是:在 IDE 里重新发布项目,或者直接去work目录下把对应项目文件夹删掉,再重启 Tomcat。

第二个原因:浏览器缓存。很多浏览器为了提高加载速度,会缓存 JSP 对应的 HTML 结果。调试时按Ctrl + F5强制刷新,而不是普通刷新。

第三个原因:项目部署目录没更新。比如用 Eclipse 开发时,改动 JSP 后没有执行Publish to Tomcat,服务器上的文件就没更新。这里我建议你关闭 IDE 的自动构建,或者设置成保存后自动发布,养成“每次改动后看一眼 console 是否重新加载”的习惯。

我自己的调试习惯是:优先看 Tomcat console 有没有重新编译 JSP 的日志,比如Reloading Context或者Compiling JSP,有就说明服务器感知到了变化,还显示旧页面就是浏览器缓存;没有就说明部署目录没同步,先把发布问题解决。

4.2 中文乱码的三大来源,一次讲透

JSP 项目的乱码,十有八九是编码不一致。但我遇到的乱码分三种,处理方式完全不同:

  • 页面乱码:JSP 文件编码和响应编码不一致。JSP 开头必须有<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>,并且在修改其他字符集之前先统一成 UTF-8。
  • 请求参数乱码:POST 请求参数中文乱码,在 web.xml 配置CharacterEncodingFilter,Spring MVC 用现成的CharacterEncodingFilter;原生 Servlet 就自己加一个 Filter,设置request.setCharacterEncoding("UTF-8")
  • 数据库乱码:JDBC URL 要加useUnicode=true&characterEncoding=UTF-8,而且数据库表本身也要 utf8。

我这次项目里最诡异的乱码出现在 Excel 导出:数据是从数据库查出来的,在 JSP 页面上显示正常,导出来却乱码。最后发现是导出 Servlet 里写流之前没有设置response.setCharacterEncoding("UTF-8")。所以写导出类时代码顺序一定不能乱:先设置 Content-Type,再设置字符集,再设置 Content-Disposition,最后拿输出流,一步都不能省。

4.3 数据库连接池与并发时的隐藏雷区

酒店管理系统在演示时不会有太大并发,但你也要考虑两个并发场景:同一个房间被两个人同时预订,以及退房和换房同时操作。

解决思路是悲观锁或者乐观锁。我在查询可用房间后,执行插入订单前,使用条件更新方式抢占房间,也就是把更新房间状态的 SQL 写成:

UPDATE t_room SET status = '已预订' WHERE id = ? AND status = '空闲'

如果更新影响行数为 0,说明房间在这几毫秒内被人抢占了,直接回滚订单并提示客户选择其他房间。这种做法简单可靠,比 Redis 分布式锁更适合这种课设项目。

连接池参数也要注意。Druid 的初始连接数、最大活跃连接数、最大等待时间要配成合理值。有些项目直接把maxActive配到几百,数据库根本扛不住,连接池强行扛到最后就是卡死。我的配置是初始 5,最大 20,等待 3 秒,不上不下,完全够演示。

提示:用 Druid 时一定要留监控页面账号,或者至少打开 StatFilter 日志,方便在项目答辩时展示“系统运行期间 SQL 执行次数、慢查询记录”,这东西在实际评分时真的加分。

4.4 部署时最容易忽略的路径问题

最后一个大坑是部署环境路径差异。我开发时在 Eclipse 里能正常访问,一打成 WAR 包丢到 Linux 服务器上,很多图片、视频、CSS 全挂了。原因百变,但根子基本都是用了相对路径,或者硬编码了/MyProject/images/logo.png

正确做法是所有资源路径都使用${pageContext.request.contextPath}前缀,或者通过<c:url>生成完整路径。比如:

<link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css"> <script src="${pageContext.request.contextPath}/js/common.js"></script> <img src="${pageContext.request.contextPath}/upload/room/1.jpg" alt="">

上传的图片这类动态资源,要新建一个虚拟目录映射到物理磁盘上的upload文件夹,不要把图片直接塞到项目里。因为 Tomcat 重新部署 WAR 包时,项目目录会被清理,图片就全没了。我就是栽过一次,重新部署后所有房间照片全丢,后台管理的上传图片功能看起来像 bug。正确姿势是在conf/server.xml里配<Context>虚拟目录,或者用 Spring MVC 的静态资源配置,让/upload/**映射到服务器磁盘地址。

最后的实操心得

说实话,做完这个 JSP 酒店管理系统,我最深的感受是:项目难点从来不是某一个功能,而是把登录、权限、房间状态流转、订单日期判断、导出编码这些细节全部串起来不崩。JSP 技术确实年迈,但正是因为它没有太多“魔法”,你才能在一次一次调试中想明白 Web 应用到底是怎么跑起来的。

如果你现在也是边学边做,我建议你先别急着追求花哨功能,把“开房、退房、换房、查房”这套最核心的流程跑通,再把 Excel 导出、视频播放这类加分项加上。每完成一个模块,就顺手写一句注释或者记录一个坑,最后答辩或写报告时,你会有非常多真实素材可以讲,而不是只能满口框架名词。

还有一个经验:尽量在项目里多预留扩展点。比如我在房间状态机里预留了“维修中”状态,在订单表里预留了remark字段,后来客户要求加“取消原因”时,几乎没改表结构。这种面向小变化的设计,在老系统里特别常见,也是 JSP 项目绕不开的一项基本功。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 14:12:33

128GB统一内存轻松跑大模型:Ryzen AI Max+ 395实战Qwen3.8

最近在Ryzen AI Max 395处理器、128GB内存的笔记本上&#xff0c;我把Qwen3.8-flash-next完整跑通了&#xff0c;从裸机到稳定出文字大概折腾了一个晚上。坦白说&#xff0c;之前我对“核显跑大模型”这件事一直持保留态度&#xff0c;毕竟本地推理最吃显存容量和带宽&#xff…

作者头像 李华
网站建设 2026/9/8 14:12:02

AI Slop治理实战:从特征识别到清洗降权的完整策略指南

先说一个我最近观察到的现象&#xff1a;你在搜索引擎里搜一个稍微有点冷门的问题&#xff0c;翻到第二页&#xff0c;开始出现大量“看似回答得很工整、实则废话连篇”的文章。标题规整、结构清晰、小标题齐全&#xff0c;但读下去你会发现&#xff0c;它根本没回答你的问题&a…

作者头像 李华
网站建设 2026/9/8 14:06:11

Vue3 watch与watchEffect核心原理与实战避坑指南

1. 从"为什么改了半天视图没反应"说起&#xff1a;watch的正确打开方式做Vue3开发的人&#xff0c;十有八九都经历过这样一个场景&#xff1a;接口返回了数据&#xff0c;明明赋值给了响应式变量&#xff0c;控制台打印也能看到新值&#xff0c;页面却像被冻住一样毫…

作者头像 李华
网站建设 2026/9/8 14:05:09

NGS与机器学习实战:从特征工程到变异检测的完整指南

简介&#xff1a;面向下一代测序与机器学习交叉领域的入门资源包&#xff0c;以Conda环境配置和交互式笔记本为载体&#xff0c;并配有C语言程序&#xff0c;适合生物信息学初学者、生信工程师或对基因组数据建模感兴趣的开发者快速入门。压缩包内共有六个文件&#xff0c;涵盖…

作者头像 李华
网站建设 2026/9/8 14:01:20

Spring Boot+Vue快递物流管理系统:运单状态与轨迹设计实战

1. 为什么这套系统一上来就要先看业务&#xff0c;而不是先建表Spring Boot Vue 快递物流管理系统&#xff0c;几乎是我被问到最多的全栈实战项目标题之一&#xff0c;尤其是毕业设计、课程设计和转行自学人群里&#xff0c;这个题目出现的频率高得惊人。我之前接过不少类似的…

作者头像 李华
网站建设 2026/9/8 14:01:04

毕业论文修改的抉择:传统方法、AI 工具与专业平台如何选

毕业论文修改的抉择&#xff1a;传统方法、AI 工具与专业平台如何选 毕业论文提交前&#xff0c;几乎每位同学都会面临同一个难题&#xff1a;如何高效、安全地完成文本修改与降重。面对传统同义词替换、通用大模型改写、专业论文处理工具等多种路径&#xff0c;选择往往令人纠…

作者头像 李华