资讯动态

JavaWeb酒店系统实战:手写JDBC事务与并发订房设计

发布时间:2026/10/1 5:24:24 来源:尧图企业网站定制
简介本资源是一套高分通过的Java Web课程设计项目——酒店客房管理系统面向计算机专业本科生及Java初学者解决课程设计、期末大作业与Web开发实践场景中的系统建模、前后端交互与数据库集成等核心问题。压缩包共73个文件含31个Java业务逻辑与Servlet类、17个HTML页面实现用户界面、4个CSS样式文件保障基础展示效果以及SQL数据库脚本、配置文件yml/properties/xml和前端字体资源等整体仅467KB轻量易部署。已有538人学习下载适合作为教学参考或快速上手案例。读者可直接运行系统完整覆盖客房管理员用户/房间/房型维护、营业统计与前台人员客房查询、订房录入、退房结算双角色功能代码结构清晰采用主流SSM技术栈附带hotel.sql建库脚本与README说明无需修改即可启动验证业务流程。1. 这不是又一个“学生交差项目”JavaWeb酒店客房管理系统为什么能成为你简历里真正扛得住压测的实战锚点很多人看到“课程设计-基于Javaweb的酒店客房管理系统源码数据库.zip”第一反应是——哦又是那种JSPServletMySQL堆出来的课堂作业登录页写死密码、房间状态全靠手动刷新、增删改查没事务、连分页都用for循环硬算。但现实是2024年校招中83%的Java后端岗面试官明确表示愿意为“能讲清并发订房扣减逻辑”“能现场调通数据库连接池泄漏”“能指出JSP页面中EL表达式失效的真实原因”的应届生多给一轮技术面。这个看似普通的.zip包恰恰卡在了教学项目与工业级代码的临界线上它用最朴素的技术栈无Spring Boot自动装配、无MyBatis动态SQL黑盒把HTTP协议层、Servlet生命周期、JDBC连接管理、事务边界控制、HTML表单与JavaBean映射这些被面试高频追问的底层机制全部暴露在明面上。它不炫技但每行代码都在回答“请求进来后到底发生了什么”。适合刚学完《Java Web编程技术》想验证知识闭环的人也适合工作2年的开发者回炉重看——当你发现连“退房时库存回滚失败导致房间数变负”这种Bug都要自己手写try-catchrollback时才真正理解Transactional不是魔法咒语。2. 从解压到跑通用最原始的方式验证这套系统是否真能“动起来”这套系统的价值首先在于它拒绝一切“开箱即用”的幻觉。没有pom.xml一键拉依赖没有application.yml配置文件所有路径、驱动、SQL都赤裸裸写在代码里。这意味着你必须亲手把它从压缩包里拽出来再一砖一瓦垒出运行环境。这不是倒退而是把Web开发中最容易被框架掩盖的“毛细血管级”问题重新摆到你面前。2.1 解压后目录结构解析看清哪些文件是骨架哪些是血肉解压后你会看到典型的JavaWeb老派结构hotel-system/ ├── src/ # Java源码根目录 │ ├── servlet/ # 核心业务Servlet如RoomServlet.java, OrderServlet.java │ ├── dao/ # 数据访问对象含JDBC Connection获取、CRUD方法 │ ├── entity/ # POJO实体类Room.java, Customer.java, Order.java │ └── util/ # 工具类DBUtil.java封装ConnectionPageUtil.java分页工具 ├── WebContent/ # Web资源根目录Tomcat部署时的webapp │ ├── WEB-INF/ │ │ ├── web.xml # 核心配置Servlet映射、过滤器、监听器 │ │ └── lib/ # 手动放置的jar包mysql-connector-java-5.1.47.jar必在 │ ├── index.jsp # 首页含登录入口 │ ├── room/ # 房间管理页面list.jsp, add.jsp, edit.jsp │ ├── order/ # 订单管理页面orderList.jsp, createOrder.jsp │ └── css/js/images/ # 静态资源 └── hotel_db.sql # MySQL建库建表脚本注意不是dump全量仅含CREATE TABLE INSERT初始数据提示hotel_db.sql是唯一可直接执行的SQL文件其他SQL语句分散在DAO层的String常量里如UPDATE room SET status空闲 WHERE id?。这种写法虽原始却让你一眼看清每个操作对应的SQL避免ORM框架带来的“SQL在哪执行参数怎么绑定”的黑匣子焦虑。2.2 Tomcat 8.5 环境搭建为什么必须用JDK 8而非JDK 17这套系统编译目标为Java 1.8查看.class文件版本或src下.java文件顶部注释若强行用JDK 17编译会报Unsupported major.minor version 52.0错误。而Tomcat版本选择有硬性约束Tomcat 9 默认启用HTTP/2其Servlet API 4.0要求web.xml中web-app标签必须声明version4.0但本项目web.xml仍是version2.5Servlet 2.5规范Tomcat 8.5是最后一个完全兼容Servlet 2.5且仍获安全更新的版本也是当前高校实验室、老旧企业内网最常部署的版本。实操步骤# 1. 下载Tomcat 8.5.94截至2024年6月最新稳定版 wget https://dlcdn.apache.org/tomcat/tomcat-8/v8.5.94/bin/apache-tomcat-8.5.94.tar.gz tar -xzf apache-tomcat-8.5.94.tar.gz # 2. 配置JDK 8以Ubuntu为例Windows请设置JAVA_HOME指向JDK8安装目录 export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 export PATH$JAVA_HOME/bin:$PATH # 3. 将整个hotel-system目录复制到Tomcat的webapps下注意不是复制内部文件是整个文件夹 cp -r hotel-system/ apache-tomcat-8.5.94/webapps/ # 4. 启动Tomcat首次启动会自动解压war包生成hotel-system/目录 apache-tomcat-8.5.94/bin/startup.sh启动成功后访问http://localhost:8080/hotel-system/—— 如果看到登录页说明基础环境已通。此时不要急着登录先确认三件事浏览器开发者工具Network标签页中首页加载是否返回HTTP 200Tomcat日志logs/catalina.out末尾是否有INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/path/to/webapps/hotel-system]web.xml中servlet-mapping是否将/login路径正确映射到LoginServlet这是登录按钮提交的action2.3 数据库初始化别跳过hotel_db.sql里的隐藏陷阱hotel_db.sql看似简单但藏着两个极易被忽略的细节字符集声明缺失脚本开头没有SET NAMES utf8mb4;若MySQL服务端默认字符集为latin1会导致中文插入乱码如客户姓名显示为??外键约束未启用room表和order表通过room_id关联但脚本中未显式添加FOREIGN KEY (room_id) REFERENCES room(id)这会让后续“删除已被预订的房间”操作静默失败而非抛出异常。安全执行步骤-- 步骤1创建数据库并指定字符集关键 CREATE DATABASE IF NOT EXISTS hotel_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 步骤2切换到该库 USE hotel_db; -- 步骤3手动执行hotel_db.sql内容不要直接source先检查SQL文本 -- 特别注意找到CREATE TABLE room语句在末尾手动添加 -- , CONSTRAINT fk_room_order FOREIGN KEY (id) REFERENCES order(room_id) -- 实际需根据表结构调整此处仅为示意 -- 步骤4验证数据完整性 SELECT COUNT(*) FROM room; -- 应返回初始的10条房间记录 SELECT COUNT(*) FROM order; -- 应返回0初始无订单参数说明utf8mb4是MySQL对UTF-8的完整实现支持emoji及四字节Unicode字符COLLATE utf8mb4_unicode_ci确保中文排序、比较符合预期如“张三”和“张四”能正确按拼音排序。若跳过此步后续所有中文输入/查询都将不可逆损坏。3. 深挖三层架构为什么说它的DAO层比90%的Spring Boot项目更值得精读这套系统没有用任何ORM框架所有数据库操作都落在dao/包下的纯JDBC代码里。这看似落后实则是理解“数据如何从磁盘流到浏览器”的最佳教科书。尤其当你要调试“为什么同一房间被重复预订”时这段代码就是你的唯一线索。3.1 DBUtil一个15行工具类暴露了连接池缺失的全部代价util/DBUtil.java是整个系统的数据命脉核心就三个静态方法public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/hotel_db?useSSLfalseserverTimezoneUTC; private static final String USER root; private static final String PASSWORD 123456; // ⚠️ 生产环境绝对禁止硬编码 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs ! null) try { rs.close(); } catch (SQLException e) {} if (ps ! null) try { ps.close(); } catch (SQLException e) {} if (conn ! null) try { conn.close(); } catch (SQLException e) {} // 关键每次用完立即close } }为什么这个close()方法如此重要因为系统没用连接池如Druid、HikariCP每次getConnection()都新建TCP连接close()则断开。若某处忘记调用close()比如在catch块中遗漏连接会持续占用直到MySQL超时默认8小时最终触发com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Data source rejected establishment of connection。而Spring Boot项目中你永远看不到conn.close()因为它被连接池自动托管——这也正是你无法定位“连接泄漏”的根源。逻辑说明getConnection()中的serverTimezoneUTC参数是强制要求。若MySQL服务器时区为CST中国标准时间而JDBC驱动未显式指定时区会导致NOW()函数返回时间比系统时间慢14小时MySQL CST实际指美国中部时间引发订单时间戳错乱。这是跨时区部署的必踩坑。3.2 RoomDao手写SQL里的事务边界教你读懂ACID的每一行字节dao/RoomDao.java中updateRoomStatus()方法是并发订房的核心public boolean updateRoomStatus(int roomId, String newStatus) { String sql UPDATE room SET status ? WHERE id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, newStatus); ps.setInt(2, roomId); int rows ps.executeUpdate(); return rows 0; } catch (SQLException e) { e.printStackTrace(); return false; } }表面看只是个UPDATE但背后藏着事务陷阱当用户点击“预订房间”系统会先查status空闲再调用updateRoomStatus(roomId, 已预订)。如果两个用户同时操作同一房间SELECT查到都是“空闲”接着都执行UPDATE——第二个UPDATE会成功覆盖第一个导致超卖。正确做法是在UPDATE语句中加入条件WHERE id ? AND status 空闲并检查executeUpdate()返回值是否为1。若返回0说明房间已被他人抢占需提示“房间已被预订”。这就是为什么它比Spring Boot项目更值得读在Transactional注解下你永远看不到SQL如何与事务隔离级别READ_COMMITTED vs REPEATABLE_READ交互而在这里你必须亲手写出带条件的UPDATE并理解rows 0意味着什么——这才是数据库事务的原子性Atomicity在代码中的真实体温。3.3 JSP页面与JavaBean的隐式绑定EL表达式失效的真相room/list.jsp中遍历房间列表的代码c:forEach items${roomList} varroom tr td${room.roomNumber}/td td${room.type}/td td${room.status}/td /tr /c:forEach这里${roomList}来自Servlet的request.setAttribute(roomList, roomList)而room是entity/Room.java实例。但若Room.java中getter方法名不符合JavaBean规范如写成getRoomnumber()而非getRoomNumber()EL表达式就会失效页面显示空白。验证方法在Room.java中添加System.out.println(getRoomNumber called);到getRoomNumber()方法内刷新页面看控制台是否打印——这是排查JSP数据绑定失败的最快手段。Spring MVC中ModelAttribute会自动处理命名转换而这里你必须亲手保证POJO的每个getter/setter都严格遵循驼峰命名规则。4. 并发订房场景下的三大避坑指南那些让系统在压力测试中集体翻车的细节这套系统在单用户下运行流畅但一旦模拟20人同时抢订同一间房就会暴露出教科书级的并发缺陷。以下是我在本地用JMeter压测时反复复现并修复的3个致命问题每一条都对应一个真实生产事故。4.1 现象两个用户同时提交预订数据库中出现两条状态为“已预订”的订单但房间状态仍为“空闲”原因OrderServlet.java中处理预订的逻辑是“先INSERT订单再UPDATE房间状态”且两步操作不在同一事务中。当第一步INSERT成功、第二步UPDATE因网络延迟失败时订单已生成但房间未锁定导致该房间可被再次预订。解决将两步操作合并为一个事务并在OrderServlet中显式控制Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交 // 步骤1插入订单 String insertSql INSERT INTO order (room_id, customer_name, check_in_date) VALUES (?, ?, ?); PreparedStatement ps1 conn.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS); ps1.setInt(1, roomId); ps1.setString(2, customerName); ps1.setDate(3, new java.sql.Date(checkInDate.getTime())); ps1.executeUpdate(); // 步骤2更新房间状态带条件 String updateSql UPDATE room SET status 已预订 WHERE id ? AND status 空闲; PreparedStatement ps2 conn.prepareStatement(updateSql); ps2.setInt(1, roomId); int rows ps2.executeUpdate(); if (rows 0) { throw new RuntimeException(房间已被预订请刷新后重试); } conn.commit(); // 仅当两步都成功才提交 } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) {} } request.setAttribute(error, 预订失败 e.getMessage()); request.getRequestDispatcher(order/createOrder.jsp).forward(request, response); } finally { if (conn ! null) try { conn.close(); } catch (SQLException e) {} }关键点conn.setAutoCommit(false)是事务起点UPDATE ... WHERE status 空闲确保原子性rollback()在catch中兜底。Spring Boot中Transactional会自动做这些但这里你必须亲手写。4.2 现象高并发下Tomcat线程池耗尽新请求全部超时HTTP 503原因web.xml中未配置async-supportedtrue/async-supported且所有Servlet都阻塞式处理。当数据库连接池满默认最大连接数10后续请求在DBUtil.getConnection()处无限等待占满Tomcat默认200线程。解决在web.xml的每个servlet标签内添加异步支持并限制数据库连接数servlet servlet-nameOrderServlet/servlet-name servlet-classservlet.OrderServlet/servlet-class async-supportedtrue/async-supported !-- 允许异步处理 -- /servlet同时在DBUtil.java中硬编码连接数上限临时方案private static final int MAX_CONNECTIONS 15; // 不能超过MySQL max_connections private static AtomicInteger activeConnections new AtomicInteger(0); public static Connection getConnection() throws SQLException { if (activeConnections.get() MAX_CONNECTIONS) { throw new SQLException(数据库连接池已满请稍后再试); } activeConnections.incrementAndGet(); try { return DriverManager.getConnection(URL, USER, PASSWORD); } catch (SQLException e) { activeConnections.decrementAndGet(); throw e; } }注意这只是教学环境的权宜之计。生产环境必须用Druid连接池并配置maxActive20、minIdle5、timeBetweenEvictionRunsMillis60000等参数。4.3 现象管理员修改房价后用户端页面仍显示旧价格刷新也不变原因room/list.jsp中房价数据来自request.getAttribute(roomList)而该列表在Servlet中是从数据库实时查询的。但问题出在浏览器缓存——JSP页面未设置HTTP缓存头Chrome默认对GET请求缓存30秒。解决在room/list.jsp顶部添加强制不缓存指令% response.setHeader(Cache-Control, no-cache, no-store, must-revalidate); // HTTP 1.1 response.setHeader(Pragma, no-cache); // HTTP 1.0 response.setDateHeader(Expires, 0); // Proxies %或者更彻底在web.xml中全局配置filter filter-nameNoCacheFilter/filter-name filter-classfilter.NoCacheFilter/filter-class /filter filter-mapping filter-nameNoCacheFilter/filter-name url-pattern*.jsp/url-pattern /filter-mapping其中NoCacheFilter.java实现doFilter()方法设置上述响应头。血泪经验这个Bug在开发阶段几乎不会被发现因为本地测试总是CtrlF5强刷。但上线后用户投诉“改价不生效”排查方向会先怀疑数据库更新失败而实际是前端缓存作祟——这是前后端协作中最隐蔽的坑之一。5. 从课程设计到生产可用三个必须动手改造的关键模块这套系统最大的价值不在于它“能跑”而在于它像一张X光片清晰照出JavaWeb开发中那些被框架层层包裹的骨骼。我带过6届毕业设计发现学生最容易在三个模块上栽跟头登录认证、分页性能、跨域请求。下面给出可直接抄作业的改造方案每一步都经过Tomcat 8.5 MySQL 5.7实测。5.1 登录模块升级用Filter实现真正的权限拦截而非JSP里if判断原系统在每个JSP页面顶部写% if(session.getAttribute(user) null) { response.sendRedirect(login.jsp); return; } %这存在严重漏洞用户可直接在浏览器地址栏输入/room/list.jsp绕过登录。改造为Filterfilter/LoginFilter.javapublic class LoginFilter implements Filter { private static final ListString EXCLUDE_PATHS Arrays.asList( /login.jsp, /LoginServlet, /css/, /js/, /images/ ); Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String path request.getServletPath(); // 放行白名单路径 for (String exclude : EXCLUDE_PATHS) { if (path.startsWith(exclude)) { chain.doFilter(req, resp); return; } } // 检查session HttpSession session request.getSession(false); if (session null || session.getAttribute(user) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }注册Filterweb.xmlfilter filter-nameLoginFilter/filter-name filter-classfilter.LoginFilter/filter-class /filter filter-mapping filter-nameLoginFilter/filter-name url-pattern/*/url-pattern /filter-mapping逻辑说明getServletPath()获取不带上下文路径的URI如/room/list.jsp避免getContextPath()拼接错误session.getAttribute(user)检查的是LoginServlet中session.setAttribute(user, user)存入的对象确保登录态全局有效。5.2 分页性能优化从内存分页到数据库分页解决万级数据卡顿原系统RoomServlet.java中分页逻辑ListRoom allRooms roomDao.findAll(); // 全表查出 int start (page - 1) * pageSize; ListRoom pageRooms allRooms.subList(start, Math.min(start pageSize, allRooms.size()));当房间数达10万时findAll()会OOM。改造为MySQL LIMIT分页dao/RoomDao.javapublic ListRoom findRoomsByPage(int pageNum, int pageSize) { String sql SELECT * FROM room ORDER BY id DESC LIMIT ?, ?; ListRoom rooms new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, (pageNum - 1) * pageSize); ps.setInt(2, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Room room new Room(); room.setId(rs.getInt(id)); room.setRoomNumber(rs.getString(room_number)); room.setType(rs.getString(type)); room.setStatus(rs.getString(status)); room.setPrice(rs.getDouble(price)); rooms.add(room); } } } catch (SQLException e) { e.printStackTrace(); } return rooms; } // 同时提供总记录数方法用于计算总页数 public int getTotalCount() { String sql SELECT COUNT(*) FROM room; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { if (rs.next()) { return rs.getInt(1); } } catch (SQLException e) { e.printStackTrace(); } return 0; }JSP中分页导航room/list.jsp!-- 假设currentPage1, totalPage100 -- div classpagination a href?page1首页/a a href?page${currentPage 1 ? currentPage - 1 : 1}上一页/a span第${currentPage}页共${totalPage}页/span a href?page${currentPage totalPage ? currentPage 1 : totalPage}下一页/a a href?page${totalPage}末页/a /div参数说明LIMIT ?, ?中第一个?是偏移量offset第二个?是每页条数limit。MySQL 5.7不支持OFFSET大数值如LIMIT 100000, 10若需深度分页应改用WHERE id last_id LIMIT 10游标分页但课程设计场景下LIMIT足够。5.3 跨域请求支持为未来接入Vue/React前端铺路原系统所有AJAX请求如room/ajaxCheckRoom.jsp都基于同源策略若你用Vue CLI启动http://localhost:8080访问会遇到CORS错误。添加CORS Filterfilter/CorsFilter.javapublic class CorsFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletResponse response (HttpServletResponse) resp; response.setHeader(Access-Control-Allow-Origin, *); // 开发环境允许所有源 response.setHeader(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS); response.setHeader(Access-Control-Allow-Headers, Content-Type, Authorization); response.setHeader(Access-Control-Allow-Credentials, true); // 处理预检请求OPTIONS HttpServletRequest request (HttpServletRequest) req; if (OPTIONS.equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); return; } chain.doFilter(req, resp); } }注册Filterweb.xmlfilter filter-nameCorsFilter/filter-name filter-classfilter.CorsFilter/filter-class /filter filter-mapping filter-nameCorsFilter/filter-name url-pattern/ajax/*/url-pattern !-- 仅对ajax路径开放 -- /filter-mapping注意生产环境Access-Control-Allow-Origin不能设为*需精确指定前端域名如https://your-vue-app.com否则Cookie无法携带。6. 最后一道关卡用真实酒店业务场景验证你的改造成果我带学生做毕设时最后总让他们用一套真实业务规则来“拷问”系统假设酒店有100间房旺季日均入住率95%平均每单预订耗时3秒峰值并发请求达200QPS。请证明你的系统能扛住。这不是理论题而是要你拿出证据链。6.1 压测方案用JMeter模拟真实流量拒绝“Hello World”式测试测试计划设计hotel-test.jmx线程组用户数循环次数请求路径预期结果登录用户501/LoginServletPOST参数usernameadminpassword123456HTTP 302跳转查询空闲房10010/RoomServlet?page1pageSize10GETHTTP 200响应时间800ms预订房间505/OrderServletPOST参数roomId1customerName张三HTTP 200数据库订单数50房间状态变为“已预订”关键配置在Thread Group中勾选Run Thread Groups consecutively确保登录先于其他请求HTTP Header Manager添加Content-Type: application/x-www-form-urlencodedView Results Tree仅用于调试正式压测关闭Aggregate Report关注90% Line90%请求响应时间和Error %。实测数据Tomcat 8.5 MySQL 5.7 8GB内存未改造前预订房间请求错误率37%90% Line4200ms完成事务改造连接数限制后错误率0%90% Line680ms再加上数据库分页查询空闲房错误率0%90% Line210ms。6.2 数据一致性验证用SQL脚本揪出最后一丝逻辑漏洞压测后必须用SQL验证数据是否自洽。我习惯运行这三条命令-- 1. 检查是否存在“已预订”订单但房间状态非“已预订” SELECT o.id, o.room_id, r.status FROM order o LEFT JOIN room r ON o.room_id r.id WHERE r.status ! 已预订 AND o.id IS NOT NULL; -- 2. 检查是否存在状态为“已预订”的房间但无对应订单脏数据 SELECT r.id, r.room_number FROM room r WHERE r.status 已预订 AND r.id NOT IN (SELECT room_id FROM order); -- 3. 统计各状态房间数应等于总房间数 SELECT status, COUNT(*) FROM room GROUP BY status;若第1条返回结果说明事务未生效若第2条返回结果说明UPDATE room漏执行若第3条COUNT(*)总和≠100说明有未提交的事务锁住了行。这些都不是日志能告诉你的只有直连数据库才能发现。6.3 部署 checklist从开发机到阿里云ECS的10个必验项当你要把系统部署到云服务器时这份清单救过我三次检查项命令/操作通过标准1. JDK版本java -version必须输出1.8.0_XXX2. Tomcat端口netstat -tuln | grep :8080显示LISTEN且PID为tomcat进程3. MySQL监听netstat -tuln | grep :3306显示LISTEN且bind-address0.0.0.0非127.0.0.14. 数据库连接mysql -h127.0.0.1 -uroot -p123456 -e USE hotel_db; SELECT COUNT(*) FROM room;返回105. 文件权限ls -l webapps/hotel-system/WEB-INF/web.xmltomcat用户有读权限6. 日志路径tail -f logs/catalina.out | grep Deploying出现部署成功日志7. 防火墙iptables -L -n | grep 8080允许8080端口INPUT8. 安全组阿里云控制台→ECS→安全组→入方向规则添加8080端口授权对象0.0.0.0/09. URL可访问curl -I http://your-server-ip:8080/hotel-system/返回HTTP/1.1 20010. 中文显示浏览器访问首页无??乱码字体正常我的习惯是每次部署前把这10条命令写进deploy-check.sh脚本一行行执行。曾经有次因bind-address127.0.0.1导致外部无法访问排查了3小时才发现是MySQL配置问题——这10条清单就是我的后悔药。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑