简介这是一份基于JavaWeb开发的蛋糕店网站系统完整项目面向正在做课程设计、毕业设计或初学JavaWeb的同学与开发者。项目分为前台和后台前台涵盖推荐商品、分类展示、商品详情、购物车、用户注册登录、个人信息修改、订单查询、关键字搜索等功能后台则为管理员提供商品与订单相关的运营管理能力。项目共586个文件压缩包约17.4MB包含JSP页面、Java源码及编译后的class文件、SQL数据库脚本、JS/CSS前端资源、jpg/png/gif图片素材和依赖jar包等目录结构清晰便于对照学习与部署。当前已有693人浏览学习。除了完整源码包内还附带界面演示截图、文档说明和详细代码注释SQL文件可直接导入数据库能帮助读者快速跑通项目并理解前后台交互逻辑代码已通过运行测试适合在课设、毕设场景中继续扩展功能。1. 一个能现场演示的JavaWeb蛋糕店网站系统课程设计才叫完整先纠一个常见误解课程设计的评分标准从来不是“代码堆得多”而是“能不能跑、逻辑通不通、文档全不全”。很多同学下载源码包后发现缺了数据库SQL或注释项目根本启动不了只能临时改页面应付答辩。基于JavaWeb的蛋糕店网站系统之所以适合做课程设计是因为业务场景足够生活化——用户浏览蛋糕、加购物车、下单管理员发货整个链路用JSP、Servlet、MySQL就能全部讲清。它既要处理会话和权限又涉及数据库事务和查询优化正好覆盖评委会问到的技术点。下面从选型、数据库SQL设计、核心代码、部署演示四层把该准备的都落到具体命令和参数上。2. 课程设计场景下JavaWeb蛋糕店系统的选型与数据库SQL设计2.1 为什么选JSPServletJDBC而不是Spring BootJavaWeb是一个宽泛的范畴很多人默认Spring Boot也算但课程设计里“基于javaweb”的要求更常见的是考察你对Servlet容器、请求响应链路、HttpSession、Filter、JDBC这些经典组件的掌握程度。JSPServletJDBC能把一次请求从浏览器到Servlet、再到DAO、数据库最后回到JSP渲染的过程完整暴露在代码里评审问“Filter什么时候执行”“PreparedStatement为什么能防SQL注入”时你可以直接指到对应代码解释。Spring Boot把这些都封装了启动快是真快但答辩时容易陷入“配置对了就能跑”的尴尬。蛋糕店系统规模不大用JSP直出页面比前后端分离更直观。不用Vue、不用REST接口表单提交后request.setAttribute配合JSP的EL表达式就能把商品列表渲染出来。这种传统方式部署也简单打一个war包丢进Tomcat即可不会出现静态资源和接口跨域的额外争论。JDBC连接直接用DriverManager写在一个DBUtil里对课程设计完全够用还能顺带解释连接池的意义。2.2 功能边界访客、会员、管理员三种角色系统不能只做一个商品陈列页。课程设计要有闭环就必须定义三种角色和各自的页面操作否则后台管理无从谈起。访客能看到首页、分类、商品详情但点击“加入购物车”会被拦截到登录页会员登录后可以加购物车、提交订单、查看自己的订单列表管理员则进入独立后台维护分类和商品处理订单状态。角色可操作内容典型页面涉及数据表访客浏览分类和商品查看详情首页、列表页、详情页category, cake会员登录后加购物车、下单、查看订单购物车、订单确认、我的订单cartSession实现、orders, order_item管理员商品上下架、库存修改、订单发货后台商品列表、订单管理cake, orders不要一开始就做大而全的会员积分、优惠券、评价系统这些容易把课程设计拖垮。把边界收敛到“下单和发货”这个核心数据表控制在5张以内既能讲清表关系又不至于让SQL脚本复杂到自己也解释不清。2.3 数据库SQL脚本从建表到索引一次性给出数据库SQL是课程设计交付物里最容易扣分的地方。很多同学的SQL脚本没有注释、没有建库语句直接在MySQL里手动建表最后导出只导出表结构少了初始化数据老师导入后看到空白页面体验非常差。一个合格的sql/cake_shop.sql必须包含建库、建表、基础数据、索引和COMMENT注释让任何一台机器都能直接执行。DROP DATABASE IF EXISTS cake_shop; CREATE DATABASE cake_shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE cake_shop; CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT COMMENT 用户ID主键, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(255) NOT NULL COMMENT MD5或加盐哈希后的密码, nickname VARCHAR(50) DEFAULT COMMENT 昵称个人中心展示, phone VARCHAR(20) DEFAULT COMMENT 手机号订单通知用, role TINYINT NOT NULL DEFAULT 0 COMMENT 角色0普通用户1管理员, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB COMMENT用户表; CREATE TABLE category ( id INT NOT NULL AUTO_INCREMENT COMMENT 分类ID, name VARCHAR(50) NOT NULL COMMENT 分类名比如生日蛋糕、慕斯、小甜点, sort_order INT DEFAULT 0 COMMENT 控制后台排序数字越小越靠前, PRIMARY KEY (id) ) ENGINEInnoDB COMMENT蛋糕分类表; CREATE TABLE cake ( id INT NOT NULL AUTO_INCREMENT COMMENT 蛋糕ID, category_id INT NOT NULL COMMENT 所属分类逻辑外键关联category.id, name VARCHAR(100) NOT NULL COMMENT 蛋糕名称, description TEXT COMMENT 详细描述前台商品详情页展示, price DECIMAL(10,2) NOT NULL COMMENT 价格单位元保留两位小数, image_url VARCHAR(255) DEFAULT COMMENT 图片路径存相对路径不建议存完整URL, stock INT NOT NULL DEFAULT 0 COMMENT 库存下单时要扣减, sales INT NOT NULL DEFAULT 0 COMMENT 累计销量用于前台按销量排序, status TINYINT NOT NULL DEFAULT 1 COMMENT 上下架状态1上架 0下架, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id), KEY idx_status_sales (status, sales) ) ENGINEInnoDB COMMENT蛋糕商品表; CREATE TABLE orders ( id INT NOT NULL AUTO_INCREMENT COMMENT 订单号, user_id INT NOT NULL COMMENT 下单用户ID, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总金额, receiver_name VARCHAR(50) NOT NULL COMMENT 收货人, receiver_phone VARCHAR(20) NOT NULL COMMENT 收货手机号, receiver_address VARCHAR(255) NOT NULL COMMENT 收货地址, status TINYINT NOT NULL DEFAULT 0 COMMENT 订单状态0待支付 1已支付 2已发货 3已完成 4已取消, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME DEFAULT NULL COMMENT 支付时间未支付为空, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_create_time (create_time) ) ENGINEInnoDB COMMENT订单主表; CREATE TABLE order_item ( id INT NOT NULL AUTO_INCREMENT, order_id INT NOT NULL COMMENT 关联orders.id, cake_id INT NOT NULL COMMENT 商品ID, cake_name VARCHAR(100) COMMENT 下单时的商品快照防止商品改名后订单显示变化, quantity INT NOT NULL DEFAULT 1 COMMENT 购买数量, unit_price DECIMAL(10,2) NOT NULL COMMENT 下单时的单价快照, PRIMARY KEY (id), KEY idx_order_id (order_id) ) ENGINEInnoDB COMMENT订单明细表;这个脚本里的几个关键点值得在文档里单独写出来。价格字段必须用DECIMAL(10,2)不能用FLOAT或DOUBLE否则累计金额可能出现精度误差。订单状态用TINYINT加注释而不是直接用字符串是因为后台要支持状态流转数字比较和索引效率更高。订单明细表里的cake_name和unit_price是冗余快照蛋糕改名或改价后历史订单仍能显示下单时的信息。表与表之间我用的是逻辑外键加KEY索引没有写物理FOREIGN KEY因为课程设计阶段经常要删测试数据物理外键会干扰初始化SQL的执行顺序。这个是可以在答辩时主动说明的设计取舍。3. 搭出Maven工程并让详细注释落在关键位置3.1 Maven pom.xml依赖版本和打包方式课程设计项目没必要手写lib目录用Maven管理依赖能让“源码文档”更整洁。一个标准的JavaWeb项目pom.xml里至少要包含Servlet API、JSTL和MySQL驱动。Servlet API的scope要设为provided因为Tomcat自带了Servlet实现MySQL驱动版本要和本机数据库匹配。groupIdcom.cake/groupId artifactIdcake-shop/artifactId version1.0.0/version packagingwar/packaging properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version /dependency /dependencies这里的JSTL是给JSP页面用的。课程设计如果不用JSTL页面里就会到处是% for (...) %这种脚本片段代码难读且容易出错。用c:forEach和EL表达式页面的Java代码量能减少一半以上这也符合“详细注释”之外的代码整洁要求。3.2 源码目录里界面演示和文档说明各归其位下载别人的源码时最怕什么怕所有文件平铺在根目录找不到SQL脚本也找不到演示说明。自己搭建时要刻意把交付物分清楚sql放数据库脚本docs放数据库设计文档和演示说明README.md写启动步骤。前端页面放在webapp下JSP尽量放WEB-INF/jsp避免直接通过URL访问。cake-shop/ ├── pom.xml ├── README.md ├── sql/ │ └── cake_shop.sql ├── docs/ │ ├── 数据库设计文档.md │ └── 演示说明.md └── src/main/ ├── java/com/example/cake/ │ ├── controller/ # Servlet层 │ ├── dao/ # JDBC数据访问层 │ ├── filter/ # 编码、登录、权限过滤器 │ ├── model/ # JavaBean │ └── util/ # DBUtil等工具类 └── webapp/ ├── static/ # css、js、images ├── WEB-INF/ │ ├── jsp/ # 页面文件 │ └── web.xml └── index.jsp这样的结构一眼就能看出是标准JavaWeb项目。static目录放静态资源是为了配合${pageContext.request.contextPath}拼路径避免部署jar包或子目录时图片、CSS全部404。docs/数据库设计文档.md里把表关系、字段含义、状态枚举写清楚评分老师看文档的速度比看代码快得多。3.3 JDBC连接和详细注释的正确写法DBUtil类不需要复杂但要稳定。用静态代码块加载驱动提供getConnection()即可。课程设计阶段不要自己手写连接池容易写出内存泄漏如果老师追问就回答“生产环境会换成HikariCP”。package com.example.cake.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; /** * 数据库连接工具类。 * 课程设计版本使用DriverManager直连生产环境应改用数据库连接池。 */ public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/cake_shop?useUnicodetruecharacterEncodingutf8useSSLfalse; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(MySQL驱动未找到请检查pom.xml依赖); } } /** * 获取数据库连接。 * return Connection对象 * throws SQLException 连接失败时抛出 */ public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }写注释时不要解释“这行代码做了什么”要解释“为什么这么写”。比如useUnicodetruecharacterEncodingutf8是为了让JDBC传输中文时使用UTF-8编码否则页面显示乱码。useSSLfalse是避免MySQL 5.7向本地连接发起SSL握手时报警告。这些注释比在ps.setString上写“设置字符串”有价值得多。3.4 数据访问层PreparedStatement的注释与防SQL注入以登录查询为例DAO层方法要写成这样public User findByUsername(String username) { // SQL使用?占位符而不是字符串拼接 String sql SELECT id, username, nickname, password, role FROM user WHERE username ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User u new User(); u.setId(rs.getInt(id)); u.setUsername(rs.getString(username)); u.setNickname(rs.getString(nickname)); u.setPassword(rs.getString(password)); u.setRole(rs.getInt(role)); return u; } } } catch (SQLException e) { e.printStackTrace(); // 课程设计可以打印日志生产环境应使用Log框架 } return null; }PreparedStatement占位符的好处是参数会由MySQL驱动转义哪怕用户在用户名输入框中写 OR 11也只会被当作普通字符串查询。这也是“sql注入万能密码绕过”的防线。不要在代码里用Statement去拼接SQL不然演示时一旦被问到“怎么防止SQL注入”你会当场改代码。4. 商品展示、购物车与订单提交的ServletJSP实现4.1 Filter统一处理编码和登录判断页面与Servlet之间传递中文参数乱码是课程设计翻车的第一名。POST请求要用request.setCharacterEncoding(UTF-8)响应要设置ContentType。与其在每个Servlet里重复写不如用Filter统一处理这也是JavaWeb规范里“过滤链”概念的落地。WebFilter(/*) public class CharsetFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding(UTF-8); response.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); chain.doFilter(request, response); } }注意chain.doFilter必须放在最后如果放在这里之前就写了响应输出后续Servlet写入的字符也可能因为编码没生效而乱码。登录拦截filter也可以按类似思路写判断HttpSession里是否存了用户没有就重定向到登录页但要排除登录接口、注册接口和静态资源。4.2 商品列表Servlet从数据库读到JSP渲染商品列表接口不能直接在JSP里写JDBC代码。标准做法是Servlet调用DAO拿到List存到request域再forward到JSP。这样JSP只负责显示控制器只负责转发。WebServlet(/cake/list) public class CakeListServlet extends HttpServlet { private CakeDao cakeDao new CakeDao(); Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String categoryId request.getParameter(categoryId); ListCake cakes cakeDao.findOnSale(categoryId); request.setAttribute(cakes, cakes); request.getRequestDispatcher(/WEB-INF/jsp/cake_list.jsp) .forward(request, response); } }这里findOnSale里执行的是SELECT * FROM cake WHERE status 1 ORDER BY sales DESC。如果categoryId不为空就加AND category_id ?。JSP放在WEB-INF/jsp下有个好处用户直接访问/cake/list会经过Servlet访问/WEB-INF/jsp/cake_list.jsp会被Tomcat拒绝避免逻辑绕过。4.3 购物车存Session还是存数据库这是课程设计答辩里最常被问的问题。先看方案对比存储位置优点缺点适用场景Session实现简单生命周期由容器管理不写SQL浏览器关闭后丢失服务端重启也会丢课程设计、临时购物车Cookie客户端持久化不占服务端内存大小受限存商品JSON可读性差匿名购物车数据库表cart用户换设备不丢可做营销分析需要额外CRUD未登录时无法关联用户正式生产环境我一般建议课程设计用Session因为Cart页面和订单确认页都要读购物车Session里的Map可以随请求直接拿不用来回查数据库。加购的Servlet可以写成这样WebServlet(/cart/add) public class CartAddServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 未登录用户先跳登录已登录用户才允许加购物车 User user (User) request.getSession().getAttribute(LOGIN_USER); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } Integer cakeId Integer.valueOf(request.getParameter(cakeId)); Integer quantity Integer.valueOf(request.getParameter(quantity)); // 购物车键为蛋糕ID值为购买数量 HttpSession session request.getSession(); MapInteger, Integer cart (MapInteger, Integer) session.getAttribute(cart); if (cart null) { cart new HashMap(); session.setAttribute(cart, cart); } cart.put(cakeId, cart.getOrDefault(cakeId, 0) quantity); response.sendRedirect(request.getContextPath() /cart/view); } }这里有个细节cart.getOrDefault(cakeId, 0) quantity能处理同一个蛋糕多次点击“加入购物车”的情况。如果直接cart.put(cakeId, quantity)第二次加购会覆盖数量用户每次只能加一个这也是调试时容易忽略的边界。4.4 页面展示JSP里用EL和JSTL而不是Scriptlet商品列表页的展示可以用一个简单的JSP片段来说明% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % div classcake-grid c:forEach items${cakes} varcake div classcake-card img src${pageContext.request.contextPath}${cake.imageUrl} alt${cake.name} h3${cake.name}/h3 p${cake.price}/p p库存${cake.stock}/p form action${pageContext.request.contextPath}/cart/add methodpost input typehidden namecakeId value${cake.id} input typenumber namequantity value1 min1 max${cake.stock} button typesubmit加入购物车/button /form /div /c:forEach /div${pageContext.request.contextPath}是所有静态资源和表单提交路径的前缀。这个值等于部署后的项目上下文路径比如/cake-shop。如果写成/static/images/a.jpg部署到http://localhost:8080/cake-shop/时浏览器会去请求http://localhost:8080/static/images/a.jpg这就会404。这也是“界面演示”环节图片加载不出来的一个重要原因。5. 登录验证、订单事务与后台权限管理5.1 登录成功后的Session与会话超时登录逻辑不要只判断“密码对不对”还要考虑Session中存什么、退出时清什么。课程设计通常只需要在登录成功后把User对象放进SessionUser loginUser userDao.findByUsernameAndPassword(username, password); if (loginUser ! null) { request.getSession().setAttribute(LOGIN_USER, loginUser); response.sendRedirect(request.getContextPath() /index.jsp); } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); }退出登录时调用session.invalidate()让整个Session失效而不是只移除LOGIN_USER属性。因为Session里可能还有购物车、验证码等敏感信息全部清掉更安全。会话超时可以用web.xml里的session-config配置默认30分钟session-config session-timeout30/session-timeout /session-config5.2 提交订单时订单主表和明细表必须同时写入订单接口是整个系统里最容易出事务问题的地方。只插入orders表不插入order_item用户订单详情就查不到商品只插入明细没有主表金额和收货信息就是空的。课程设计里最常见的错误是两条INSERT分开执行第一条成功、第二条抛异常数据库里留下脏数据。正确的写法是把两条INSERT放在同一个事务里Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); // 插入订单主表需要拿到生成的主键 String insertOrder INSERT INTO orders (user_id, total_amount, receiver_name, receiver_phone, receiver_address, status) VALUES (?,?,?,?,?,0); PreparedStatement psOrder conn.prepareStatement(insertOrder, Statement.RETURN_GENERATED_KEYS); psOrder.setInt(1, loginUser.getId()); psOrder.setBigDecimal(2, totalAmount); psOrder.setString(3, receiverName); psOrder.setString(4, receiverPhone); psOrder.setString(5, receiverAddress); psOrder.executeUpdate(); ResultSet keys psOrder.getGeneratedKeys(); int orderId 0; if (keys.next()) { orderId keys.getInt(1); } // 批量插入订单明细 String insertItem INSERT INTO order_item (order_id, cake_id, cake_name, quantity, unit_price) VALUES (?,?,?,?,?); PreparedStatement psItem conn.prepareStatement(insertItem); for (CartItem item : cartItems) { psItem.setInt(1, orderId); psItem.setInt(2, item.getCakeId()); psItem.setString(3, item.getCakeName()); psItem.setInt(4, item.getQuantity()); psItem.setBigDecimal(5, item.getUnitPrice()); psItem.addBatch(); } psItem.executeBatch(); conn.commit(); } catch (SQLException e) { // 任意一条失败回滚整个订单避免出现订单主表成功、明细失败的情况 conn.rollback(); throw e; } finally { conn.setAutoCommit(true); conn.close(); }这段代码里有三个值得写在注释里的要点Statement.RETURN_GENERATED_KEYS用于获取自增主键明细单价必须从数据库的cake表重新查询得到不能直接信任用户在页面上传的价格参数购物车里的数量校验在Servlet层做一次在SQL里再做一次stock quantity判断防止并发下单超卖。5.3 后台管理管理员资格判断和订单状态更新后台管理页面不能只靠前端隐藏入口来保护因为用户可以直接输入/admin/order/list访问。必须要有一个Filter拦截/admin/*路径WebFilter(/admin/*) public class AdminAuthFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpSession session request.getSession(false); User user null; if (session ! null) { user (User) session.getAttribute(LOGIN_USER); } // 角色1表示管理员其他角色一律拒绝 if (user null || user.getRole() ! 1) { ((HttpServletResponse) resp).sendError(HttpServletResponse.SC_FORBIDDEN); return; } chain.doFilter(req, resp); } }request.getSession(false)不会主动创建Session避免未登录用户被打到后台时也在服务端生成无用会话。管理员更新订单状态的SQL很简单但要注意参数校验UPDATE orders SET status ?, update_time NOW() WHERE id ?状态值必须限制在枚举范围内不能让请求传一个99就把订单状态改乱。一般可以用一个后台Servlet判断参数在0到4之间否则返回错误。5.4 后台接口的权限边界很多课程设计项目只做了登录Filter没做管理员Filter导致普通用户登录后通过猜测URL就能进入后台管理界面。一旦评审老师问“你如何保证普通用户不能下单篡改成已发货”就会露馅。前后台要分开拦截普通登录Filter排除/admin/*管理员Filter专门处理/admin/*。如果项目里用role字段区分管理员后台的JSP顶部也要再判断一次角色双保险。即使第一个Filter不小心放行第二个Filter也能拦住。权限之外后台还要防止越权修改比如updateOrderStatus接口要校验订单id存在更新结果executeUpdate()返回0时说明订单不存在要提示错误而不是直接显示成功。6. 答辩前用EXPLAIN抓慢SQL并整理演示材料6.1 用EXPLAIN看订单列表查询课程设计里订单量不大但答辩时老师经常问“数据量大了怎么办”。与其空口回答不如现场演示一次慢SQL优化。例如后台要查某用户最近的订单常用查询是EXPLAIN SELECT o.*, u.nickname FROM orders o LEFT JOIN user u ON o.user_id u.id WHERE o.status 1 ORDER BY o.create_time DESC LIMIT 20;EXPLAIN的结果里要看三列type是ALL说明全表扫描rows是扫描行数Extra如果出现filesort说明排序没走索引。给orders表加上索引后情况会明显改善ALTER TABLE orders ADD INDEX idx_status_create_time (status, create_time);这个索引同时覆盖status过滤和create_time排序避免ORDER BY临时文件排序。在数据库设计文档里可以加一段这类优化记录演示时直接把EXPLAIN结果截图放进去比任何“性能不错”更有说服力。6.2 演示前五分钟自检命令准备答辩前把交付物按下面顺序检查一遍能避开多数“跑不起来”的情况mysql -uroot -p sql/cake_shop.sql mvn clean package cp target/cake-shop.war /path/to/tomcat/webapps/ /opt/tomcat/bin/startup.sh curl -I http://localhost:8080/cake-shop/第一条命令执行完看输出里有没有ERROR尤其是编码不一致的报错。第二条命令如果失败检查pom.xml里JDK版本和依赖版本。curl返回200后再打开浏览器走一遍“注册→登录→加购→下单→后台发货”的完整路径。页面图片如果打不开优先检查控制台里的image_url值是否包含${pageContext.request.contextPath}后缀。把这条完整路径录屏或截图放进文档说明评分老师不用自己动手点就能快速理解系统全貌。本文还有配套的精品资源点击获取