ARTICLE DETAIL

资讯详情

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

基于JavaWeb的美食网站源码解析:从Servlet到Spring Boot改造

基于JavaWeb的美食网站源码解析:从Servlet到Spring Boot改造 简介基于JavaWeb的美食网站设计与实现项目源码面向Java Web入门及进阶开发者适合用作课程设计、毕业设计或自学练手。项目围绕美食信息浏览、菜谱搜索、烹饪心得分享等场景完整呈现利用Servlet、JSP、JDBC等技术构建动态Web应用的过程涵盖用户注册登录、菜谱分类展示、搜索功能、评论系统与用户互动等核心模块。压缩包为zip格式大小50.34MB内部源码目录结构清晰如WebRoot、Servlets、JSP、DAO、Models、Utils等便于对照学习MVC分层思想、数据库连接与CRUD操作、页面跳转及前后端交互逻辑。源码经实际测试验证可运行亲测有效可帮助学习者快速搭建环境、理解JavaWeb开发全流程并在此基础上扩展功能或完成二次开发。该资源目前已有1558人学习适合需要实战参考或项目复现的开发者下载使用。1. 基于 JavaWeb 的美食网站一份能直接跑起来的设计与实现一套基于 JavaWeb 的美食网站.zip 解压之后你得到的不只是几个 JSP 页面而是一个把“菜品展示—分类检索—购物车—下单结算”整条链路串起来的完整 javaweb 项目案例。这个定位对正在赶课设、毕设或期末实训的人很关键很多同学的卡点不在会不会写 Servlet而在于从来没有把 Servlet、JSP、MySQL、Tomcat 这几样东西在 IDEA 里完整拼通过。黑马那种 JavaWeb 笔记刷完了一大半真动手时还是会在映射配置和数据库连接上卡壳。这套项目源码补齐的正是这一公里。它适合三类人想把 JavaWeb 请求流转真正看懂的初学者、需要一份能演示的完整案例拿去改的课设选手、以及想搞清一个 Web 项目内部到底怎么分层的从业者。2. 项目骨架与请求流转MVC 分层和路由到底怎么走2.1 技术栈选型为什么还是 Servlet JSP JDBC这份美食网站走的是 JavaWeb 最经典的一条路Servlet JSP JSTL/EL JDBC MySQL部署容器是 Tomcat。现在新项目都在谈 Spring Boot但课设、实训和很多学校期末项目仍然要求用这套原始组合原因很实际Servlet JSP 能完整暴露 HTTP 请求从进入到响应离开的全过程代码里每一行都能对应到协议层面的动作。Spring Boot 会把请求映射、参数封装、视图渲染这些事包得很严实对初学者来说是个黑匣子写完了也讲不清楚。这套技术栈的代码量也很适合一个人短时间啃下来。Servlet 充当控制器接收请求参数、调用业务逻辑、最后转发页面JSP 负责视图渲染配合 EL 表达式和 JSTL 标签可以让页面里基本不出现 Java 代码JDBC 通过连接池拿连接、执行 SQL、把结果集封装成实体。三块东西各管一段边界清楚出了问题也容易定位到具体某一个类。项目里常见的组织方式就是 controller、service、dao、entity 四层包结构如果你的压缩包源码里是别的包名只要认准“谁在收参数、谁在写 SQL、谁在渲染页面”这三件事就能快速对号入座。2.2 解压后先看包结构别急着导入先建立代码地图拿到 zip 包后不要立刻丢给 IDEA 执行先解压看一眼目录。一个规范的 JavaWeb 项目通常长这样下面的结构是这套美食网站最常见的组织方式具体文件命名以压缩包里为准food-web/ ├── src/ │ ├── main/java/com/food/ │ │ ├── controller/ // Servlet 控制器接收前端参数 │ │ │ ├── UserServlet.java │ │ │ ├── DishServlet.java │ │ │ └── CartServlet.java │ │ ├── service/ // 业务逻辑层事务控制基本在这一层 │ │ │ ├── DishService.java │ │ │ └── OrderService.java │ │ ├── dao/ // 数据访问层只关心 SQL 和结果集 │ │ │ ├── DishDao.java │ │ │ └── UserDao.java │ │ ├── entity/ // 实体类与数据库表字段一一对应 │ │ │ ├── Dish.java │ │ │ └── User.java │ │ ├── util/ // 连接池、字符串处理等工具 │ │ └── filter/ // 编码过滤器、登录过滤器 │ ├── main/resources/ │ │ ├── jdbc.properties // 数据库连接配置 │ │ └── c3p0-config.xml // 连接池配置 │ └── main/webapp/ │ ├── static/ // css、js、图片 │ ├── WEB-INF/ // web.xml 和依赖 jar 包 │ ├── index.jsp // 首页入口 │ ├── dish_list.jsp // 菜品列表 │ ├── dish_detail.jsp // 菜品详情 │ ├── cart.jsp // 购物车页面 │ └── login.jsp // 登录页 └── sql/ └── food_web.sql // 数据库初始化脚本这个结构里最值得先看的是 controller 目录因为 Web 项目的入口全在 Servlet 上。无论是 web.xml 还是 WebServlet 注解都会把 URL 地址映射到某个 Java 类打开这些文件就等于打开了一张站点路由表。理解了路由表后面所有业务功能都能顺着 URL 找到对应代码。2.3 一条请求的完整旅程从“点川菜”到菜品列表我习惯用一个真实场景把这条链路串起来讲用户打开首页看到分类导航栏点了一下“川菜”。从前端到数据库这条请求会经过四个环节。!-- 前端触发分类链接中携带 action 和 category 两个参数 -- a href${pageContext.request.contextPath}/dish?actionlistcategory川菜 川菜 /a// DishServlet 接收请求 WebServlet(/dish) public class DishServlet extends HttpServlet { private DishService dishService new DishService(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String action req.getParameter(action); if (list.equals(action)) { // 从请求中取出分类参数交给 service 层查询 String category req.getParameter(category); ListDish dishes dishService.queryByCategory(category); // 把查询结果放进 request 域转发给 JSP 渲染 req.setAttribute(dishes, dishes); req.getRequestDispatcher(/dish_list.jsp).forward(req, resp); } } }这里的请求参数设计沿用了 JavaWeb 项目常见的“一个 Servlet 对应多种操作”的做法action 字段决定调用哪个业务方法category 是查询条件。setCharacterEncoding 放在 Servlet 入口是为了保证参数在解析前已经是 UTF-8 编码这一步少写了中文关键词基本必乱。forward 是服务端跳转浏览器地址栏不会变化request 域中的数据可以直接带到 JSP 页面。!-- dish_list.jsp 中使用 EL 和 JSTL 遍历循环渲染列表 -- c:forEach items${dishes} vardish div classdish-card h3${dish.name}/h3 p价格${dish.price} 元/p p月售${dish.sales}/p /div /c:forEachJSP 页面只负责取数据、循环、输出不写 Java 代码。items${dishes} 从 request 域中取出 DishServlet 放入的列表vardish 定义循环变量${dish.name} 调用的是 Dish 实体类中名为 getName() 的 getter 方法。这种写法让页面保持干净也方便以后把视图层替换成 FreeMarker 或其他模板引擎service 和 dao 层可以原封不动搬走。3. 核心业务模块落地菜品搜索、分页与购物车状态管理3.1 数据库设计从 SQL 脚本看业务边界压缩包里的 sql/food_web.sql 是整个项目的数据基础。导入之前建议先打开看一眼建表语句你会发现这套业务核心就是围绕“用户、菜品、订单”三张主轴展开。常见的表设计大致如下表名主要职责常见字段user系统用户id, username, password, phonecategory菜品分类id, name, sort_orderdish菜品信息id, name, price, image, sales, description, cidcart_item购物车明细id, uid, dish_id, quantityorder订单主表id, order_no, uid, total_price, status, create_timeorder_detail订单明细id, order_id, dish_id, price, quantitycomment菜品评论id, uid, dish_id, content, create_time每张表的命名和留言、分类、订单状态这类附加表不一定完全一致但看完这个清单你就能理解项目里每个 Servlet 都在操作什么数据。DishServlet 主要围绕 dish 表和 category 表工作CartServlet 操作的是 Session 里的购物车对象OrderServlet 下单时会同时写 order 表和 order_detail 表事务就发生在这一步。导入脚本的命令很简单mysql -uroot -p --default-character-setutf8 source D:/workspace/food-web/sql/food_web.sql;--default-character-setutf8 这个参数经常被漏掉。如果你的 MySQL 客户端默认字符集不是 UTF-8建表时中文注释和表数据会出现乱码后面页面怎么调都白费。source 后面接的是 sql 文件的绝对路径Windows 下注意路径分隔符要用正斜杠或者双反斜杠否则 MySQL 会报找不到文件的错。3.2 菜品分页与搜索LIMIT 计算和 SQL 注入美食网站菜品一多就必须分页。项目里的分页查询通常集中在 DishDao 中最常见的实现是基于 MySQL 的 LIMIT 子句。先看核心查询方法public ListDish findDishesWithCondition(String keyword, int pageNum, int pageSize) { // 拼接动态条件时优先使用 StringBuilder 占位符 StringBuilder sql new StringBuilder(SELECT * FROM dish WHERE 11 ); ListObject params new ArrayList(); if (keyword ! null !keyword.trim().isEmpty()) { // 模糊搜索前后都拼接百分号 sql.append(AND name LIKE ? ); params.add(% keyword.trim() %); } // 按销量倒序取当前页的数据 sql.append(ORDER BY sales DESC LIMIT ?, ?); // pageNum 从 1 开始数据库 offset 从 0 开始 params.add((pageNum - 1) * pageSize); params.add(pageSize); // 使用 PreparedStatement 执行参数不拼接进 SQL 字符串 // 这是防止 SQL 注入的第一道防线 return jdbcTemplate.query(sql.toString(), params.toArray(), new DishRowMapper()); }这段代码里有三个关键点值得细说。第一LIKE 关键字的 % 是拼在参数值里而不是拼在 SQL 语句里这样写 PreparedStatement 才能正确识别通配符。第二LIMIT 的 offset 计算是分页最容易出错的地方页面传过来的 pageNum 如果是 1数据库的偏移量应该是 0所以必须做 (pageNum - 1) * pageSize 的换算不然第一页会少一条数据或者直接查空。第三所有用户输入都通过 ? 占位符传参即便搜索框里输入了 SQL 片段也只会被当作普通字符串处理。配套的还有一个查询总数的方法通常长这样SELECT COUNT(*) FROM dish WHERE name LIKE ?总记录数除以 pageSize 向上取整得到总页数这个值要放进 request 域传给 JSP 生成分页按钮。我见过不少项目把 count 查询和列表查询混在一个方法里做结果每次翻页都要重复查询两次数据页面性能在数据量小时看不出问题到几百条菜品时就能明显感到卡顿。养成两个方法拆开的习惯后面接 Spring Boot 也好迁移。3.3 购物车与 Session会话状态怎么存购物车是 JavaWeb 项目里最典型的 Session 应用场景。用户打开浏览器选了几道菜放到购物车再点结算这期间服务端需要记住“这个用户买了什么”。整套美食网站的购物车实现通常在 CartServlet 中核心代码是protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session req.getSession(); // 从 Session 中取出购物车对象没有则新建一个 Cart cart (Cart) session.getAttribute(cart); if (cart null) { cart new Cart(); session.setAttribute(cart, cart); } // 接收菜品 id 和数量参数 String dishId req.getParameter(dishId); String quantity req.getParameter(quantity); // 项目里常见的写法是购物车内部维护一个 MapDish, Integer cart.addDish(Integer.parseInt(dishId), Integer.parseInt(quantity)); // 跳回菜品列表页 resp.sendRedirect(req.getContextPath() /dish?actionlist); }购物车放在 Session 而不是 Cookie 里主要是两个原因。一是 Cookie 有 4KB 的大小限制购物车装不了几道菜就超了二是 Cookie 每次请求都会原样发到服务端购物车数据放里面既占带宽又容易被篡改。Session 数据保存在服务端内存客户端只持有 sessionId安全性好得多。代价是服务端重启或者浏览器关闭Session 随之消失所以记得提醒使用者改完代码重启 Tomcat 后购物车清空是正常现象。如果你要在这个项目上做二次开发下一步最值得动的地方就是把购物车落库。利用 3.1 里的 cart_item 表用户每次加购时同步写数据库这样刷新不掉数据也能支撑多设备同步。改造思路很简单CartServlet 在写入 Session 的同时调用 CartDao.insert 方法查询购物车时优先读数据库。这一处改动面试时讲出来比单纯背 Session 概念要有说服力得多。4. 在 IDEA Tomcat 里跑通全流程从导入 ZIP 到浏览器验证4.1 导入项目区分 Maven 和带 lib 两种结构拿到 zip 包先看根目录有没有 pom.xml。有 pom.xml 说明是 Maven 项目没有则大概率是传统的 lib 目录管理模式。两种结构在 IDEA 中的导入方式完全不同。如果是 Maven 项目在 IDEA 里点 File → New → Project from Existing Sources选中解压目录选择 MavenIDEA 会自动读取 pom.xml 并下载依赖。这一步需要联网且首次加载会比较慢耐心等右下角进度条跑完。如果是带 lib 的普通 JavaWeb 项目同样选择 Project from Existing Sources但导入完成后需要手动设置依赖File → Project Structure → Modules → Dependencies把 webapp/WEB-INF/lib 目录下的 jar 包全部加入。导入过程中最容易翻车的是 JDK 版本不匹配。美食网站这种项目的 pom.xml 里如果指定了 Java 8而你本机只装了 JDK 17IDEA 会让你重新选择 SDK。这里不建议硬选高版本因为老项目的依赖包未必兼容新编译目标。我一般会在 Project Structure 里创建一个新的 SDK指到本地已装的 JDK 8 目录再重新编译一遍成功率最高。4.2 数据库初始化与连接配置把 zip 里的 sql 脚本导入 MySQL 后下一步就是改连接配置。这套项目的配置文件通常叫 jdbc.properties 或 db.properties位于 src/main/resources 下。打开后你会看到类似下面的内容jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/food_web?useUnicodetruecharacterEncodingUTF-8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456这四行配置每一行都是坑。driver 这一行要注意区分 MySQL 版本MySQL 5.7 及以下一般用 com.mysql.jdbc.DriverMySQL 8.0 以上必须用 com.mysql.cj.jdbc.Driver用错就会报 ClassNotFoundException。url 里的 characterEncodingUTF-8 是中文不乱码的关键useSSLfalse 是为了关掉 MySQL 8 默认开启的 SSL 警告serverTimezoneAsia/Shanghai 则是解决 MySQL 8 对时区敏感导致的连接超时问题。username 和 password 改成你自己数据库的用户名密码不要直接沿用压缩包里的默认值。改完配置后启动项目如果数据库连接还是失败优先检查 MySQL 服务有没有启动以及 food_web 这个数据库名是否存在。命令行里执行 mysql -uroot -p -e show databases; 可以快速确认如果列表里没有 food_web回到第 3.1 节重新导入一遍 SQL 脚本。4.3 配置 Tomcat 并启动验证项目导入、数据库就绪后剩下就是配置 Tomcat 跑起来。IDEA 里运行 JavaWeb 项目的标准步骤是这样Run → Edit Configurations → 左上角 → Tomcat Server → Local。在 Server 标签页选择 Tomcat 安装目录在 Deployment 标签页点 → Artifact选择项目的 exploded war 包Application context 建议改成 /food_web这样访问地址简短且不容易和本机其他项目冲突。配置完成后启动 Tomcat控制台出现类似下面的日志就说明部署成功INFO: Deployment of web application archive [food_web] has finished INFO: Starting ProtocolHandler [http-bio-8080]浏览器访问 http://localhost:8080/food_web/index.jsp按下面五步验证首页图片和中文文字是否正常显示没有乱码点击“川菜”分类URL 变成 /dish?actionlistcategory川菜列表能刷新搜索一个菜品关键词搜索结果能按销量排序点“加入购物车”cart.jsp 能看到数量和总价注册一个新用户后登录session 能记录登录状态这五步走完这套美食网站就算真正跑通了。如果你访问后报 404 或者页面空白先回第 2.2 节重新核对项目结构再回到第 5 章逐条排查问题基本都在那几个常见位置。5. 常见问题与避坑启动、编码、数据库连接的几个拦路虎5.1 一访问就报 404URL pattern 与项目路径对不上现象Tomcat 正常启动控制台也没有报错但浏览器访问 http://localhost:8080/food_web/index.jsp 时出现 404 页面。原因最常见的有三种。一是 Artifact 的名字和 Application context 不一致项目实际部署路径不是你访问的路径二是 web.xml 里 Servlet 的 url-pattern 没配对比如访问 /dish实际映射却是 /DishServlet三是 index.jsp 没有放在 webapp 根目录下被塞进了 WEB-INF 里面导致直接访问不到。解决先去 IDEA 的 Run → Edit Configurations 看 Deployment 标签下的 Application context确认是 /food_web。再打开 web.xml 或各 Servlet 类上的 WebServlet 注解逐个核对 URL。最后确认 JSP 文件在 webapp 根目录或子目录下WEB-INF 下的页面只能通过服务端 forward 访问不能直接在地址栏输入。5.2 中文显示成问号或乱码三个环节缺一不可现象菜品名称、用户昵称在页面上显示为 ??????或者 JSP 页面里的中文正常数据库中查询出来的中文全乱。原因JavaWeb 的编码链路涉及三处请求参数编码、JSP 页面编码、数据库连接编码。常见情况是只设置了 JSP 的 pageEncoding忽略了 Servlet 入口的 setCharacterEncodingPOST 请求的中文在进入业务逻辑前就成了乱码。另一种常见情况是数据库连接 URL 里没加 characterEncodingUTF-8JDBC 写入时用的是服务端默认字符集。解决三个地方全部统一成 UTF-8。JSP 文件第一行声明 pageEncodingUTF-8过滤器中强制 req.setCharacterEncoding(UTF-8)jdbc.properties 的 URL 上带 useUnicodetruecharacterEncodingUTF-8。如果数据库里已经存了乱码需要先清理数据再用 UTF-8 重新导入 SQL 脚本。5.3 MySQL 8 连接失败驱动版本和时区现象启动项目后报 Communications link failure 或 Access denied for user但用户名密码明明是对的。原因高版本 MySQL 对连接要求更严格。MySQL 8 开始驱动类从 com.mysql.jdbc.Driver 改成了 com.mysql.cj.jdbc.Driver而且这个驱动要求连接 URL 里显式指定 serverTimezone否则连接会超时。很多项目包用的是五六年之前的代码配置里还是老驱动和老 URL。解决检查三个点默认驱动是否已升级到 mysql-connector-java 8.x配置改成 com.mysql.cj.jdbc.DriverURL 末尾补上 serverTimezoneAsia/Shanghai如果 MySQL 8 默认的 caching_sha2_password 插件导致认证失败可以在 MySQL 里把用户改成 mysql_native_password 加密方式再试。5.4 ClassNotFoundExceptionlib 没有被 IDEA 打包进 Artifacts现象代码编译没问题控制台也没有语法报错但启动后访问任意一个 Servlet 就抛 ClassNotFoundException提示找不到某个 jar 包里的类。原因普通 JavaWeb 项目手动导入 lib 目录后IDEA 的编译器编译时能看到这些 jar但打包成 Artifact 时默认不会自动带上外部依赖。运行过程中容器找不到这些类就会在第一次使用时报 ClassNotFoundException。解决File → Project Structure → Artifacts在选中的 Web Artifact 下方的 Available Elements 里把 WEB-INF/lib 下的依赖全部右键 Put into /WEB-INF/lib然后重新 Build Artifact。做完这一步再看输出的 war 包解压确认 lib 目录里有 jar 文件再启动。5.5 改了 JSP 却没反应Tomcat 部署缓存和浏览器缓存现象修改了 dish_list.jsp 的页面样式或结构刷新页面还是老样子有时重启 Tomcat 也解决不了。原因两个层面。一是 IDEA 里 Tomcat 部署方式如果选的不是 exploded每次改动都需要重新打包重部署二是浏览器端 JSP 响应没有带 no-cache 头本地缓存了旧页面。解决开发阶段尽量选 exploded artifact 部署改完 JSP 后 IDEA 会自动同步到 Tomcat 目录。浏览器按 CtrlF5 强制刷新跳过缓存。如果重启后还是旧页面去 Tomcat 的 work/Catalina 目录删掉对应项目的缓存文件夹再用 Clean 清一次 IDEA 的 target 目录这招基本能解决九成以上“改了没生效”的问题。6. 把 Servlet 项目改造成 Spring Boot 风格从实训代码到简历项目这个压缩包能跑通验收只是第一步如果你想让它在简历上发挥更大价值我很推荐沿着“换壳不改芯”的思路改造成 Spring Boot。改造的核心不是重写业务而是保留 service 和 dao 层只把 controller 层从 Servlet 换成 Spring MVC 的注解风格。因为项目当初做了合理分层service 层写业务逻辑时并不依赖 HttpServletRequest 这些 Servlet API所以迁移成本极低。原 Servlet 写法是WebServlet(/dish) public class DishServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String category req.getParameter(category); // ... } }改成 Spring Boot 的 Controller 是RestController RequestMapping(/api/dish) public class DishController { private final DishService dishService; public DishController(DishService dishService) { this.dishService dishService; } GetMapping(/list) public Result list(RequestParam(defaultValue 1) int page, RequestParam(defaultValue 8) int size) { return Result.success(dishService.pageList(page, size)); } }对比下来你会发现参数从 req.getParameter 变成了 RequestParam 注解返回值从转发 JSP 变成了 JSON 对象但 service 层的 pageList 方法几乎原封不动。这就是我强调要保住 service 层独立性的原因。改成 Spring Boot 后原来那些 JSP 可以退居二线前端用 Vue 或原生 HTML 调用 /api/dish/list 接口美食网站的菜品数据完全不需要重复编写。改造完的验证方法很简单浏览器直接访问curl http://localhost:8080/api/dish/list?page1size8能返回菜品列表的 JSON说明业务层完整迁移成功。从那以后我每次拿到一个新的 JavaWeb 项目包都会按固定三步走先跑通首页再改一处业务逻辑验证代码是不是真的看懂了最后检查有没有硬编码的 SQL 和数据库密码确认没有才往简历上放。这套流程替我省了不少返工的时间也希望能帮到你少踩几个坑。本文还有配套的精品资源点击获取
返回列表