ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JavaWeb课程设计实战:在线旅游网站从环境搭建到部署避坑全解析

JavaWeb课程设计实战:在线旅游网站从环境搭建到部署避坑全解析 简介一份面向JavaWeb课程设计与期末大作业的在线旅游网站项目内含完整源代码、数据库脚本及配套文档说明。项目基于Java服务端技术实现了旅游线路展示、关键字搜索、路线详情等常用模块并整合Bootstrap等前端资源页面风格统一适合计算机相关专业学生用于课设、毕设或项目初期演示。压缩包共1870个文件大小约70.29MB其中以1753张JPG图片页面截图及素材为主辅以39个Java源文件、12个HTML、10个CSS、7个JS文件同时包含SQL数据库脚本、Eclipse项目配置文件及字体图标资源目录结构清晰便于按需查阅与导入开发环境。已有756人浏览学习代码经过测试运行成功可直接作为课程作业参考资源内附文档说明可帮助初学者理解项目结构与实现思路也适合在此基础上扩展功能、完成二次开发。1. 在线旅游网站的 JavaWeb 课程设计这份源代码和文档说明到底值不值得信期末前两周才打开老师给的题目是 JavaWeb 课程设计最常见的时间线。这个标题下面的东西其实分两部分一份能在 Tomcat 里跑起来的在线旅游网站源代码和一份能讲清设计思路的文档说明。它的价值不在于代码量有多大而在于你不用从 Servlet 接口开始抠改改名字、换换业务就能把它变成能答辩的作业。适合三类人第一次做 JavaWeb 课程设计的、时间不够又不想整段复制的、答辩时怕被老师问住的人。接下来我会按一条复现路线来讲环境 → 建库 → 登录 → 线路列表 → 下单收藏 → 避坑 → 验收每步都能直接落地。2. JavaWeb 项目完整案例 MySQL 起步先把环境、建表和项目骨架一次配好一个能跑的项目80% 的功夫在环境而不是代码。新电脑上我配过不下十次 IDEA 的 JavaWeb 环境每次套路都差不多。这一章先把三条主线走完导入或创建项目、MySQL 建库、Maven 依赖这三步不出错后面写代码基本一路绿灯。2.1 在 IDEA 里跑通 JavaWeb 项目三个必不可少的运行配置无论你用的 IDEA 是 2023 还是最新的 2026New Project 向导再怎么改版只要注意三个地方成功率都很高。第一是项目导入方式课程设计通常给的是一个压缩包解压后如果是 Maven 项目直接 File → Open 选到 pom.xml 那一层如果给的是纯 Web 目录里面没有 pom.xml就在 IDEA 里新建一个普通 Java 项目把 src 目录整体拷进去再右键 Project Structure → Modules → 加 Web 模块。第二是 SDK 版本项目用的 JDK 要和你本地 Tomcat 支持的版本一致一般 JDK 8 或 11 最稳别为了追求新装个 JDK 21 去跑 Tomcat 9容易在类加载阶段冒出奇怪问题。第三是 Tomcat 配置Run → Edit Configurations → 加 Tomcat Server → Local在 Server 页签选本地 Tomcat 目录然后到 Deployment 页签点加号选 Artifact → 后缀为 war exploded 的那个。这里有一个最容易被忽略的细节Deployment 页签里的 Application context。默认值是一长串建议改成/travel。它决定整个项目在浏览器里的访问路径也决定了 JSP 页面里所有静态资源引用的开头。如果留空后面 index.jsp 里引用 CSS 的路径会全部对不上页面会变成一堆没有样式的文字。JSP 里引用资源时不要写死成/travel/css/xxx课程设计一旦换了 context path全站资源全部 404。更稳的写法是动态拼接link relstylesheet href${pageContext.request.contextPath}/css/common.css script src${pageContext.request.contextPath}/js/index.js/script这段代码把项目根路径动态拼到资源路径最前面。${pageContext.request.contextPath}在 JSP 环境里等于 Application context 里填的值部署到任意路径下都能自适应。它和直接写/travel/...的效果一样但前者可以跨目录部署后者改一次路径要改十几个文件。运行时如果 Tomcat 启动成功但访问是 404先看 IDEA 控制台的信息出现 “Artifact is being deployed” 和INFO: Server startup in xxx ms两行日志说明部署完成如果只有启动日志没有部署日志多半是 Artifact 没配好回去检查 Deployment 页签。常见做法是给 Tomcat 单独指定一个干净的 CATALINA_BASE把之前残留的旧项目清掉。JDK 8 以上的项目用WebServlet注解即可不需要额外配 web.xml这一点在文档说明里写清楚能省很多事。2.2 数据库脚本先落地五张表把业务锁住在线旅游网站的核心表不复杂常见做法是用户表、线路表、订单表、收藏表。消息公告要不要加一张看课程设计文档说明里有没有“新闻公告”功能要求没要求就不要自己加表越多后期改起来越痛苦。下面这段 SQL 我整理过库名、字段名和注释都按课程设计文档常用的习惯写MySQL 5.7 和 8.0 都兼容CREATE DATABASE IF NOT EXISTS travel DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE travel; CREATE TABLE t_user ( uid INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 密码MD5 后存放, nickname VARCHAR(30) COMMENT 昵称, phone VARCHAR(11), email VARCHAR(50), activate TINYINT DEFAULT 1 COMMENT 1 正常0 禁用 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE t_route ( rid INT PRIMARY KEY AUTO_INCREMENT, rname VARCHAR(100) NOT NULL COMMENT 线路名称, price DECIMAL(10,2) NOT NULL COMMENT 人均价格, route_intro TEXT COMMENT 行程介绍, stock INT DEFAULT 100 COMMENT 剩余可预订名额, rdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 上架时间, rflag TINYINT DEFAULT 1 COMMENT 1 上架0 下架, rimage VARCHAR(200) COMMENT 封面图相对路径 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT旅游线路表; CREATE TABLE t_order ( oid INT PRIMARY KEY AUTO_INCREMENT, uid INT NOT NULL, rid INT NOT NULL, order_time DATETIME DEFAULT CURRENT_TIMESTAMP, people INT DEFAULT 1 COMMENT 出游人数, contact_name VARCHAR(30) COMMENT 联系人, contact_phone VARCHAR(11) COMMENT 联系电话, pay_status TINYINT DEFAULT 0 COMMENT 0 未支付1 已支付, CONSTRAINT fk_order_user FOREIGN KEY (uid) REFERENCES t_user(uid), CONSTRAINT fk_order_route FOREIGN KEY (rid) REFERENCES t_route(rid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; CREATE TABLE t_favorite ( uid INT NOT NULL, rid INT NOT NULL, fav_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (uid, rid), CONSTRAINT fk_fav_user FOREIGN KEY (uid) REFERENCES t_user(uid), CONSTRAINT fk_fav_route FOREIGN KEY (rid) REFERENCES t_route(rid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT收藏表;建表脚本里有两个参数值得专门说明。一是字符集统一用utf8mb4不要只写utf8。MySQL 的utf8最多存 3 个字节而 emoji 和生僻字要占 4 个字节在线旅游网站里用户昵称一旦带 emoji用utf8的表会直接写入报错二是密码字段留 64 位配合 MD5 或 SHA-256 这类摘要算法存放不要建 20 位课程设计里密码明文入库是答辩老师最高频指出的问题。接着初始化一点数据方便登录和首页展示INSERT INTO t_user(username, password, nickname, phone) VALUES (admin, MD5(123456), 管理员, 13800000000); INSERT INTO t_route(rname, price, route_intro, stock, rimage) VALUES (云南大理丽江 5 日游, 3280.00, 洱海骑行、玉龙雪山、丽江古城, 80, images/dali.jpg), (三亚纯玩 4 日游, 2580.00, 亚龙湾、蜈支洲岛、海鲜大餐, 50, images/sanya.jpg), (川西稻城亚丁 6 日游, 3680.00, 新都桥、稻城亚丁、高原湖泊, 60, images/daocheng.jpg);初始化数据里用MD5(123456)而不是直接写死一串散列值好处是文档说明里写“默认账号 admin / 123456”时别人能对上自己换密码也方便。注意不同 MySQL 版本的 MD5 函数返回结果一致这点放心。2.3 项目骨架与 Maven 依赖不引框架也能让代码不散装课程设计阶段引 Spring 全家桶是冒险行为一来答辩老师大概率会问“Spring 帮你做了什么你自己写了什么”答不好反而扣分二来 Maven 解析依赖耗时教室没外网时卡在下载这里很难看。纯 JSP Servlet JDBC 是最稳的组合代码量不大每个环节都能说清楚。Maven 的 pom.xml 里我一般只保留这几个依赖dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.20/version /dependency /dependencies版本号不必死盯这一组能拉到即可用 8.0.x 和 1.2.x 的后续版本也可以。重点说明两处javax.servlet-api的 scope 必须是provided因为 Tomcat 自己带 Servlet 实现带上它会和容器里的类冲突导致一些莫名其妙的 NoSuchMethodErrorDruid 负责连接池它的细节先不用深究课设阶段知道它比 DriverManager 手动管理连接更稳定、也更合答辩口味就行。包结构我习惯这样组织不硬套三层架构但一定要拆出清晰的层src/main/java/com/example/travel ├── domain -- User、Route、Order、Favorite 的 POJO ├── dao -- JDBC 数据访问只负责 SQL ├── service -- 业务逻辑含事务 ├── servlet -- 所有控制器继承 BaseServlet ├── filter -- 编码过滤、登录拦截 └── util -- DbUtils、MD5Utilsdao层和service层分开是 JavaWeb 教学案例里的常规动作哪怕 service 层只是简单转调也建议留出来。答辩被问“为什么这么分层”时你能说出“dao 管数据怎么存service 管业务规则和事务servlet 只做参数接收和页面跳转”这一句话的印象分比多写十个接口都值钱。util里放个 Md5Utils所有密码统一走 md5 后再入库也是避免散装代码的细节。3. 从登录到线路列表把在线旅游网站的核心功能用代码串起来环境跑通、表建好之后剩下的都是体力活。这一章不看前端样式只看链路浏览器发请求 → servlet 接收 → dao 查库 → service 处理 → JSP 渲染。把这条链路走顺后面加任何页面都只是复制粘贴。3.1 用 BaseServlet 做请求分发不用再写一堆 doGet/doPost一个在线旅游网站至少有登录、注册、退出、线路列表、线路详情、下单、收藏这 7 个操作。如果每个功能都重写 doGet 和 doPost代码里全是重复的转发逻辑。常见的轻量方案是写一个 BaseServlet让所有业务 Servlet 继承它通过 URL 上的 method 参数来定位要执行的方法。import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Method; public class BaseServlet extends HttpServlet { Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String methodName req.getParameter(method); if (methodName null || methodName.isEmpty()) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, 缺少 method 参数); return; } try { Method method this.getClass() .getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); method.invoke(this, req, resp); } catch (NoSuchMethodException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, 找不到对应方法); } catch (Exception e) { throw new ServletException(方法调用失败, e); } } }这段代码的核心不是反射本身而在于统一了请求入口。子类里写一个public void login(HttpServletRequest, HttpServletResponse)URL 里带methodlogin框架就会自动执行这个方法。它把 doGet/doPost 的样板代码压缩成一个方法名参数新功能只需要加方法不用改任何转发配置。需要注意getMethod只能找到 public 方法所以业务方法必须写成 public方法签名要和反射调用一致。有些同学把方法写成 private或者多写了一个参数启动时不报错一访问就提示“找不到对应方法”。出现这种问题先重新编译再检查 URL 里的参数名是否拼对。具体业务 Servlet 上用WebServlet(/route)注解子类不会被父类影响。3.2 登录校验与 Session为什么登录状态不能只靠 JSP 判断登录是最常见的踩坑点不少教学案例直接在 login.jsp 里嵌% if (admin.equals(...)) %这种写法虽然能演示但根本挡不住别人直接访问内部页面。正确做法是让登录逻辑走 Servlet再把用户信息写进 Session由 Filter 统一做拦截。LoginServlet 的核心方法public void login(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { String username req.getParameter(username); String password req.getParameter(password); if (username null || username.trim().isEmpty() || password null || password.isEmpty()) { req.setAttribute(msg, 用户名或密码不能为空); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } User user userService.login(username.trim(), Md5Utils.md5(password)); if (user null) { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } req.getSession().setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /route?methodlistcurrentPage1); }这段代码有几个细节值得在文档说明里写清楚。第一层校验是参数非空这是后端必做的不要指望前端表单一定可靠第二层校验交给 service密码在进 SQL 之前已经用 md5 做了一次摘要数据库里始终不出现明文第三层是 Session 的写入时机只有在 user 不为 null 时才写入失败时用请求转发带着 msg 回到登录页。注意最后一行用了sendRedirect而不是forward这是防止“刷新后重复提交登录表单”的关键后面第 4 章还会用到同一个原则。Session 里存整个User对象而不是布尔标志原因很实际页面右上角要显示昵称订单页要拿 uid 做关联把那几个字段分开存Session 里会堆一堆 key。对应的登录拦截 Filter 长这样WebFilter(/user/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); if (session null || session.getAttribute(loginUser) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }这段代码的拦截范围只对/user/*路径生效所以受保护页面的访问路径要按这个前缀组织例如/user/orderList.jsp放在 WebContent 下的 user 目录里。getSession(false)不会创建新 Session未登录用户直接跳登录页已登录用户正常放行。如果路径组织乱了这个 Filter 会拦截不到一部分页面出现“明明要求登录才能看不登录也能打开”的漏洞。3.3 首页线路列表与分页JDBC 查询结果怎么在 JSP 里渲染首页通常是一个带分页的线路列表。常见做法是维护一个 PageInfo 对象包含当前页、每页条数、总记录数和当页数据。先看 DAO 里的分页查询public ListRoute findPage(int start, int pageSize) throws SQLException { String sql SELECT rid, rname, price, route_intro, rimage, stock FROM t_route WHERE rflag 1 ORDER BY rdate DESC LIMIT ?, ?; // DruidUtils.getConnection() 取连接 // ps.setInt(1, start); // ps.setInt(2, pageSize); // 执行查询封装成 Route 对象放入 List }SQL 里的LIMIT ?, ?第一个参数是偏移量第二个是数量。页面传过来的是第几页要换算成偏移量(currentPage - 1) * pageSize这个换算容易漏漏掉以后第二页起数据全对不上。每页 8 条对课设比较合适太少显得数据稀太多分页按钮用不上答辩时不好展示。在 Servlet 里把 PageInfo 塞进 requestint currentPage parseInt(req.getParameter(currentPage), 1); int pageSize 8; PageInfoRoute pageInfo routeService.page(currentPage, pageSize); req.setAttribute(pageInfo, pageInfo); req.getRequestDispatcher(/index.jsp).forward(req, resp);JSP 页面用 JSTL 循环渲染不要用% for %脚本片段c:forEach items${pageInfo.list} varroute div classroute-card h3 a href${pageContext.request.contextPath}/route?methoddetailrid${route.rid} ${route.rname} /a /h3 p classprice${route.price} 起/p p${route.routeIntro}/p p剩余名额${route.stock}/p /div /c:forEachc:forEach的items指向pageInfo.listvar是循环变量名等价于 Java 里的增强 for。用它而不用 scriptlet一是 JSP 文件里不出现 Java 代码二是答辩时能回答出“我用了 JSTL 做页面展示逻辑”这是 JavaWeb 教学案例里比较受认可的做法。页脚再加一组分页链接把currentPage传到同一个route?methodlist上即可。注意边界判断第一页不显示“上一页”最后一页不显示“下一页”用c:if包一下这种小细节评审老师也会注意到。4. 订单与收藏让在线旅游网站的业务真正形成闭环很多课设项目做到这里就停了首页能看线路能点然后没有然后了。在线旅游网站之所以叫“在线”核心在预订动作订单和收藏这两块是拉开差距的地方。4.1 线路详情与“立即预订”URL 传参的隐藏坑列表页的每个线路名都指向/route?methoddetailrid${route.rid}详情页 Servlet 接收 rid 后查单条线路public void detail(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String rid req.getParameter(rid); if (rid null || !rid.matches(\\d)) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, rid 参数不合法); return; } Route route routeService.findById(Integer.parseInt(rid)); if (route null || route.getRflag() ! 1) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, 线路不存在或已下架); return; } req.setAttribute(route, route); req.getRequestDispatcher(/route_detail.jsp).forward(req, resp); }这段代码真正要防的不是格式错误而是两类真实情况列表页拿到的 rid 可能在用户手动改 URL 时被传成负数或非数字字符一条已下架线路的历史链接还能访问详情页等于绕过业务规则。所以先做正则校验再查库判断 rflag 状态。matches(\\d)只接受纯数字解析前顺手做掉能少写一堆异常分支。findById返回 null 的情况也要兜住否则 Integer.parseInt 成功但查不到记录时页面会直接抛 NullPointerException。详情页的“立即预订”按钮指向/order?methodaddrid${route.rid}注意这个请求必须带 rid。我见过有人把 rid 存在 Session 里用户开两个标签页后下单结果下了另一条线路的单这种症状特别难查所以 URL 参数该传就传别依赖全局状态。4.2 下单与事务为什么订单入一半是课设里高频翻车现场下单动作至少包含两个写操作插入订单记录、扣减线路库存。如果只调用两条 SQL 却不在同一个事务里插入订单成功后扣库存失败页面会提示“下单失败”数据库里却多了一条脏订单反过来也一样。这种问题靠代码 review 很难发现一定要在 service 层开事务。手动事务的代码骨架如下public boolean addOrder(Order order, int rid) { Connection conn null; try { conn DruidUtils.getConnection(); conn.setAutoCommit(false); OrderDao orderDao new OrderDao(); RouteDao routeDao new RouteDao(); orderDao.insert(conn, order); routeDao.decreaseStock(conn, rid, order.getPeople()); conn.commit(); return true; } catch (Exception e) { if (conn ! null) conn.rollback(); throw new RuntimeException(下单失败订单与库存已回滚, e); } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException ignored) { } } } }这段代码的细节集中在三处。第一setAutoCommit(false)必须在拿到连接后立刻执行它之后的 insert 和 update 都处于同一个事务里第二catch里rollback()保证两条 SQL 要么都成功要么都不生效第三finally里恢复setAutoCommit(true)是关键习惯因为连接是从 Druid 连接池借的归还前不恢复自动提交这个连接被后续请求借走时会沿用 false 状态导致后面的操作全部不提交这种玄学问题排查起来极其消耗时间。参数order.getPeople()对应 t_order 里的出游人数它同时用于扣减库存两件事本质是同一个业务动作。有的课设会在 Order 对象里带上totalPrice订单金额要在 service 层重新用price * people计算并覆盖前端传来的值避免用户篡改表单金额这一步叫服务端重算金额建议在文档说明里强调老师非常爱问。4.3 收藏功能与联合主键防重复收藏靠数据库不靠前端按钮收藏表的核心设计是联合主键(uid, rid)它保证一个用户对一条线路至多一条收藏记录重复收藏会被数据库直接拒绝。很多同学在收藏按钮上用前端disabled防二次点击但浏览器刷新、双开页面、网络重试都会绕过按钮状态所以最终防线要落在数据库约束上。收藏的 DAO 可以用一条 upsert 语句public void saveFavorite(Favorite fav) throws SQLException { String sql INSERT INTO t_favorite(uid, rid, fav_time) VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE fav_time NOW(); // 用 PreparedStatement 执行 }INSERT ... ON DUPLICATE KEY UPDATE是 MySQL 的写法逻辑是如果插入时碰到联合主键冲突就改为更新 fav_time。这样收藏按钮永远是幂等的点十次和点一次在数据上只有时间戳的差别不会产生重复行。收藏状态查询也走同一个表查t_favorite里是否存在uid rid的记录存在渲染“已收藏”不存在渲染“收藏”。由于有联合主键兜底按钮文案和真实状态不一致的情况会少很多。收藏或下单之后要立即做一次sendRedirect把 URL 重定向回列表页或详情页不要在 POST 之后直接forward到成功页。原因是刷新页面时浏览器会重新提交最后一次请求如果最后一个动作是 forward刷新就等于再执行一次收藏或下单重定向之后浏览器最后一次请求变成 GET刷新只会重新拉页面不会再写数据。这个模式叫 PRG答辩时可以提这个词和 3.2 登录成功用sendRedirect是同一个理由。5. 避坑指南JavaWeb 课设里最常见的五个翻车现场这一章把排查过程中遇到最多的五个问题写出来每一条都按“现象 → 原因 → 解决”展开最后一条尤其隐蔽。5.1 启动就报 ClassNotFoundException: javax.servlet.*现象Tomcat 启动直接报错或者浏览器打开首页出现 500控制台提示ClassNotFoundException: javax.servlet.http.HttpServlet但代码里 import 明明没错pom 里也配了依赖。原因版本换代问题。Tomcat 10 起Servlet API 的包名从javax.servlet迁移到jakarta.servlet。如果你的项目基于旧代码写用的是javax.servlet-api依赖跑在 Tomcat 10 上必然找不到类。解决两个方向选一个。一是换 Tomcat 9这是课程设计最省事的解法代码和依赖都不动二是把所有import javax.servlet改成import jakarta.servlet同时把 pom 里的 Servlet API 依赖换成jakarta.servlet-api5.x。我一般建议先看代码里 import 了多少处超过十处就换 Tomcat别跟包名较劲。5.2 表单提交后中文全部变问号现象注册页面昵称填“小明”存进数据库变成“”但查询页面显示却正常。原因三个环节至少有一个字符集不一致。MySQL 表字符集、JDBC 连接串里的 characterEncoding 参数、Servlet 请求编码任一个不是 UTF-8 就会出现这种问题最常见的是连接串没加 characterEncoding。解决三步全部设成 UTF-8。第一步建库建表用utf8mb4第二步 JDBC 连接串写全jdbc:mysql://localhost:3306/travel?characterEncodingutf8useUnicodetrueserverTimezoneAsia/Shanghai第三步加一个编码 Filter逻辑可以参考 3.2 里的登录拦截 Filter去掉登录判断即可。注意时机Filter 必须在getParameter之前设置request.setCharacterEncoding(UTF-8)否则参数已经被按默认 ISO-8859-1 解码了再设置就不起作用。同理会话里的中文乱码大部分也是因为响应没设resp.setContentType(text/html;charsetUTF-8)。5.3 MySQL 8 连接报 Public Key Retrieval is not allowed现象项目换到 MySQL 8 环境后第一次获取连接就抛SQLNonTransientConnectionException: Public Key Retrieval is not allowed页面直接 500。本地 5.7 跑得好好的迁移到机房就炸。原因MySQL 8 默认的认证插件是caching_sha2_password客户端第一次连接需要向服务端请求公钥来加密密码但 JDBC 驱动出于安全考量默认不允许自动获取公钥连接串里没授权就在握手阶段被拒绝。解决在连接串末尾追加allowPublicKeyRetrievaltrueuseSSLfalse。useSSLfalse是为了避免本地开发环境因为证书问题又冒出新的 SSL 握手报错。注意这两个参数是 JDBC URL 上的不是 MySQL 配置项改完连接串必须重启项目才生效。如果机房 MySQL 是 8 以上但驱动太老也可能触发这个报错先把 mysql-connector-java 升到 8.0.x 再排查。5.4 订单时间在 JSP 页面显示成一串数字现象订单列表页上order.orderTime输出了一串类似2026-04-01 13:20:33.0的长字符串格式难看和文档说明里的截图对不上。原因java.sql.Timestamp的toString()输出包含毫秒EL 表达式${order.orderTime}直接调用了 toString所以显示不是预期格式。这不算代码错误但答辩时看起来很不专业。解决在 JSP 顶部引入 JSTL 的 fmt 标签库然后用 formatDate 格式化% taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % fmt:formatDate value${order.orderTime} patternyyyy-MM-dd HH:mm/fmt:formatDate要求 value 是java.util.Date类型而java.sql.Timestamp继承自java.util.Date可以直接传。pattern 里HH是 24 小时制hh是 12 小时制这个字母大小写经常被拼错拼错后会出现同一张表里上下午时间错乱的问题。线路的上架日期同样建议统一用这个标签处理。5.5 刷新页面订单多了一条现象用户点“提交订单”后成功跳到订单列表页顺手刷一下订单列表里多了一模一样的记录。收藏也是同样的问题刷新后收藏时间不停被更新。原因表单提交是 POST 请求提交成功后如果forward到成功页浏览器地址栏仍是 POST 地址刷新就会重发 POST再执行一次下单或收藏逻辑。前端按钮置灰只能挡新手挡不住浏览器刷新。解决下单、收藏成功后一律sendRedirect也就是 PRG 模式。让最后一次响应变成 302 GET刷新只会重新请求列表页不会重放写操作。想做得更严格可以在页面生成一次性 token 存进 Session提交时校验后立即失效双保险。对课设来说 PRG 足够token 可以在文档说明里作为扩展点提一句。6. 验收前的自查让项目在老师电脑上也能跑起来再好的代码只要不能在教室电脑上跑一遍前面全白搭。我给自己做的项目预留半小时验收时间按下面这张清单过一遍。检查项具体操作通过标准数据库导出Navicat 或 MySQL Workbench 转储 SQL 文件导入后所有表和数据都在字符集为 utf8mb4运行环境记录 JDK 版本、Tomcat 版本与项目 pom 依赖匹配避免 javax/jakarta 冲突初始账号用 admin / 123456 登录登录后右上角正常显示昵称演示路径首页 → 线路详情 → 收藏 → 下单 → 订单列表五个页面全部可点通不出现 404端口检查8080 被占用时用命令处理服务能正常启动Tomcat 端口被占用是最常见的环境问题直接给一条排查命令netstat -ano | findstr 8080看到 PID 后用taskkill /PID 对应PID /F结束进程。注意先把占用端口的程序列表看清楚再动手机房电脑上经常有已打开的旧 Tomcat 在后台占着 8080不要误杀其他同学的进程。启动后可以用一个最基础的冒烟验证代替手工点页面curl -I http://localhost:8080/travel/返回HTTP/1.1 200说明 Tomcat 部署没问题如果 404先检查 Application context 是否写成/travel再看部署里的 Artifact 是不是 exploded 模式这两处是 IDEA 运行 JavaWeb 项目配置里最容易出岔子的点。文档说明这一块我习惯把环境版本、初始账号、从建库到启动的三步命令放在最前面后面才是需求分析、数据库设计、核心功能截图。答辩老师通常不会通读全文而是先照文档跑一遍再快速翻几个关键章节。我自己踩过最亏的一次是代码完全没问题但文档里的数据库密码写成自己电脑上的老师照着跑一直报连接失败。从那以后数据库连接配置单独放一个db.properties文档里只写占位符并加一行注释提醒修改这个习惯后来一直保留。希望帮到你。本文还有配套的精品资源点击获取
返回列表