☰
JSP+Servlet蛋糕店网站实战:Java Web入门核心训练
2026/10/1 4:49:33 网站建设 项目流程

简介:本资源是一套完整可用的JavaWeb课程设计级蛋糕店售卖网站项目,面向计算机相关专业在校学生、初学者及教师,解决Web开发实践入门与毕设选题落地问题。项目基于JSP+Servlet技术栈构建,涵盖用户注册登录、商品分类展示、推荐模块(轮播图/热销/新品)、购物车管理、订单结算等核心电商功能,代码经实际运行验证,答辩平均分96分,适合作为课程设计、毕业设计或自学进阶范例。压缩包含290个文件,主体为58个JSP页面、55个Java业务类、20个XML配置文件、14个JS脚本及12个CSS样式文件,辅以图片资源与SQL数据库脚本,整体18.95MB;预览可见Bootstrap与Layer UI框架集成,体现前端工程化基础实践。已有182人学习下载,提供README说明文档、结构清晰的MVC分层目录及可直接部署的完整源码,便于快速理解JavaWeb开发全流程。

1. 这不是“过时技术”的怀旧练习:一个能真上手、真调试、真部署的 JSP+Servlet 蛋糕店网站,为什么至今仍是 Java Web 入门最扎实的落脚点?

你可能在招聘要求里看到“熟悉 Spring Boot”,在课程表上被推着学“微服务架构”,但当你第一次需要独立搭一个带登录、商品管理、购物车、订单提交的完整 Web 应用——没有现成脚手架、不依赖云平台、不调第三方 API,只有一台装了 JDK 和 Tomcat 的笔记本,这时候,JSP + Servlet 不是古董,而是你唯一能立刻画出全链路、逐行断点、改完即生效的“可控黑匣子”。这个《基于 JSP 和 Servlet 的蛋糕店售卖网站》大作业,表面是课程任务,实则是把 HTTP 请求怎么来、Session 怎么存、数据库怎么连、页面怎么渲染、跳转逻辑怎么写这五根骨头一根根拆开、再亲手拼回去的硬核训练。它不追求高并发或分布式,但逼你直面request.getParameter()拿不到值、response.sendRedirect()丢失中文、MySQL 时间字段和 Java Date 类型对不上、Bootstrap 表单校验和 Servlet 后端校验脱节这些真实翻车现场。适合刚学完 Java 基础、正卡在“写了代码却不知道请求到底走到了哪一步”的同学;也适合想补全传统 Web 开发底层逻辑的 Spring Boot 开发者——毕竟,Spring MVC 的@Controller本质就是 Servlet 的封装,ModelAndView就是 JSP 的request.setAttribute()加RequestDispatcher.forward()。别急着跳进框架,先把地基的每一块砖的尺寸、承重、砌法摸透。


2. 从零启动:用 IntelliJ IDEA 搭建可运行的 JSP+Servlet 工程,避开环境配置的“第一道深坑”

2.1 创建标准 Dynamic Web Project:拒绝“新建 Maven 项目后手动加 web.xml”的玄学操作

很多新手在 IDEA 里点“New Project” → “Maven”,然后一路 Next,结果发现创建出来的项目缺webapp目录、没有web.xml、JSP 文件标红报错。这不是你配置错了,是 IDEA 默认的 Maven 模板压根没按 Java EE 规范生成 Web 结构。正确做法是:

File → New → Project → 选择左侧 "Java Enterprise" → 勾选 "Web Application" → 取消勾选 "Create project from template" → Next

提示:务必取消“Create project from template”,否则会强制引入不必要的框架(如 Struts 或 JSF),反而污染纯净的 Servlet 环境。IDEA 会自动生成标准目录结构:src/main/java(放 Servlet)、src/main/webapp(放 JSP/HTML/CSS/JS)、src/main/webapp/WEB-INF/web.xml(核心配置文件)。

项目创建后,右键项目名 →Add Framework Support...→ 勾选Web→ 点击OK。此时 IDEA 会自动识别webapp为 Web Root,并将WEB-INF/web.xml标记为 Deployment Descriptor。

2.2 配置 Tomcat Server:让 JSP 编译、Servlet 映射、静态资源访问全部归位

光有项目结构还不够,必须让 Tomcat 知道“这个项目该跑在哪、怎么跑”。关键三步:

  1. 下载并指定 Tomcat Home Directory
    去 Apache Tomcat 官网 下载Tomcat 9.x(推荐 9.0.83)或 10.x(注意 Servlet API 版本兼容性)。解压后,在 IDEA 中:
    Run → Edit Configurations... → + → Tomcat Server → Local
    在Application server栏点击Configure...→Tomcat Home选择你解压的目录(如D:\apache-tomcat-9.0.83)。

  2. Deployment 设置:确保 WAR exploded 正确指向
    在同一配置页,切换到Deployment标签 →+ → Artifact → your-project-name:war exploded→Application context改为/cake(这样访问地址就是http://localhost:8080/cake,避免根路径冲突)。

    参数说明:war exploded是“解压式部署”,修改 JSP 或 class 文件后无需重新打包,刷新浏览器即可生效,是开发阶段的刚需;Application context决定应用上下文路径,设为/cake比/更安全,避免与 Tomcat 自带的 manager 应用冲突。

  3. Server 设置:解决中文乱码与热更新失效
    切换到Server标签 →VM options添加:

    -Dfile.encoding=UTF-8 -Duser.language=zh -Duser.country=CN

    On update action选Update classes and resources(类和资源变更自动热更新);On frame deactivation选Update classes and resources(切出 IDEA 窗口时也同步)。

    逻辑说明:-Dfile.encoding=UTF-8强制 JVM 使用 UTF-8 解析源码和配置文件,解决 JSP 中文注释乱码、request.getParameter()获取中文参数为空的问题;Update classes and resources是 IDEA 对 Tomcat 的热加载支持,比手动重启快 10 倍以上。

2.3 验证基础通路:写一个最简 Servlet + JSP,确认请求能完整走完

在src/main/java下新建包com.cake.servlet,创建HelloServlet.java:

package com.cake.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/hello") public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setAttribute("msg", "欢迎来到蛋糕店!"); // 向 request 域存数据 request.getRequestDispatcher("/index.jsp").forward(request, response); // 转发到 JSP } }

在src/main/webapp下创建index.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>蛋糕店首页</title> </head> <body> <h1>${msg}</h1> <!-- EL 表达式取值 --> </body> </html>

运行 Tomcat,访问http://localhost:8080/cake/hello。如果页面显示“欢迎来到蛋糕店!”,说明:
✅ Servlet 被正确映射并执行
✅request.setAttribute()数据成功传递到 JSP
✅ JSP 的 EL 表达式解析正常
✅ Tomcat 的 JSP 编译器(Jasper)工作正常

参数说明:@WebServlet("/hello")是 Servlet 3.0+ 的注解式映射,比web.xml配置更简洁;request.getRequestDispatcher().forward()是服务器端转发,URL 不变,且能共享request域对象;${msg}是 EL 表达式,依赖 JSP 容器的 EL 解析器,若报错需检查web.xml的web-app版本是否 ≥ 2.4(IDEA 创建时默认满足)。


3. 核心功能落地:用原生 Servlet + JSP 实现蛋糕店四大模块,不绕弯、不跳步

3.1 用户登录与 Session 管理:用HttpSession实现状态保持,拒绝 Cookie 伪造漏洞

蛋糕店网站必须区分顾客和管理员。登录不是简单比对密码,关键是会话状态的可信绑定。常见错误是把用户名存在 Cookie 里,前端随意修改就能“登录任意账号”。正确做法是:用户登录成功后,将用户 ID 和角色存入HttpSession,后续所有敏感操作(如下单、管理商品)都校验 Session 是否存在且有效。

LoginServlet.java关键逻辑:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); // 1. 查询数据库(此处简化为硬编码验证,实际应调用 DAO) if ("admin".equals(username) && "123456".equals(password)) { // 2. 创建 Session 并存入用户信息 HttpSession session = request.getSession(true); // true 表示不存在则创建 session.setAttribute("userId", 1); session.setAttribute("username", "admin"); session.setAttribute("role", "admin"); session.setMaxInactiveInterval(30 * 60); // Session 30 分钟无操作失效 response.sendRedirect(request.getContextPath() + "/admin/index.jsp"); return; } request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("/login.jsp").forward(request, response); }

admin/index.jsp顶部添加权限校验:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <% // 传统 scriptlet 方式校验(兼容性最强) HttpSession session = request.getSession(false); if (session == null || !"admin".equals(session.getAttribute("role"))) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } %> <!DOCTYPE html> <html> <head><title>管理员后台</title></head> <body> <h2>欢迎,${sessionScope.username}!</h2> <!-- 管理员专属内容 --> </body> </html>

逻辑说明:request.getSession(true)确保 Session 存在;setMaxInactiveInterval()设定超时时间,防止长期空闲 Session 占用内存;sessionScope.username是 JSTL 的隐式对象,比session.getAttribute("username")更简洁安全;return;在response.sendRedirect()后立即终止 JSP 执行,避免后续 HTML 渲染造成响应已提交异常。

3.2 商品展示与分页:用ArrayList+PageBean手写分页,理解数据与视图的边界

Bootstrap 的表格组件需要 JSON 或 HTML 表格数据,而 JSP 天然适合直接嵌套循环。我们不引入 AJAX,用最朴素的“页码链接”实现分页,重点在于:数据层(Servlet)计算好当前页数据,视图层(JSP)只负责展示,不参与逻辑判断。

ProductListServlet.java:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int currentPage = 1; String pageStr = request.getParameter("page"); if (pageStr != null && !pageStr.trim().isEmpty()) { currentPage = Integer.parseInt(pageStr); } int pageSize = 8; // 每页显示 8 条 List<Product> allProducts = productService.findAll(); // 从 DAO 获取全部商品 int total = allProducts.size(); int fromIndex = (currentPage - 1) * pageSize; int toIndex = Math.min(fromIndex + pageSize, total); List<Product> pageList = allProducts.subList(fromIndex, toIndex); // 封装分页信息 PageBean pageBean = new PageBean(); pageBean.setPage(currentPage); pageBean.setPageSize(pageSize); pageBean.setTotal(total); pageBean.setList(pageList); pageBean.setTotalPage((int) Math.ceil((double) total / pageSize)); request.setAttribute("pageBean", pageBean); request.getRequestDispatcher("/product/list.jsp").forward(request, response); }

list.jsp中用 JSTL 循环展示:

<c:forEach items="${pageBean.list}" var="product"> <tr> <td>${product.id}</td> <td>${product.name}</td> <td>¥${product.price}</td> <td><img src="${pageContext.request.contextPath}/images/${product.image}" width="80"></td> <td> <a href="${pageContext.request.contextPath}/product/detail?id=${product.id}" class="btn btn-info btn-sm">详情</a> </td> </tr> </c:forEach> <!-- 分页导航 --> <ul class="pagination"> <c:if test="${pageBean.page > 1}"> <li class="page-item"><a class="page-link" href="?page=1">首页</a></li> <li class="page-item"><a class="page-link" href="?page=${pageBean.page - 1}">上一页</a></li> </c:if> <c:forEach begin="1" end="${pageBean.totalPage}" var="i"> <c:choose> <c:when test="${i == pageBean.page}"> <li class="page-item active"><a class="page-link">${i}</a></li> </c:when> <c:otherwise> <li class="page-item"><a class="page-link" href="?page=${i}">${i}</a></li> </c:otherwise> </c:choose> </c:forEach> <c:if test="${pageBean.page < pageBean.totalPage}"> <li class="page-item"><a class="page-link" href="?page=${pageBean.page + 1}">下一页</a></li> <li class="page-item"><a class="page-link" href="?page=${pageBean.totalPage}">尾页</a></li> </c:if> </ul>

参数说明:subList(fromIndex, toIndex)是 Java 集合的轻量级分页,避免 SQLLIMIT的数据库耦合,适合数据量不大的教学场景;PageBean是自定义 POJO,封装当前页、总页数、数据列表等,是 Servlet 向 JSP 传递分页上下文的标准方式;Bootstrap 的pagination组件通过<ul class="pagination">结构渲染,JSTL 的<c:forEach>动态生成<li>,active类控制当前页高亮。

3.3 购物车实现:用HttpSession存储Map<Integer, Integer>,模拟真实业务中的数量叠加

购物车不是“存个商品 ID 列表”那么简单。用户可能多次添加同一商品,需合并数量;删除时要减数量,数量为 0 时才真正移除。用Map<productId, quantity>是最贴合业务的内存结构。

CartServlet.java添加商品逻辑:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int productId = Integer.parseInt(request.getParameter("productId")); int quantity = Integer.parseInt(request.getParameter("quantity")); HttpSession session = request.getSession(); @SuppressWarnings("unchecked") Map<Integer, Integer> cart = (Map<Integer, Integer>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<>(); session.setAttribute("cart", cart); } // 合并数量:已有则累加,否则设为 quantity int currentQty = cart.getOrDefault(productId, 0); cart.put(productId, currentQty + quantity); response.sendRedirect(request.getContextPath() + "/cart/view.jsp"); }

view.jsp展示并计算总价:

<c:set var="totalPrice" value="0"/> <table class="table table-bordered"> <thead><tr><th>商品</th><th>单价</th><th>数量</th><th>小计</th><th>操作</th></tr></thead> <tbody> <c:forEach items="${sessionScope.cart}" var="entry"> <c:set var="product" value="${productService.findById(entry.key)}"/> <tr> <td>${product.name}</td> <td>¥${product.price}</td> <td>${entry.value}</td> <td>¥${product.price * entry.value}</td> <td> <a href="${pageContext.request.contextPath}/cart/remove?pid=${entry.key}" class="btn btn-danger btn-sm">删除</a> </td> </tr> <c:set var="totalPrice" value="${totalPrice + product.price * entry.value}"/> </c:forEach> </tbody> </table> <h4>总计:¥${totalPrice}</h4>

逻辑说明:session.getAttribute("cart")返回的是Map,需强制转换;getOrDefault()是 Java 8 方法,安全获取默认值 0;<c:set>在 JSP 中声明变量并参与计算,totalPrice在循环中累加,最终输出;删除链接remove?pid=${entry.key}对应RemoveServlet,其逻辑是cart.remove(productId)后重定向回view.jsp。

3.4 订单提交与 MySQL 事务:用Connection.setAutoCommit(false)保证库存扣减与订单创建原子性

下单是典型多表操作:扣减商品库存、插入订单主表、插入订单明细表。任何一步失败,整个操作必须回滚。不能依赖“先查再更新”的乐观锁(教学场景数据量小,但逻辑必须严谨)。

OrderServlet.java关键事务代码:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Connection conn = null; try { conn = JDBCUtil.getConnection(); // 自定义工具类获取连接 conn.setAutoCommit(false); // 关闭自动提交,开启事务 // 1. 扣减库存(UPDATE product SET stock = stock - ? WHERE id = ?) String updateSql = "UPDATE product SET stock = stock - ? WHERE id = ?"; PreparedStatement ps1 = conn.prepareStatement(updateSql); // ... 设置参数,执行 // 2. 插入订单主表(INSERT INTO orders (...) VALUES (...)) String insertOrderSql = "INSERT INTO orders (user_id, total_price, status) VALUES (?, ?, ?)"; PreparedStatement ps2 = conn.prepareStatement(insertOrderSql, Statement.RETURN_GENERATED_KEYS); // ... 设置参数,执行,获取生成的 order_id // 3. 插入订单明细(INSERT INTO order_item (...) VALUES (...)) String insertItemSql = "INSERT INTO order_item (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)"; PreparedStatement ps3 = conn.prepareStatement(insertItemSql); // ... 设置参数,执行 conn.commit(); // 全部成功,提交事务 request.setAttribute("message", "订单提交成功!"); } catch (SQLException e) { if (conn != null) { try { conn.rollback(); // 任一失败,回滚 } catch (SQLException ex) { ex.printStackTrace(); } } request.setAttribute("error", "下单失败:" + e.getMessage()); } finally { JDBCUtil.close(conn); // 工具类统一关闭资源 } request.getRequestDispatcher("/order/success.jsp").forward(request, response); }

参数说明:setAutoCommit(false)是事务起点;Statement.RETURN_GENERATED_KEYS允许ps2获取orders表的自增id,用于后续order_item的外键关联;JDBCUtil是封装了DriverManager.getConnection()和close()的工具类,避免重复代码;finally块确保连接无论成功失败都释放,防止连接泄漏。


4. 前端增强:用 Bootstrap 4 + Layer 弹层提升交互体验,不写一行 jQuery 也能搞定

4.1 Bootstrap 4 表单与栅格:用form-control和row/col快速构建响应式页面

蛋糕店网站不需要炫技动画,但必须清晰易用。Bootstrap 4 的栅格系统(12 列)和表单组件是最佳选择。例如商品添加表单:

<form action="${pageContext.request.contextPath}/admin/product/add" method="post" class="needs-validation" novalidate> <div class="row"> <div class="col-md-6 mb-3"> <label for="name">商品名称</label> <input type="text" class="form-control" id="name" name="name" required> <div class="invalid-feedback">请输入商品名称</div> </div> <div class="col-md-3 mb-3"> <label for="price">价格(元)</label> <input type="number" class="form-control" id="price" name="price" step="0.01" required> </div> <div class="col-md-3 mb-3"> <label for="stock">库存</label> <input type="number" class="form-control" id="stock" name="stock" required> </div> </div> <div class="row"> <div class="col-md-12 mb-3"> <label for="description">描述</label> <textarea class="form-control" id="description" name="description" rows="3"></textarea> </div> </div> <button class="btn btn-primary" type="submit">提交</button> </form>

逻辑说明:class="needs-validation"启用 Bootstrap 4 内置表单验证;novalidate禁用浏览器默认验证,由 Bootstrap 控制;<div class="row">包裹col-md-*实现响应式布局,小屏自动堆叠,中屏并排;step="0.01"确保价格输入支持小数;required属性触发invalid-feedback提示。

4.2 Layer 弹层集成:用layer.msg()和layer.confirm()替代原生 alert,提升专业感

Layer 是国产轻量弹层库,比 Bootstrap Modal 更易用,且与 JSP 无缝集成。下载layer.js放入webapp/js/,在页面底部引入:

<script src="${pageContext.request.contextPath}/js/layer/layer.js"></script> <script> // 登录成功后跳转并提示 <c:if test="${not empty param.success}"> layer.msg('登录成功!', {icon: 1}); setTimeout(function(){ window.location.href = '${pageContext.request.contextPath}/index.jsp'; }, 1500); </c:if> // 删除商品前确认 function confirmDelete(id) { layer.confirm('确定要删除该商品吗?', {icon: 3, title:'警告'}, function(index){ window.location.href = '${pageContext.request.contextPath}/admin/product/delete?id=' + id; layer.close(index); }); } </script>

参数说明:layer.msg('text', {icon: 1})显示绿色成功提示(icon:1);layer.confirm()弹出带“确定/取消”按钮的对话框,icon:3是黄色警告图标;setTimeout延迟跳转,给用户阅读提示的时间;confirmDelete(id)是 JavaScript 函数,绑定在删除按钮的onclick="confirmDelete(${product.id})"上,避免误删。

4.3 图片上传与预览:用commons-fileupload实现多图上传,不依赖前端框架

JSP 原生不支持文件上传,必须借助 Apache Commons FileUpload。添加 Maven 依赖:

<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.5</version> </dependency>

ProductAddServlet.java处理上传:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); upload.setHeaderEncoding("UTF-8"); // 解决中文文件名乱码 try { List<FileItem> items = upload.parseRequest(request); for (FileItem item : items) { if (item.isFormField()) { // 普通表单项 String fieldName = item.getFieldName(); String value = item.getString("UTF-8"); // ... 处理 name, price 等 } else { // 文件项 String fileName = new File(item.getName()).getName(); // 获取原始文件名 if (!fileName.isEmpty()) { String uploadPath = request.getServletContext().getRealPath("/images/"); File uploadDir = new File(uploadPath); if (!uploadDir.exists()) uploadDir.mkdirs(); String finalPath = uploadPath + fileName; item.write(new File(finalPath)); // 保存文件 // 保存相对路径到数据库,如 "images/cheese-cake.jpg" product.setImage("images/" + fileName); } } } productService.add(product); request.setAttribute("message", "商品添加成功!"); } catch (Exception e) { request.setAttribute("error", "上传失败:" + e.getMessage()); } request.getRequestDispatcher("/admin/product/add.jsp").forward(request, response); }

逻辑说明:upload.setHeaderEncoding("UTF-8")解决 IE 浏览器上传中文文件名乱码;new File(item.getName()).getName()提取纯文件名,过滤掉 IE 的完整路径;getServletContext().getRealPath("/images/")获取服务器上webapp/images/的绝对路径,确保文件保存到正确位置;item.write()是 FileItem 的便捷方法,自动处理流关闭。


5. 避坑指南:JSP+Servlet 开发中 5 个高频翻车点,血泪经验总结

5.1 现象:JSP 页面中文显示为乱码(如“???”),或request.getParameter()获取中文参数为空

原因:Tomcat 默认使用 ISO-8859-1 解码 URL 参数,而浏览器发送的是 UTF-8 编码;同时 JSP 文件本身未声明编码。
解决:
① 在web.xml中全局设置请求编码(Tomcat 8.0+ 推荐):

<filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

② 若不用 Spring,可在每个 Servlet 的doPost()开头手动设置:

request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8");

③ JSP 文件顶部必须声明:<%@ page contentType="text/html;charset=UTF-8" %>。

5.2 现象:response.sendRedirect()后页面空白,或报IllegalStateException: Cannot forward after response has been committed

原因:sendRedirect()是客户端重定向,会发送 302 响应头;若之前已向响应体写入内容(如out.print()或 JSP 中的 HTML),再调用sendRedirect()就会因响应已提交而失败。
解决:
①sendRedirect()必须在response未写入任何内容前调用;
② 重定向后立即return;,阻止后续代码执行;
③ 避免在 JSP 中混用response.sendRedirect()和 HTML 输出,重定向逻辑全部放在 Servlet 中。

5.3 现象:MySQL 中文存入后变成???,或查询结果中文乱码

原因:MySQL 服务端、数据库、表、连接四层编码不一致,常见于安装时未设 UTF-8。
解决:
① 检查 MySQL 配置文件my.ini(Windows)或my.cnf(Linux),确保[client]和[mysqld]下都有:

[client] default-character-set=utf8mb4 [mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci

② 创建数据库时指定字符集:

CREATE DATABASE cake_shop CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

③ JDBC URL 添加参数:

jdbc:mysql://localhost:3306/cake_shop?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=GMT%2B8

④serverTimezone=GMT%2B8解决 MySQL 8.0+ 的时区问题,避免java.sql.SQLException: The server time zone value '...' is unrecognized。

5.4 现象:Bootstrap 样式不生效,按钮无圆角、表格无边框

原因:未正确引入 Bootstrap CSS 和 JS,或引入顺序错误(CSS 必须在 JS 前),或 CDN 地址失效。
解决:
① 使用国内 CDN(如 BootCDN)确保稳定:

<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css"> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.min.js"></script>

② 检查浏览器开发者工具 Network 标签,确认bootstrap.min.css返回 200;
③ Bootstrap 4 依赖 jQuery,必须在bootstrap.min.js前引入,且版本匹配(Bootstrap 4.6 需 jQuery 3.0+)。

5.5 现象:Layer 弹层不显示,控制台报layer is not defined

原因:layer.js未加载成功,或加载时机早于 jQuery(Layer 依赖 jQuery),或路径错误。
解决:
① 确保layer.js在jquery.min.js之后引入;
② 使用绝对路径引入,避免相对路径错误:

<script src="${pageContext.request.contextPath}/js/layer/layer.js"></script>

③ 检查layer.js文件是否真的存在于webapp/js/layer/目录下,文件名大小写是否匹配(Linux 服务器严格区分);
④ 初始化前加判断:

if (typeof layer === 'undefined') { console.error('Layer.js 未加载'); } else { layer.msg('Hello'); }

6. 进阶技巧:用 Maven 打包 WAR 并部署到独立 Tomcat,完成从开发到交付的最后一公里

6.1 将传统 Dynamic Web Project 迁移为 Maven 工程:结构标准化,依赖可追溯

IDEA 创建的 Dynamic Web Project 缺少pom.xml,无法用 Maven 管理依赖和构建。迁移步骤:

  1. 右键项目 → Add Framework Support... → Maven,IDEA 会自动生成pom.xml;
  2. 手动修正pom.xml,补充 Servlet、JSP、MySQL 驱动等依赖:
<dependencies> <!-- Servlet API --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> <!-- provided 表示由 Tomcat 提供,不打入 WAR --> </dependency> <!-- JSP API --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <!-- JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- MySQL Driver --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <!-- Apache Commons FileUpload --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.5</version> </dependency> </dependencies> <build> <plugins> <!-- Maven Compiler Plugin --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> <!-- Maven War Plugin --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.4.0</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> <!-- Servlet 3.0+ 可无 web.xml --> </configuration> </plugin> </plugins> </build>

参数说明:<scope>provided</scope>告诉 Maven 这些依赖由运行容器(Tomcat)提供,编译时可用,但不会打包进 WAR,避免版本冲突;<failOnMissingWebXml>false</failOnMissingWebXml>允许无web.xml的注解式配置;<source>和<target>设为 8,匹配 JDK 8 编译要求(Tomcat 9 兼容)。

6.2 构建可部署 WAR 包:用mvn clean package生成标准归档,验证结构完整性

在 IDEA Terminal 中执行:

mvn clean package

成功后,target/cake-shop-1.0-SNAPSHOT.war生成。解压该 WAR 文件(用 7-Zip 或jar -xvf),检查关键结构:

路径说明必须存在
WEB-INF/classes/编译后的.class文件,含 Servlet 和工具类✅
WEB-INF/lib/pom.xml中scope非provided的依赖(如mysql-connector-java.jar)✅
WEB-INF/web.xml若使用注解,此文件可为空或不存在⚠️(可选)
index.jsp,login.jsp等JSP 文件位于 WAR

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

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

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

立即咨询