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 对象放进 SessionFilter 里判断请求路径是否是受保护资源是的话就检查 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_id和check_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-nameAuthFilter/filter-name filter-classcom.example.filter.AuthFilter/filter-class /filter filter-mapping filter-nameAuthFilter/filter-name url-pattern/*/url-pattern /filter-mappingFilter 里的逻辑很直接放行登录页、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 prefixc urihttp://java.sun.com/jsp/jstl/core % div classcard p姓名${user.realName}/p p手机号${user.phone}/p p注册时间fmt:formatDate value${user.createTime} patternyyyy-MM-dd HH:mm//p /div这样写不仅页面干净还能自动做 HTML 转义降低 XSS 风险。如果你在 JSP 里直接用% user.getRealName() %变量值里如果含有脚本内容很容易被注入。看似是展示问题实际上是安全问题。还有一个容易被忽略的地方个人中心页面需要展示“我的订单”列表这里一定要做分页。哪怕数据量只有几十条也要把分页逻辑做出来毕竟这是演示项目里一个非常加分的亮点。我用的方案是 PageBean 封装当前页、总记录数、总页数、每页记录数Service 层计算起始下标DAO 层用LIMIT ?, ?查当前页数据。3.3 JSP 页面里播放 MP4没那么简单“JSP 实现 MP4 视频播放”这个需求一般是做酒店房间展示、宣传视频或操作引导用的。简单做法是video srcvideo/intro.mp4 controls/video但实际跑起来会发现三个高频问题跨域、路径错误、MIME 类型不对。先说路径。如果你把视频放在webapp/video下页面路径应该是相对于上下文根的。我推荐用${pageContext.request.contextPath}拼接比如video controls width720 height480 source src${pageContext.request.contextPath}/video/intro.mp4 typevideo/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加上preloadmetadata这样页面加载时不会立刻下载完整视频。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 contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %并且在修改其他字符集之前先统一成 UTF-8。请求参数乱码POST 请求参数中文乱码在 web.xml 配置CharacterEncodingFilterSpring MVC 用现成的CharacterEncodingFilter原生 Servlet 就自己加一个 Filter设置request.setCharacterEncoding(UTF-8)。数据库乱码JDBC URL 要加useUnicodetruecharacterEncodingUTF-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 relstylesheet 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 项目绕不开的一项基本功。本文还有配套的精品资源点击获取