JavaWeb旅游管理系统:JSP+Servlet+MySQL完整MVC实战 简介这是一套基于JavaWeb技术栈开发的旅游管理系统完整源码面向Java初学者与Web开发入门者聚焦ServletJSPMySQL三层架构实践帮助学习者掌握用户注册审核、景点信息检索、酒店在线预订及后台多模块管理等典型业务场景的实现逻辑。资源包共738个文件包含111个JSP页面实现前后端交互、415个GIF动图用于界面展示、69个JPG图片景点与酒店素材、40个CSS样式文件统一前端视觉、23个JS脚本增强交互功能以及关键Java类、SQL数据库脚本和配置文件整体压缩包大小为8.46MB。已有1111人学习下载资源结构规范含完整MVC分层目录、可直接部署运行的MySQL数据库含初始化数据并附带后台权限控制、留言审核、自驾游路线维护等实用功能模块适合课程设计、毕业项目参考与Web全栈技能巩固。1. JavaWeb旅游管理系统JSPServletMySQL一个能跑通、能改、能交作业的完整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 中。常见做法是创建数据库CREATE DATABASE travel_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;执行建表语句以下为典型结构实际以 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) );插入初始管理员账号用于后台登录INSERT INTO users (username, password, real_name, email, status) VALUES (admin, e10adc3949ba59abbe56e057f20f883e, 系统管理员, admintravel.com, 1); -- 密码明文为 123456MD5 加密值后台登录时校验用参数说明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/classesWebContent/映射到/。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} varspot div classspot-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 查询 ListScenicSpot 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→ 邮箱验证模拟 → 管理员后台激活注册流程刻意设计为「异步审核」体现真实业务约束用户填register.jsp提交 →RegisterServlet.doPost()Servlet 将用户信息插入users表status0待审核不发邮件教学简化但页面提示「请等待管理员审核」管理员登录后台/admin/login.jsp→AdminLoginServlet→AdminDAO.checkLogin()→ 成功后跳转/admin/dashboard.jsp在/admin/userManage.jsp中列表显示status0的用户 → 点击「启用」→ 触发UpdateUserStatusServlet执行UPDATE users SET status1 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.jarJSTL 1.2 依赖在web.xml顶部确认xmlnshttp://xmlns.jcp.org/xml/ns/javaee且version3.1或更高在 JSP 顶部添加% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %若仍无效在web.xml中添加jsp-configel-ignorefalse/el-ignore/jsp-config强制启用 EL4.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 methodpost若同时存在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?useSSLfalseserverTimezoneUTCMySQL 8.0 必须加serverTimezone检查username/password是否与 MySQL 中创建的用户一致默认root/123456登录 MySQL 执行CREATE USER travel_userlocalhost IDENTIFIED BY pwd123; GRANT ALL ON travel_db.* TO travel_userlocalhost; FLUSH PRIVILEGES;若用 Docker 或远程 MySQL将localhost改为 IP并授权travel_user%4.4 现象中文乱码用户注册姓名显示为??数据库存入也是问号原因MySQL 字符集、JDBC 连接参数、JSP 页面编码、Tomcat URI 编码四者未统一解决四步必须全做MySQL 创建库时指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ciJDBC URL 加参数?useUnicodetruecharacterEncodingutf8mb4serverTimezoneGMT%2B8index.jsp等所有 JSP 顶部加% page contentTypetext/html;charsetUTF-8 %Tomcat 的conf/server.xml中Connector标签增加URIEncodingUTF-8属性4.5 现象后台管理页面点击「删除景点」弹出确认框但点击确定后无反应F12 看 Network 无请求发出原因admin/scenicManage.jsp中删除按钮的 JavaScript 事件绑定失效或deleteScenic.jsp路径错误导致 404解决检查按钮 HTML 是否为a hrefdeleteScenic.jsp?id${spot.id} onclickreturn confirm(确定删除)删除/a确认deleteScenic.jsp是否在WebContent/admin/目录下而非WebContent/根目录更健壮写法用表单提交替代 a 标签防止 onclick 被拦截form actiondeleteScenic.jsp methodpost styledisplay:inline; input typehidden nameid value${spot.id} button typesubmit onclickreturn confirm(确定删除)删除/button /form5. 进阶改造实战把「塞北人文」模块升级为富文本编辑 图片上传附可抄代码5.1 为什么必须升级原始模块的硬伤在哪原始项目中「塞北人文」内容是硬编码在 JSP 里的静态文字或从数据库TEXT字段读取纯文本。这带来三个致命问题管理员无法在线编辑排版加粗、换行、插入图片所有图文混排需手动拼 HTML 字符串极易 XSS 注入图片只能存路径不能由管理员上传并自动保存到服务器因此真正的进阶不是加功能而是替换内容管理范式用KindEditor轻量级富文本 Apache Commons FileUpload文件上传 Base64或本地存储实现所见即所得编辑。5.2 富文本集成三步接入 KindEditor兼容 JSP Servlet下载并部署 JS/CSS下载 KindEditor 4.1.11 最后稳定版兼容 IE8解压后将kindeditor/目录放入WebContent/js/下在admin/addHumanity.jsp中初始化编辑器% page contentTypetext/html;charsetUTF-8 % html head link relstylesheet 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 actionSaveHumanityServlet methodpost textarea idcontent namecontent stylewidth:100%;height:400px;/textarea input typesubmit value保存 /form /body /html服务端接收富文本内容SaveHumanityServlet.javaprotected 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 importorg.apache.commons.fileupload.*,org.apache.commons.fileupload.disk.*,org.apache.commons.fileupload.servlet.*,java.util.*,java.io.* % % page contentTypetext/html;charsetUTF-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 { ListFileItem 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都强制走一遍这三件事清空 Tomcatwork/Catalina/localhost/下对应项目的缓存目录——否则旧 class 不生效debug 时看到的永远是上一版逻辑用 Postman 测试所有 Servlet 的 POST 接口如/login,/register,/saveBooking传 raw JSON 或 form-data观察响应体是否含error字段而不是只信浏览器 F5在 MySQL 命令行执行SELECT * FROM users WHERE usernametest;确认数据真的落库且中文、时间、状态值准确无误——眼见为实日志只是辅助。这三步做完你才真正掌控了这个 JavaWeb 项目而不是被它牵着鼻子走。希望帮到你。本文还有配套的精品资源点击获取