资讯动态

JavaWeb旅游管理系统:JSP+Servlet+MySQL实战指南

发布时间:2026/9/28 6:00:37 来源:尧图企业网站定制
简介这是一套基于JavaWeb技术栈开发的旅游管理系统完整源码面向Java初学者与Web开发入门者聚焦ServletJSPMySQL三层架构实践帮助学习者掌握用户注册审核、景点信息检索、酒店在线预订及后台多模块管理等典型业务场景。资源共738个文件包含111个JSP页面实现前后端交互、415个GIF动图用于界面效果展示、69个JPG图片景点与酒店素材、40个CSS样式文件保障前端布局与美化、23个JS脚本增强交互逻辑以及SQL数据库脚本、Java类文件和Eclipse项目配置文件等压缩包大小为8.46MB。已有1111人学习下载资源结构规范目录层次清晰涵盖主界面、用户中心、景点展示、酒店预订及后台九大管理功能模块附带完整数据库设计与可直接部署运行的工程结构适合动手实践、课程设计或毕业设计参考。1. JavaWeb旅游管理系统JSPServletMySQL为什么它仍是高校毕设和中小后台的“稳态选择”你可能在头歌实训、黑马笔记或某份毕设开题报告里反复见过这个标题——JavaWeb旅游管理系统JSPServletMySQL。它不炫技不提Spring Boot不聊微服务但偏偏是计算机专业学生第一个能独立部署、老师能当场验功能、企业外包小单子敢直接用的完整闭环系统。这不是过时而是精准卡位它用最薄的技术栈覆盖了Web开发四大刚性环节——页面渲染JSP、业务调度Servlet、数据持久MySQL、请求流转HTTP协议原生落地。没有框架黑匣子所有跳转逻辑、表单提交、数据库增删查改都明明白白写在.java和.jsp里没有自动装配和注解魔法一个request.getParameter(username)就能让你看清参数怎么从浏览器飞进Java内存。适合两类人一是刚学完Java基础、正卡在“写了代码却不知道怎么连上网页”的新手需要一次从零搭起可访问URL的实感二是要快速交付一个带用户注册、线路浏览、订单提交、后台管理的轻量旅游信息平台的小团队——它不追求高并发但要求功能全、改得动、查得清、部署稳。下面我们就按真实项目节奏从环境筑基、分层编码、数据建模到部署排错一步步把它跑通、调顺、用熟。2. 环境筑基在IDEA中搭建可运行的JSPServlet最小工作流2.1 为什么必须用Tomcat 9.x而非10JSP兼容性血泪经验JSP本质是Servlet的语法糖其编译依赖Tomcat内置的Jasper引擎。Tomcat 10开始全面转向Jakarta EE 9规范将所有javax.*包名强制改为jakarta.*——这意味着你写的import javax.servlet.http.HttpServlet;在Tomcat 10里会直接报红。而绝大多数JSP教材、头歌实训模板、黑马笔记示例仍基于javax.*体系。实测结论Tomcat 9.0.87是当前JSP项目的黄金版本——它既支持JDK 11主流教学JDK又完全兼容javax.servlet生态且对Windows/Linux/macOS三端部署友好。别被“新版更安全”误导你花3小时配好Tomcat 10结果JSP页面404不如用Tomcat 9.0.87省下6小时debug时间。提示下载地址直取Apache官网归档页archive.apache.org/dist/tomcat/tomcat-9/v9.0.87/bin/选apache-tomcat-9.0.87.zip。解压后路径中严禁含中文、空格、特殊符号如D:\我的项目\tomcat→ 必翻车推荐C:\tomcat9或~/tomcat9。2.2 IDEA新建Dynamic Web Project5步完成无插件配置IntelliJ IDEA默认不内置JSP支持但无需安装额外插件。关键在Project Structure的精准配置# 步骤1新建Project → 选择Empty Project → Next → Finish # 步骤2右键Project → New → Module → 选Web Application注意不是Spring Initializr # 步骤3勾选Create web.xml → 设置Web resource directory为src/main/webapp # 步骤4File → Project Structure → Project → SDK选JDK 11非JDK 17Servlet 4.0需JDK 11 # 步骤5Artifacts → → Web Application: Archive → Output directory指向target/ROOT.war此时项目结构应为tourism-system/ ├── src/ │ ├── main/ │ │ ├── java/ # Servlet类放这里 │ │ └── webapp/ # JSP、CSS、JS、WEB-INF放这里 │ │ ├── index.jsp │ │ └── WEB-INF/ │ │ ├── web.xml # 核心配置文件 │ │ └── lib/ # 手动放mysql-connector-java-8.0.33.jar ├── target/ │ └── ROOT.war # 构建产物注意web.xml必须手动生成IDEA勾选时自建内容不能为空。最小化web.xml必须包含servlet和servlet-mapping声明否则所有Servlet 404。这是新手最常漏的一步。2.3 MySQL驱动注入为什么mysql-connector-java-8.0.33.jar必须放WEB-INF/libServlet容器Tomcat遵循“类加载双亲委派模型”但WEB-INF/lib是应用级类路径Application ClassLoader的专属目录。若把MySQL驱动jar放在Tomcat的lib目录下会导致所有Web应用共享同一连接池引发事务隔离混乱若仅在IDEA的Module Dependencies里添加jar编译期不报错但打包成WAR后该jar不会进入归档——运行时Class.forName(com.mysql.cj.jdbc.Driver)直接抛ClassNotFoundException。唯一可靠路径将mysql-connector-java-8.0.33.jar复制到src/main/webapp/WEB-INF/lib/下并在IDEA中右键该jar → Add as Library。验证方式打开WAR包用WinRAR或7z确认WEB-INF/lib/mysql-connector-java-8.0.33.jar存在且大小2MB。3. 分层编码从首页JSP到订单Servlet的7个核心文件落地3.1 首页index.jsp用JSTL替代Scriptlet的现代写法传统JSP滥用% %嵌入Java代码导致维护灾难。正确做法是用JSTLJSP Standard Tag Library做视图逻辑% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % html headtitle旅游管理系统/title/head body h1热门旅游线路/h1 c:forEach items${tours} vartour div classtour-card h3${tour.name}/h3 p价格fmt:formatNumber value${tour.price} typecurrency//p p出发地${tour.departure}/p a hreforder?tourId${tour.id}立即预订/a /div /c:forEach /body /html逻辑说明${tours}由Servlet通过request.setAttribute(tours, tourList)传入fmt:formatNumber自动处理货币格式避免手动拼¥符号c:forEach替代% for(...) { %彻底分离HTML与Java逻辑。参数说明urihttp://java.sun.com/jsp/jstl/core是JSTL 1.2标准命名空间对应jstl-1.2.jar——该jar必须与MySQL驱动一同放入WEB-INF/lib。3.2 TourServletGET请求处理首页数据POST处理搜索一个Servlet可同时响应GET/POST关键在doGet()与doPost()方法分工// src/main/java/com/tour/servlet/TourServlet.java WebServlet(/tour) public class TourServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 查询全部线路 ListTour tours new TourDAO().findAll(); // 2. 绑定到request作用域 req.setAttribute(tours, tours); // 3. 转发到index.jsp非重定向 req.getRequestDispatcher(/index.jsp).forward(req, resp); } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 获取搜索关键词 String keyword req.getParameter(keyword); // 2. 模糊查询 ListTour results new TourDAO().searchByKeyword(keyword); // 3. 重新绑定并转发 req.setAttribute(tours, results); req.getRequestDispatcher(/index.jsp).forward(req, resp); } }逻辑说明WebServlet(/tour)让该类响应/tour路径forward()是服务器端跳转URL不变request属性可传递redirect()是客户端跳转URL变request属性丢失——此处必须用forward。参数说明req.getParameter(keyword)获取表单namekeyword的值TourDAO是数据访问对象封装JDBC操作见3.4节。3.3 UserLoginServlet表单验证与Session状态管理登录不是简单比对密码需处理空值、SQL注入、密码加密、会话绑定// src/main/java/com/tour/servlet/UserLoginServlet.java WebServlet(/login) public class UserLoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); // 1. 基础校验防空提交 if (username null || username.trim().isEmpty() || password null || password.trim().isEmpty()) { req.setAttribute(error, 用户名或密码不能为空); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } // 2. 密码MD5加盐简化版生产环境用BCrypt String saltedPwd DigestUtils.md5Hex(password tour2024); // 3. 查询用户 User user new UserDAO().findByUsernameAndPassword(username, saltedPwd); if (user ! null) { // 4. 登录成功存入Session跳转首页 HttpSession session req.getSession(); session.setAttribute(currentUser, user); // 关键绑定用户状态 resp.sendRedirect(index.jsp); // 重定向避免F5重复提交 } else { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }逻辑说明req.getSession()创建或获取当前会话session.setAttribute(currentUser, user)将用户对象存入Session后续所有Servlet可通过session.getAttribute(currentUser)获取resp.sendRedirect()强制客户端跳转防止刷新导致重复登录。参数说明DigestUtils.md5Hex()来自Apache Commons Codec库需额外引入commons-codec-1.15.jar加盐字符串tour2024应存于配置文件不可硬编码。3.4 TourDAOJDBC连接池实战——为什么不用DriverManagerDriverManager.getConnection()每次调用都新建物理连接100并发即耗尽MySQL连接数。必须用连接池// src/main/java/com/tour/dao/TourDAO.java public class TourDAO { private static final String URL jdbc:mysql://localhost:3306/tourism?useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; // 1. 初始化HikariCP连接池推荐比DBCP更轻量 private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(URL); config.setUsername(USER); config.setPassword(PASSWORD); config.setMaximumPoolSize(20); // 最大连接数 config.setMinimumIdle(5); // 最小空闲连接 config.setConnectionTimeout(30000); // 连接超时30秒 config.setIdleTimeout(600000); // 空闲连接存活10分钟 config.setMaxLifetime(1800000); // 连接最大寿命30分钟 dataSource new HikariDataSource(config); } public ListTour findAll() { ListTour tours new ArrayList(); String sql SELECT id, name, price, departure, days FROM tour; try (Connection conn dataSource.getConnection(); // 从连接池取连接 PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Tour tour new Tour(); tour.setId(rs.getLong(id)); tour.setName(rs.getString(name)); tour.setPrice(rs.getBigDecimal(price)); tour.setDeparture(rs.getString(departure)); tour.setDays(rs.getInt(days)); tours.add(tour); } } catch (SQLException e) { e.printStackTrace(); // 实际项目应记录日志 } return tours; } }逻辑说明static块确保连接池全局单例try-with-resources自动关闭Connection/PreparedStatement/ResultSetHikariDataSource比BasicDataSource启动更快、性能更高。参数说明useSSLfalse禁用SSL本地开发可省证书配置serverTimezoneAsia/Shanghai解决MySQL时区与Java不一致导致的时间字段错乱maximumPoolSize20适配中小型系统过高反而增加MySQL负担。3.5 OrderServlet事务控制——为什么订单插入必须原子化旅游订单涉及多表操作插入order主表、插入order_item明细表、更新tour库存。任一环节失败必须全部回滚// src/main/java/com/tour/servlet/OrderServlet.java WebServlet(/order) public class OrderServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Long tourId Long.parseLong(req.getParameter(tourId)); Integer quantity Integer.parseInt(req.getParameter(quantity)); Connection conn null; try { conn TourDAO.getDataSource().getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 // 1. 插入订单主表 String orderSql INSERT INTO order (user_id, status, create_time) VALUES (?, unpaid, NOW()); try (PreparedStatement ps conn.prepareStatement(orderSql, Statement.RETURN_GENERATED_KEYS)) { ps.setLong(1, getCurrentUserId(req)); // 从Session获取当前用户 ps.executeUpdate(); ResultSet keyRs ps.getGeneratedKeys(); keyRs.next(); Long orderId keyRs.getLong(1); // 2. 插入订单明细 String itemSql INSERT INTO order_item (order_id, tour_id, quantity) VALUES (?, ?, ?); try (PreparedStatement psItem conn.prepareStatement(itemSql)) { psItem.setLong(1, orderId); psItem.setLong(2, tourId); psItem.setInt(3, quantity); psItem.executeUpdate(); } // 3. 更新线路库存假设有stock字段 String updateSql UPDATE tour SET stock stock - ? WHERE id ?; try (PreparedStatement psUpdate conn.prepareStatement(updateSql)) { psUpdate.setInt(1, quantity); psUpdate.setLong(2, tourId); int affected psUpdate.executeUpdate(); if (affected 0) throw new SQLException(库存不足); } conn.commit(); // 全部成功才提交 req.setAttribute(success, 订单创建成功); } } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } req.setAttribute(error, 订单创建失败 e.getMessage()); } finally { if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } req.getRequestDispatcher(/order_result.jsp).forward(req, resp); } }逻辑说明conn.setAutoCommit(false)开启事务conn.commit()显式提交conn.rollback()异常时回滚所有SQL操作必须在同一个Connection对象内执行。参数说明Statement.RETURN_GENERATED_KEYS让executeUpdate()返回自增主键getCurrentUserId(req)从HttpSession中提取用户ID需先登录stock字段需在tour表中预先添加ALTER TABLE tour ADD COLUMN stock INT DEFAULT 0。4. 数据建模MySQL旅游数据库的5张核心表设计与初始化4.1 表结构设计为什么tour表要拆出tour_category表旅游系统需支持“国内游/出境游/周边游”等分类若在tour表中用category VARCHAR(20)存储将导致分类修改需全表UPDATE如“出境游”改为“国际游”分类统计SQL复杂GROUP BY category无法利用索引无法约束分类值用户可能输错“出国游”正确方案建立独立分类表tour表通过外键关联-- 1. 分类表主数据人工维护 CREATE TABLE tour_category ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL UNIQUE, description VARCHAR(200) ); -- 2. 线路表业务表频繁读写 CREATE TABLE tour ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL DEFAULT 0.00, departure VARCHAR(50), days INT NOT NULL DEFAULT 1, category_id BIGINT NOT NULL, stock INT NOT NULL DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (category_id) REFERENCES tour_category(id) ); -- 3. 用户表含角色字段支持管理员/普通用户 CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, -- 存储MD5或BCrypt密文 real_name VARCHAR(50), phone VARCHAR(20), role ENUM(admin, user) DEFAULT user, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 4. 订单主表 CREATE TABLE order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, status ENUM(unpaid, paid, cancelled) DEFAULT unpaid, total_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES user(id) ); -- 5. 订单明细表一对多 CREATE TABLE order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, tour_id BIGINT NOT NULL, quantity INT NOT NULL DEFAULT 1, price DECIMAL(10,2) NOT NULL, -- 快照价格避免线路调价影响历史订单 FOREIGN KEY (order_id) REFERENCES order(id), FOREIGN KEY (tour_id) REFERENCES tour(id) );提示ENUM类型比VARCHAR节省空间且保证取值范围price在order_item中冗余存储确保订单价格不随线路调价而变动FOREIGN KEY约束需在InnoDB引擎下生效建表时指定ENGINEInnoDB。4.2 初始化测试数据用INSERT SELECT生成100条模拟线路手动INSERT 100条数据效率极低。用MySQL内置函数批量生成-- 插入5个分类 INSERT INTO tour_category (name, description) VALUES (国内游, 中国大陆境内旅游线路), (出境游, 港澳台及海外旅游线路), (周边游, 城市周边一日/两日游), (亲子游, 适合家庭儿童的旅游产品), (摄影游, 以摄影采风为主题的深度游); -- 批量插入100条线路使用RAND()和CONCAT生成随机数据 INSERT INTO tour (name, price, departure, days, category_id, stock) SELECT CONCAT(【, tc.name, 】, SUBSTRING(MD5(RAND()), 1, 8), 深度游) AS name, ROUND(99 (RAND() * 9001), 2) AS price, -- 99~9100元 ELT(FLOOR(1 RAND() * 4), 北京, 上海, 广州, 成都) AS departure, FLOOR(1 RAND() * 7) AS days, tc.id AS category_id, FLOOR(10 RAND() * 90) AS stock FROM tour_category tc JOIN (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t1 JOIN (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t2 JOIN (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t3 LIMIT 100;逻辑说明ELT()函数根据序号返回对应字符串FLOOR(1 RAND() * 4)生成1~4的随机整数三重JOIN生成125行笛卡尔积LIMIT 100截取前100行SUBSTRING(MD5(RAND()), 1, 8)生成8位随机字符串作线路名后缀。参数说明ROUND(99 (RAND() * 9001), 2)确保价格为两位小数FLOOR(10 RAND() * 90)生成10~99的库存值避免0库存导致订单失败。4.3 索引优化3个必建索引让查询快10倍无索引的WHERE条件是全表扫描10万行数据查询需秒级。针对高频查询场景建索引查询场景SQL示例推荐索引说明按分类查线路SELECT * FROM tour WHERE category_id ?INDEX idx_category_id (category_id)外键字段必须建索引否则JOIN性能暴跌按用户查订单SELECT * FROM \order WHERE user_id ? ORDER BY create_time DESCINDEX idx_user_time (user_id, create_time)联合索引覆盖查询排序避免filesort模糊搜线路名SELECT * FROM tour WHERE name LIKE %三亚%FULLTEXT(name)全文索引支持中文分词需MySQL 5.7且表引擎为MyISAM或InnoDB建索引命令-- 1. 分类外键索引 ALTER TABLE tour ADD INDEX idx_category_id (category_id); -- 2. 用户订单联合索引 ALTER TABLE order ADD INDEX idx_user_time (user_id, create_time); -- 3. 线路名全文索引InnoDB需5.6 ALTER TABLE tour ADD FULLTEXT(name); -- 使用SELECT * FROM tour WHERE MATCH(name) AGAINST(三亚 IN NATURAL LANGUAGE MODE);注意FULLTEXT索引对中文支持有限若需强中文搜索建议集成Elasticsearchidx_user_time中create_time必须放第二位因B树索引最左匹配原则WHERE user_id? ORDER BY create_time才能走索引。5. 部署排错从本地启动到Linux上线的5类高频故障排查5.1 启动报错java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver现象Tomcat启动后访问首页控制台抛ClassNotFoundException页面显示500错误。原因MySQL驱动jar未正确加载。常见有三处错误① jar未放入WEB-INF/lib② jar文件名含-sources.jar或-javadoc.jar应选mysql-connector-java-8.0.33.jar③ IDEA中未将jar添加为Library右键jar → Add as Library。解决检查target/ROOT.war包内WEB-INF/lib/是否含mysql-connector-java-8.0.33.jar若用Maven管理依赖需在pom.xml中添加scopeprovided/scope因Tomcat不提供JDBC驱动必须打包进WARdependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version scopeprovided/scope !-- 关键 -- /dependency5.2 页面404The requested resource [/tour] is not available现象浏览器输入http://localhost:8080/tour返回404但http://localhost:8080/index.jsp可访问。原因Servlet未被容器识别。根本原因有二①WebServlet(/tour)注解未生效因Servlet 3.0需web.xml中声明web-app版本为3.0②web.xml中遗漏servlet声明当使用注解时web.xml仍需声明web-app version3.0。解决打开src/main/webapp/WEB-INF/web.xml确认根节点为web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_0.xsd version3.0若版本为2.5改为3.0并重启Tomcat。5.3 数据库连接失败Communications link failure现象Servlet中dataSource.getConnection()抛Communications link failure控制台显示Connection refused。原因MySQL服务未启动或连接参数错误。重点排查①URL中端口是否为3306mysql -u root -p登录验证②localhost在Linux中解析为127.0.0.1但MySQL可能只监听::1IPv6③ MySQL用户权限未授权本地访问。解决Linux下用sudo systemctl status mysqld确认服务运行将URL中的localhost改为127.0.0.1绕过IPv6解析执行SQL授权GRANT ALL PRIVILEGES ON tourism.* TO root127.0.0.1 IDENTIFIED BY 123456; FLUSH PRIVILEGES;。5.4 中文乱码页面显示“????”或数据库存入“??”现象JSP页面中文正常但数据库查出的中文为问号或表单提交的中文在数据库中变为乱码。原因字符集未统一。需同时配置四层① MySQL服务端my.cnf② MySQL数据库/表③ JDBC连接URL④ Tomcat server.xml。解决MySQL配置/etc/my.cnf[client] default-character-set utf8mb4 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci创建数据库时指定CREATE DATABASE tourism CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;JDBC URL追加参数?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiTomcatconf/server.xml中Connector添加URIEncodingUTF-8Connector port8080 protocolHTTP/1.1 URIEncodingUTF-8 /5.5 WAR部署失败Linux下Tomcat启动但页面空白现象将target/ROOT.war上传至Linux的$CATALINA_HOME/webapps/Tomcat日志无报错但访问http://ip:8080/返回空白页。原因Linux文件权限或SELinux拦截。常见于CentOS/RHEL系统。解决检查WAR包权限sudo chown -R tomcat:tomcat $CATALINA_HOME/webapps/ROOT.war关闭SELinux临时验证sudo setenforce 0若恢复则setenforce 1查看Tomcat日志定位tail -f $CATALINA_HOME/logs/catalina.out重点关注SEVERE级别错误强制解压WAR删除ROOT.war和ROOT/目录重新上传WAR确保Tomcat有写权限解压。6. 进阶技巧用Filter实现统一登录拦截与请求日志6.1 LoginFilter30行代码拦截未登录访问所有需要登录的页面如/admin/*,/order/*不应靠前端链接隐藏而应由服务端强制校验。Filter是最佳选择// src/main/java/com/tour/filter/LoginFilter.java WebFilter(filterName LoginFilter, urlPatterns {/admin/*, /order/*, /user/*}) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(false); // 不创建新session // 1. 检查Session中是否存在currentUser if (session null || session.getAttribute(currentUser) null) { // 2. 未登录重定向到登录页并保存原请求URL String originalUrl req.getRequestURL().toString(); if (req.getQueryString() ! null) { originalUrl ? req.getQueryString(); } session.setAttribute(originalUrl, originalUrl); resp.sendRedirect(req.getContextPath() /login.jsp); return; } // 3. 已登录放行 chain.doFilter(request, response); } }逻辑说明WebFilter(urlPatterns {/admin/*, ...})声明拦截路径session.getAttribute(currentUser)复用登录Servlet存入的对象session.setAttribute(originalUrl, ...)保存用户原始请求登录成功后跳转回来需在UserLoginServlet中读取并重定向。参数说明req.getContextPath()获取应用上下文如/tourism避免硬编码路径chain.doFilter()是过滤链核心不调用则请求终止。6.2 LogFilter记录每个请求的耗时与参数调试时需快速定位慢接口LogFilter可打印关键指标// src/main/java/com/tour/filter/LogFilter.java WebFilter(/*) public class LogFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; long startTime System.currentTimeMillis(); // 记录请求开始 System.out.printf([LOG] %s %s %s?%s%n, new Date(), req.getMethod(), req.getRequestURI(), req.getQueryString() null ? : req.getQueryString()); try { chain.doFilter(request, response); } finally { long duration System.currentTimeMillis() - startTime; // 记录响应结束 System.out.printf([LOG] %s %s %s %dms%n, new Date(), req.getMethod(), req.getRequestURI(), duration); } } }技巧将此Filter放在LoginFilter之后按WebFilter的dispatcherTypes或Order注解控制顺序避免登录重定向也被计时生产环境应替换System.out为Log4j2或SLF4J配置异步日志避免阻塞。6.3 Filter链执行顺序与责任分离多个Filter按WebFilter的value或urlPatterns匹配但顺序由Order注解或web.xml中filter-mapping顺序决定。推荐显式声明WebFilter(urlPatterns /*) Order(1) // 数字越小优先级越高 public class CharsetFilter implements Filter { /* 强制设置request/response编码 */ } WebFilter(urlPatterns /*) Order(2) public class LogFilter implements Filter { /* 日志记录 */ } WebFilter(urlPatterns {/admin/*, /order/*}) Order(3) public class LoginFilter implements Filter { /* 登录校验 */ }我的习惯永远把CharsetFilter放在第一位解决中文乱码根源LogFilter第二位记录原始请求业务Filter第三位。这样日志里看到的URL和参数都是解码后的干净数据排查问题时少踩50%的编码坑。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑