☰
JavaWeb旅游管理系统:JSP+Servlet+MySQL完整MVC实战
2026/10/8 23:20:37 网站建设 项目流程

简介:这是一套基于JavaWeb技术栈开发的旅游管理系统完整源码,面向Java初学者与Web开发入门者,聚焦Servlet+JSP+MySQL三层架构实践,帮助学习者掌握用户注册审核、景点信息检索、酒店在线预订及后台多模块管理等典型业务场景的实现逻辑。资源包共738个文件,包含111个JSP页面(实现前后端交互)、415个GIF动图(用于界面展示)、69个JPG图片(景点与酒店素材)、40个CSS样式文件(统一前端视觉)、23个JS脚本(增强交互功能)以及关键Java类、SQL数据库脚本和配置文件,整体压缩包大小为8.46MB。已有1111人学习下载,资源结构规范,含完整MVC分层目录、可直接部署运行的MySQL数据库(含初始化数据),并附带后台权限控制、留言审核、自驾游路线维护等实用功能模块,适合课程设计、毕业项目参考与Web全栈技能巩固。

1. JavaWeb旅游管理系统(JSP+Servlet+MySQL):一个能跑通、能改、能交作业的完整MVC骨架

你手头刚拿到一个叫“JavaWeb旅游管理系统”的压缩包,解压后满屏.class、.classpath、一堆重复的editor.css,还有org.eclipse.wst.common.component这种一看就是 Eclipse 工程元数据的文件——第一反应是:这玩意儿真能跑?别是别人导出失败的半成品吧?
它不是 Demo,也不是教学幻灯片,而是一个真实可部署、功能闭环、结构清晰的 JavaWeb 全栈项目:前端用 JSP 拼页面、后端靠 Servlet 做控制器、数据存 MySQL、业务逻辑走面向对象分层。它覆盖了主界面展示、用户注册审核、景点检索、酒店预订、后台 CRUD 全流程,连塞北人文、自驾游路线这种带地域特色的模块都预留了接口和页面占位。适合刚学完 Servlet 生命周期、JSP EL 表达式、MySQL 基础增删改查的同学,拿来当毕设原型、课程设计底座、甚至小团队快速验证旅游类业务逻辑的沙盒环境。它不炫技,不堆框架,但每一步都踩在 JavaWeb 教学与工程落地的交界线上——你改得动,看得懂,部署不翻车。


2. 项目结构拆解与运行前必做的三件事:从 Eclipse 工程到可执行 WAR 包

2.1 目录结构还原:识别哪些是源码、哪些是 IDE 痕迹、哪些是真正要保留的资源

这个项目明显是从 Eclipse 导出的动态 Web 项目(Dynamic Web Project),所以你会看到大量 IDE 特有文件:

文件/目录类型是否必须保留说明
src/Java 源码根目录✅ 必须所有 Servlet、DAO、Bean 都在这里,com.example.servlet.*和com.example.dao.*是核心包
WebContent/或WebRoot/Web 资源根目录✅ 必须JSP 页面、CSS、JS、图片全在此;注意WEB-INF/web.xml是关键配置入口
WEB-INF/classes/编译后 class 目录❌ 不需保留(自动生成)Eclipse 编译输出,手动清理不影响运行
.classpath,.project,org.eclipse.wst.common.componentEclipse 工程描述文件❌ 可删除IDEA 或 Tomcat 直接部署时完全不需要,留着反而可能干扰构建
style.css,editor.css(重复多个)CSS 样式文件⚠️ 保留 1 份,删冗余检查WebContent/css/下是否已归类,重复文件可能是误复制,统一路径更可控

提示:不要直接双击index.jsp打开浏览器——那只是静态 HTML 渲染,Servlet 逻辑根本不会触发。必须通过 Tomcat 启动整个 Web 应用上下文,让web.xml中的<servlet-mapping>生效。

2.2 数据库初始化:建库、建表、导入初始数据(含管理员账号)

系统依赖 MySQL,但项目包里没有 SQL 脚本——这是新手最容易卡住的第一步。你需要根据src/com/example/dao/下的 DAO 类反推表结构,或从WebContent/WEB-INF/web.xml里找数据库连接配置(通常在<context-param>或某个初始化 Servlet 中)。常见做法是:

  1. 创建数据库:CREATE DATABASE travel_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  2. 执行建表语句(以下为典型结构,实际以 DAO 中 SQL 为准):
-- 用户表(含审核状态) CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, -- 注意:明文存储,仅教学用途! real_name VARCHAR(30), email VARCHAR(100), status TINYINT DEFAULT 0, -- 0:待审核, 1:已启用, 2:禁用 create_time DATETIME DEFAULT NOW() ); -- 景点表 CREATE TABLE scenic_spots ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, description TEXT, location VARCHAR(200), image_path VARCHAR(200), -- 存相对路径,如 /images/scenic/1.jpg is_hot TINYINT DEFAULT 0 ); -- 酒店表(简化版) CREATE TABLE hotels ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, address VARCHAR(200), price DECIMAL(10,2), rating FLOAT DEFAULT 0.0, image_path VARCHAR(200) ); -- 预订表(关联用户和酒店) CREATE TABLE bookings ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, hotel_id INT NOT NULL, check_in DATE, check_out DATE, status ENUM('pending','confirmed','cancelled') DEFAULT 'pending', FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (hotel_id) REFERENCES hotels(id) );
  1. 插入初始管理员账号(用于后台登录):
INSERT INTO users (username, password, real_name, email, status) VALUES ('admin', 'e10adc3949ba59abbe56e057f20f883e', '系统管理员', 'admin@travel.com', 1); -- 密码明文为 "123456",MD5 加密值,后台登录时校验用

参数说明:e10adc3949ba59abbe56e057f20f883e是"123456"的 MD5 值,项目中UserDAO.java里login()方法大概率用DigestUtils.md5Hex(input)或类似方式比对。若你改密码,务必同步更新此字段或重写校验逻辑。

2.3 开发环境适配:Eclipse → IDEA → 纯 Tomcat 部署的三种路径

  • Eclipse 用户:右键项目 →Properties→Targeted Runtimes→ 勾选已配置的 Tomcat Server;确保Deployment Assembly中src/映射到/WEB-INF/classes,WebContent/映射到/。
  • IDEA 用户:新建Empty Project→Add Framework Support→ 选Web Application→ 手动指定WebContent为 Web root;在Project Structure→Artifacts中添加Web Application: Archive,输出 WAR 包。
  • 纯命令行/Tomcat 用户(推荐教学验证):
    # 将项目目录重命名为 travel-web(无空格) # 复制到 $CATALINA_HOME/webapps/ 下 cp -r travel-web $CATALINA_HOME/webapps/ # 启动 Tomcat $CATALINA_HOME/bin/startup.sh # Linux/Mac $CATALINA_HOME/bin/startup.bat # Windows # 访问 http://localhost:8080/travel-web/

逻辑说明:Tomcat 启动时会扫描webapps/下所有目录,自动解包并加载WEB-INF/web.xml。该文件定义了 Servlet 映射(如/login→LoginServlet)、过滤器(如字符编码过滤器)、监听器(如ContextLoaderListener初始化 DAO 工厂)。没有它,JSP 能显示,但表单提交必然 404。


3. 核心模块运行链路:从首页点击到酒店预订成功的 7 步调用追踪

3.1 主界面请求流:index.jsp→IndexServlet→ScenicSpotDAO→ 数据库 → 回显

首页index.jsp并非纯静态页,它包含动态数据加载:

<!-- index.jsp 片段 --> <c:forEach items="${scenicList}" var="spot"> <div class="spot-card"> <img src="${spot.imagePath}" alt="${spot.name}"> <h3>${spot.name}</h3> <p>${fn:substring(spot.description, 0, 50)}...</p> </div> </c:forEach>

这段 JSTL 代码背后是IndexServlet在doGet()中完成的数据准备:

// IndexServlet.java protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取热门景点(DAO 层封装 JDBC 查询) List<ScenicSpot> hotSpots = new ScenicSpotDAO().findHotSpots(); // 2. 设置请求属性,供 JSP EL 表达式取用 request.setAttribute("scenicList", hotSpots); // 3. 转发到 index.jsp(注意:不是重定向,保留 request 作用域) request.getRequestDispatcher("/index.jsp").forward(request, response); }

参数说明:request.setAttribute()是 Servlet 向 JSP 传值的核心机制;forward()保证 URL 不变且 request 作用域有效;若用sendRedirect(),则 request 属性丢失,JSP 会报scenicList为空异常。

3.2 用户注册审核流:表单提交 →RegisterServlet→ 邮箱验证模拟 → 管理员后台激活

注册流程刻意设计为「异步审核」,体现真实业务约束:

  1. 用户填register.jsp提交 →RegisterServlet.doPost()
  2. Servlet 将用户信息插入users表,status=0(待审核)
  3. 不发邮件(教学简化),但页面提示「请等待管理员审核」
  4. 管理员登录后台(/admin/login.jsp)→AdminLoginServlet→AdminDAO.checkLogin()→ 成功后跳转/admin/dashboard.jsp
  5. 在/admin/userManage.jsp中,列表显示status=0的用户 → 点击「启用」→ 触发UpdateUserStatusServlet执行UPDATE users SET status=1 WHERE id=?

关键细节:RegisterServlet中密码处理通常是password = DigestUtils.md5Hex(request.getParameter("password")),而AdminLoginServlet校验时也用同样 MD5 方式比对,确保一致性。若你替换为 BCrypt,必须同步修改 DAO 层的insertUser()和checkLogin()方法。

3.3 酒店预订事务流:跨表操作 + 手动事务控制(非 Spring)

预订功能涉及users、hotels、bookings三张表,且要求「酒店库存扣减」与「订单生成」原子性。项目未用 Spring,而是用 JDBC 手动控制事务:

// BookingServlet.java 关键片段 Connection conn = null; try { conn = JdbcUtil.getConnection(); // 自定义工具类获取连接 conn.setAutoCommit(false); // 关闭自动提交 // 1. 检查酒店是否可订(伪库存,实际查 bookings 表当日占用数) int bookedCount = new HotelDAO().getBookedCount(hotelId, checkIn, checkOut); if (bookedCount >= 10) { // 假设每酒店最多10间房 request.setAttribute("error", "该酒店当前日期已满,请选择其他时间"); request.getRequestDispatcher("/hotelDetail.jsp").forward(request, response); return; } // 2. 插入预订记录 new BookingDAO().insertBooking(userId, hotelId, checkIn, checkOut); // 3. 提交事务 conn.commit(); request.setAttribute("msg", "预订成功!"); } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} } request.setAttribute("error", "预订失败:" + e.getMessage()); } finally { JdbcUtil.close(conn); }

逻辑说明:setAutoCommit(false)是手动事务起点;rollback()在异常时回滚所有 DML 操作;JdbcUtil.close()确保连接释放,避免连接池耗尽。这是 JavaWeb 原生开发中必须掌握的硬技能,也是面试高频考点。


4. 避坑指南:五个让新手调试到凌晨三点的真实问题与解法

4.1 现象:首页打开空白,浏览器控制台无报错,但 JSP 中<c:forEach>循环不渲染任何数据

原因:JSTL 标签库未正确引入,或web.xml中未声明 Servlet 3.0+ 版本导致 EL 表达式被忽略
解决:

  • 检查WebContent/WEB-INF/lib/是否存在jstl-1.2.jar和standard-1.1.2.jar(JSTL 1.2 依赖)
  • 在web.xml顶部确认xmlns="http://xmlns.jcp.org/xml/ns/javaee"且version="3.1"或更高
  • 在 JSP 顶部添加<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
  • 若仍无效,在web.xml中添加<jsp-config><el-ignore>false</el-ignore></jsp-config>强制启用 EL

4.2 现象:登录后跳转到admin/dashboard.jsp,但页面显示HTTP Status 404 – Not Found

原因:web.xml中<servlet-mapping>的url-pattern与实际访问路径不匹配,或AdminLoginServlet的@WebServlet注解路径冲突
解决:

  • 查看web.xml中<servlet-mapping>的url-pattern(如/admin/login)
  • 确认表单action属性是否一致(<form action="/admin/login" method="post">)
  • 若同时存在@WebServlet("/admin/login")注解,必须删除 web.xml 中对应<servlet>和<servlet-mapping>,二者不可共存
  • 检查dashboard.jsp是否真在WebContent/admin/目录下(路径敏感!)

4.3 现象:MySQL 连接报错Communications link failure或Access denied for user

原因:JdbcUtil.java中数据库连接参数错误,或 MySQL 用户权限未开放远程访问(本地部署常见)
解决:

  • 打开src/com/example/util/JdbcUtil.java,检查url是否为jdbc:mysql://localhost:3306/travel_db?useSSL=false&serverTimezone=UTC(MySQL 8.0+ 必须加serverTimezone)
  • 检查username/password是否与 MySQL 中创建的用户一致(默认root/123456)
  • 登录 MySQL 执行:CREATE USER 'travel_user'@'localhost' IDENTIFIED BY 'pwd123'; GRANT ALL ON travel_db.* TO 'travel_user'@'localhost'; FLUSH PRIVILEGES;
  • 若用 Docker 或远程 MySQL,将localhost改为 IP,并授权'travel_user'@'%'

4.4 现象:中文乱码,用户注册姓名显示为??,数据库存入也是问号

原因:MySQL 字符集、JDBC 连接参数、JSP 页面编码、Tomcat URI 编码四者未统一
解决(四步必须全做):

  • MySQL 创建库时指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
  • JDBC URL 加参数:?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=GMT%2B8
  • index.jsp等所有 JSP 顶部加<%@ page contentType="text/html;charset=UTF-8" %>
  • Tomcat 的conf/server.xml中<Connector>标签增加URIEncoding="UTF-8"属性

4.5 现象:后台管理页面点击「删除景点」弹出确认框,但点击确定后无反应,F12 看 Network 无请求发出

原因:admin/scenicManage.jsp中删除按钮的 JavaScript 事件绑定失效,或deleteScenic.jsp路径错误导致 404
解决:

  • 检查按钮 HTML 是否为<a href="deleteScenic.jsp?id=${spot.id}" onclick="return confirm('确定删除?')">删除</a>
  • 确认deleteScenic.jsp是否在WebContent/admin/目录下(而非WebContent/根目录)
  • 更健壮写法:用表单提交替代 a 标签,防止 onclick 被拦截
    <form action="deleteScenic.jsp" method="post" style="display:inline;"> <input type="hidden" name="id" value="${spot.id}"> <button type="submit" onclick="return confirm('确定删除?')">删除</button> </form>

5. 进阶改造实战:把「塞北人文」模块升级为富文本编辑 + 图片上传(附可抄代码)

5.1 为什么必须升级?原始模块的硬伤在哪

原始项目中「塞北人文」内容是硬编码在 JSP 里的静态文字,或从数据库TEXT字段读取纯文本。这带来三个致命问题:

  • 管理员无法在线编辑排版(加粗、换行、插入图片)
  • 所有图文混排需手动拼 HTML 字符串,极易 XSS 注入
  • 图片只能存路径,不能由管理员上传并自动保存到服务器

因此,真正的进阶不是加功能,而是替换内容管理范式:用KindEditor(轻量级富文本) +Apache Commons FileUpload(文件上传) +Base64或本地存储实现所见即所得编辑。

5.2 富文本集成:三步接入 KindEditor(兼容 JSP + Servlet)

  1. 下载并部署 JS/CSS:

    • 下载 KindEditor 4.1.11 (最后稳定版,兼容 IE8+)
    • 解压后将kindeditor/目录放入WebContent/js/下
  2. 在admin/addHumanity.jsp中初始化编辑器:

    <%@ page contentType="text/html;charset=UTF-8" %> <html> <head> <link rel="stylesheet" href="${pageContext.request.contextPath}/js/kindeditor/themes/default/default.css" /> <script src="${pageContext.request.contextPath}/js/kindeditor/kindeditor-all-min.js"></script> <script> KindEditor.ready(function(K) { window.editor = K.create('#content', { width: '100%', height: '400px', uploadJson: '${pageContext.request.contextPath}/uploadImage.jsp', // 图片上传接口 fileManagerJson: '${pageContext.request.contextPath}/fileManager.jsp', allowFileManager: true, afterBlur: function() { this.sync(); } // 失去焦点时同步 textarea 内容 }); }); </script> </head> <body> <form action="SaveHumanityServlet" method="post"> <textarea id="content" name="content" style="width:100%;height:400px;"></textarea> <input type="submit" value="保存"> </form> </body> </html>
  3. 服务端接收富文本内容(SaveHumanityServlet.java):

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 防止中文乱码 String content = request.getParameter("content"); // 已含 HTML 标签 String title = request.getParameter("title"); // 【关键】过滤 XSS:移除 script、iframe、onxxx 事件等危险标签 content = Jsoup.clean(content, Whitelist.relaxed() .addTags("p", "br", "strong", "em", "u", "ol", "ul", "li", "img") .addAttributes("img", "src", "alt", "width", "height")); // 保存到数据库(假设 HumanityDAO 有 save() 方法) new HumanityDAO().save(title, content); response.sendRedirect("humanityList.jsp"); }

参数说明:Jsoup.clean()是防 XSS 的后悔药,Whitelist.relaxed()允许基础排版标签,addAttributes()明确放行img的src等安全属性。不做过滤直接存库,等于给黑客开后门。

5.3 图片上传落地:uploadImage.jsp实现安全上传与路径返回

KindEditor 要求上传接口返回 JSON 格式:{"error":0,"url":"/uploads/20240510/abc.jpg"}。我们用传统 Servlet 方式实现(不依赖 Spring MultipartFile):

<%@ page import="org.apache.commons.fileupload.*,org.apache.commons.fileupload.disk.*,org.apache.commons.fileupload.servlet.*,java.util.*,java.io.*" %> <%@ page contentType="text/html;charset=UTF-8" %> <% // 1. 检查是否为 multipart 请求 boolean isMultipart = ServletFileUpload.isMultipartContent(request); if (!isMultipart) { out.print("{\"error\":1,\"message\":\"请求不是文件上传格式\"}"); return; } // 2. 配置上传参数 DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setSizeThreshold(1024 * 1024); // 1MB 缓存阈值 ServletFileUpload upload = new ServletFileUpload(factory); upload.setFileSizeMax(10 * 1024 * 1024); // 单文件最大 10MB upload.setSizeMax(20 * 1024 * 1024); // 总请求最大 20MB try { List<FileItem> items = upload.parseRequest(request); String uploadPath = application.getRealPath("/uploads/"); File uploadDir = new File(uploadPath); if (!uploadDir.exists()) uploadDir.mkdirs(); for (FileItem item : items) { if (!item.isFormField()) { String fileName = new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(new java.util.Date()) + "_" + item.getName().replaceAll("[^a-zA-Z0-9.\\-]", ""); // 安全过滤:只允许 jpg/jpeg/png/gif String ext = fileName.substring(fileName.lastIndexOf(".")).toLowerCase(); if (!ext.equals(".jpg") && !ext.equals(".jpeg") && !ext.equals(".png") && !ext.equals(".gif")) { out.print("{\"error\":1,\"message\":\"不支持的文件类型\"}"); return; } File uploadedFile = new File(uploadDir, fileName); item.write(uploadedFile); // 返回 JSON(注意:路径必须是 WebContext 下可访问的相对路径) String url = request.getContextPath() + "/uploads/" + fileName; out.print("{\"error\":0,\"url\":\"" + url + "\"}"); return; } } } catch (Exception e) { out.print("{\"error\":1,\"message\":\"" + e.getMessage() + "\"}"); } %>

逻辑说明:application.getRealPath("/uploads/")获取服务器绝对路径;request.getContextPath() + "/uploads/xxx.jpg"构成浏览器可访问的 URL;文件名重命名防恶意脚本(如xxx.jsp);扩展名白名单比 MIME Type 更可靠。

5.4 最后一步:验证与习惯养成——每次改完必须做的三件事

从那以后我每次改完 JSP 或 Servlet,都强制走一遍这三件事:

  1. 清空 Tomcatwork/Catalina/localhost/下对应项目的缓存目录——否则旧 class 不生效,debug 时看到的永远是上一版逻辑;
  2. 用 Postman 测试所有 Servlet 的 POST 接口(如/login,/register,/saveBooking),传 raw JSON 或 form-data,观察响应体是否含error字段,而不是只信浏览器 F5;
  3. 在 MySQL 命令行执行SELECT * FROM users WHERE username='test';,确认数据真的落库,且中文、时间、状态值准确无误——眼见为实,日志只是辅助。

这三步做完,你才真正掌控了这个 JavaWeb 项目,而不是被它牵着鼻子走。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询