简介这是一套基于JavaEE技术栈开发的在线购物商城完整项目面向计算机专业本科生及Java初学者专为毕业设计、课程设计与期末大作业打造。项目采用原生ServletJSP架构不依赖Spring等框架代码结构清晰、注释详尽涵盖用户管理、商品浏览、购物车、订单处理等核心电商模块便于理解MVC分层逻辑与Web基础开发流程。资源包共2000个文件主体包括312个HTML页面、262个CSS样式文件、193个PNG图片、107个JS脚本、54个Java源码、30个JSP视图页及1个SQL数据库脚本辅以配置文件与文档说明总大小11.55MB开箱即用简单部署Tomcat即可运行。目前已有742人学习下载项目获导师高度认可个人手打评分98分附带完整数据库表结构、DAO层实现类如ProductDao、UserServlet、OrderServlet等及初始化逻辑是掌握JavaWeb基础开发与系统集成实践的高价值学习范例。1. 这不是又一个“Hello World”JSP项目它真能跑通用户注册→商品浏览→下单支付全流程且所有Servlet类名、DAO层命名、数据库字段全对齐JavaEE规范非Spring Boot魔改版你可能已经点开过十几个标着“JSP购物商城”的压缩包解压后发现login.jsp里写死了用户名密码、productList.jsp连数据库都没连、web.xml里servlet-mapping路径错位导致404——最后只能删掉重装Tomcat。这个项目不一样。它用纯原生JavaEE技术栈Servlet 3.1 JSP 2.3 JDBC MySQL 5.7不依赖任何框架但完整实现了用户账户体系含密码MD5加盐、商品分类管理、购物车会话保持、订单生成与状态流转、后台管理员登录鉴权五大核心模块。所有Servlet类UserServlet、ProductServlet、OrderServlet等都继承HttpServlet并覆写doGet/doPost所有DAO类ProductDao、AccountDao、TypeDao严格遵循JDBC模板模式SQL语句全部参数化预编译无拼接风险。它不是教学Demo而是按高校《Java Web开发课程设计》评分标准逐项打分的98分实作有完整ER图、数据库建表SQL含外键约束与索引说明、每个JSP页面顶部都有pageEncodingUTF-8和contentType声明、web.xml中servlet-name与url-pattern一一对应。适合正在赶毕设 deadline 的大三学生、需要交课程设计的专科生、或想补足JavaEE底层链路的转行者——部署前你唯一要改的只有数据库连接配置里的ip、端口、库名、账号密码。2. 从零部署Tomcat 8.5 MySQL 5.7 环境搭建与war包热部署实操2.1 Tomcat 8.5 安装与JVM参数调优避坑关键别用Tomcat 10这个项目基于Servlet 3.1规范而Tomcat 10开始默认使用Jakarta EE命名空间javax.* → jakarta.*直接部署会报java.lang.NoClassDefFoundError: javax/servlet/HttpServlet。必须用Tomcat 8.5.x或9.0.x。我本地验证过8.5.99最新8.5分支和9.0.85均稳定运行。下载地址https://tomcat.apache.org/download-8.cgi选Binary Distributions → Core → zip解压后进入conf/server.xml找到Connector节点必须修改以下三项Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 !-- 关键解决中文参数乱码 -- maxThreads200 !-- 避免高并发时线程耗尽 -- minSpareThreads20 /提示URIEncodingUTF-8是硬性要求。否则在商品搜索页输入中文关键词request.getParameter(keyword)返回乱码后续所有查询失效。启动前检查bin/catalina.batWindows或catalina.shLinux中的JVM参数。项目含大量JSP编译需增大PermGenJava 7/8或MetaspaceJava 8# Windows catalina.bat 末尾追加 set JAVA_OPTS%JAVA_OPTS% -XX:MaxMetaspaceSize256m -Xms512m -Xmx1024m# Linux catalina.sh 末尾追加 JAVA_OPTS$JAVA_OPTS -XX:MaxMetaspaceSize256m -Xms512m -Xmx1024m启动命令# Windows bin\startup.bat # Linux ./bin/startup.sh访问http://localhost:8080出现Tomcat欢迎页即成功。2.2 MySQL 5.7 创建专用库与用户字符集必须为utf8mb4项目数据库脚本sql/shop_db.sql明确要求CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci这是为兼容emoji和四字节UTF-8字符如某些生僻汉字。若用默认latin1或utf8MySQL的utf8实为utf8mb3插入商品描述含“¥”符号或用户昵称含“”字时会报错Incorrect string value。执行以下命令创建库与用户-- 1. 创建数据库注意不是CREATE DATABASE shop; CREATE DATABASE shop_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 2. 创建专用用户禁止用root直连 CREATE USER shop_userlocalhost IDENTIFIED BY ShopPass123!; -- 3. 授予最小必要权限 GRANT SELECT, INSERT, UPDATE, DELETE ON shop_db.* TO shop_userlocalhost; -- 4. 刷新权限 FLUSH PRIVILEGES;注意密码ShopPass123!需符合MySQL 5.7默认密码策略至少8位含大小写字母数字特殊字符。若提示Your password does not satisfy the current policy requirements先执行SET GLOBAL validate_password_policy0; SET GLOBAL validate_password_length4;2.3 导入数据库脚本与验证表结构项目提供的sql/shop_db.sql包含建库、建表、初始化数据三部分。不要直接双击运行——Navicat或MySQL Workbench可能因SQL文件编码问题导致注释解析错误。正确做法命令行导入确保文件保存为UTF-8无BOM格式# Windows在MySQL安装目录bin下执行 mysql -u shop_user -pShopPass123! D:\project\shop_db.sql # Linux mysql -u shop_user -pShopPass123! /home/user/project/shop_db.sql导入后验证关键表是否存在且字段正确USE shop_db; SHOW TABLES; -- 应看到account, order_info, order_item, product, product_type, shopping_cart DESCRIBE product; -- 必须包含id(INT PK), name(VARCHAR 100), price(DECIMAL 10,2), type_id(INT), stock(INT), image_path(VARCHAR 200) -- 注意price是DECIMAL类型不是FLOAT——避免浮点数精度误差导致金额计算错误2.4 编译Java源码并打包为WARIntelliJ IDEA与Eclipse双路径项目源码结构为标准JavaEE Web Applicationsrc/ ├── com/shop/servlet/ # 所有Servlet类 ├── com/shop/dao/ # 所有DAO类 ├── com/shop/entity/ # 实体类Account, Product, Order等 ├── com/shop/util/ # 工具类DBUtil, MD5Util WebContent/ ├── WEB-INF/ │ ├── web.xml # 核心配置 │ └── lib/ # 依赖jarmysql-connector-java-5.1.47.jar已内置 ├── index.jsp ├── login.jsp └── ...IntelliJ IDEA 路径File → New → Project from Existing Sources选择项目根目录选择Import project from external model → Eclipse即使不是Eclipse项目此选项能自动识别Web Facet在Project Structure → Modules → Dependencies中确认mysql-connector-java-5.1.47.jar已在WEB-INF/lib下且被标记为ProvidedBuild → Build Artifacts → Build生成shop.warEclipse 路径File → Import → General → Existing Projects into Workspace右键项目 →Properties → Project Facets→ 勾选Dynamic Web Module 3.1、Java 1.8Project → Properties → Java Build Path → Libraries→ Add External JARs → 选择WebContent/WEB-INF/lib/mysql-connector-java-5.1.47.jarRight click project → Export → WAR file提示若IDEA编译报错package javax.servlet does not exist说明未添加Servlet API依赖。手动添加Project Structure → Libraries → → Java → 选择Tomcat安装目录/lib/servlet-api.jar2.5 部署WAR包到Tomcat并验证首页可访问将生成的shop.war复制到tomcat/webapps/目录下Tomcat会自动解压生成同名文件夹shop/。等待控制台输出INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [...] has finished in [...] ms。此时访问http://localhost:8080/shop/若跳转至index.jsp且显示“欢迎来到在线购物商城”说明部署成功若报404检查webapps/shop/目录是否存在以及shop/WEB-INF/web.xml是否被正确读取若报500查看logs/catalina.out常见原因是DBUtil.java中数据库连接参数未修改3. 源码级拆解Servlet生命周期、DAO分层与JSP表达式语言实战3.1 Servlet如何响应一次用户登录请求以UserServlet为例UserServlet.java是整个用户体系的入口。它不处理HTML渲染只做业务逻辑与跳转控制严格遵循MVC中CController职责。关键代码段doPost方法protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action request.getParameter(action); // 从表单hidden字段获取操作类型 if (login.equals(action)) { String username request.getParameter(username); String password request.getParameter(password); // 1. 密码加盐MD5盐值hardcode在MD5Util.SALT String md5Password MD5Util.getMD5(password MD5Util.SALT); // 2. 调用DAO查询用户 Account account new AccountDao().findByNameAndPassword(username, md5Password); if (account ! null) { // 3. 登录成功存入session重定向到首页 request.getSession().setAttribute(user, account); response.sendRedirect(request.getContextPath() /index.jsp); } else { // 4. 登录失败设置错误信息转发回登录页 request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } else if (logout.equals(action)) { request.getSession().invalidate(); // 销毁session response.sendRedirect(request.getContextPath() /index.jsp); } }逻辑说明request.getParameter(action)是JSP表单提交的关键路由开关避免一个Servlet写满if-elseMD5Util.getMD5()使用MessageDigest.getInstance(MD5)盐值SALTshop2024硬编码在工具类中生产环境应改为动态盐AccountDao.findByNameAndPassword()内部执行预编译SQLSELECT * FROM account WHERE username? AND password?response.sendRedirect()是客户端重定向URL变化request.getRequestDispatcher().forward()是服务器端转发URL不变request属性可传递3.2 DAO层如何实现“一次查询多处复用”以ProductDao为例ProductDao.java封装了所有商品相关数据库操作采用标准JDBC模板public ListProduct findAll() throws SQLException { String sql SELECT p.id, p.name, p.price, p.stock, p.image_path, t.name AS type_name FROM product p LEFT JOIN product_type t ON p.type_id t.id ORDER BY p.id DESC; ListProduct products new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Product p new Product(); p.setId(rs.getInt(id)); p.setName(rs.getString(name)); p.setPrice(rs.getBigDecimal(price)); // 注意用BigDecimal而非double p.setStock(rs.getInt(stock)); p.setImagePath(rs.getString(image_path)); p.setTypeName(rs.getString(type_name)); // 关联查询结果直接赋值 products.add(p); } } return products; }参数说明DBUtil.getConnection()从db.properties读取配置每次调用新建连接无连接池适合小项目PreparedStatement防止SQL注入rs.getBigDecimal(price)确保金额精度double在金融场景会丢失精度LEFT JOIN保证即使某商品type_id为NULL也能查出t.name AS type_name为关联字段起别名便于JSP中EL表达式取值3.3 JSP如何安全展示动态数据EL表达式与JSTL最佳实践所有JSP页面顶部声明% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8% % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt %在product_list.jsp中展示商品列表c:forEach items${products} varp div classproduct-item img src${p.imagePath} alt${p.name} width150 height150 h3${p.name}/h3 p价格fmt:formatNumber value${p.price} typecurrency currencySymbol¥//p p库存c:out value${p.stock}/ 件/p a hrefproduct_detail.jsp?id${p.id}查看详情/a /div /c:forEach关键点${p.imagePath}自动转义HTML特殊字符如变成lt;防止XSS攻击fmt:formatNumber格式化金额避免199.99000000000002这种浮点误差显示c:out value${p.stock}/显式输出比${p.stock}更安全当p.stock为null时不会报错hrefproduct_detail.jsp?id${p.id}中ID直接拼接URL因p.id为Integer类型且来自数据库主键无注入风险3.4 web.xml配置如何决定URL路由Servlet映射原理web.xml是JavaEE时代的“中央路由器”定义了URL路径与Servlet的绑定关系servlet servlet-nameUserServlet/servlet-name servlet-classcom.shop.servlet.UserServlet/servlet-class /servlet servlet-mapping servlet-nameUserServlet/servlet-name url-pattern/user/url-pattern !-- 所有/user/*请求都交给UserServlet -- /servlet-mapping servlet servlet-nameProductServlet/servlet-name servlet-classcom.shop.servlet.ProductServlet/servlet-class /servlet servlet-mapping servlet-nameProductServlet/servlet-name url-pattern/product/url-pattern /servlet-mapping这意味着表单提交form action/user methodpost→ 触发UserServlet.doPost()浏览器访问http://localhost:8080/shop/product?actionlist→ 触发ProductServlet.doGet()绝对路径规则/user前面的/表示应用根路径即/shop所以实际URL是/shop/user注意若url-pattern写成/user/*则/user/login和/user/logout都会进同一个Servlet需在doPost中用request.getRequestURI()二次解析而本项目用?actionxxx更轻量。4. 避坑指南90%新手卡住的5个真实问题与血泪解决方案4.1 现象首页打开空白浏览器控制台报Failed to load resource: the server responded with a status of 404 ()原因web.xml中welcome-file-list未配置index.jsp或index.jsp不在WebContent/根目录下解决检查web.xml是否有以下配置welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list并确认index.jsp物理路径为WebContent/index.jsp不是WebContent/pages/index.jsp。若放错位置Tomcat启动时不报错但无法作为欢迎页。4.2 现象登录时用户名密码正确却提示“用户名或密码错误”原因MD5Util.getMD5()生成的密文与数据库中存储的密文不一致常见于数据库password字段长度不足解决执行SQL检查字段长度DESCRIBE account; -- 正确应为password VARCHAR(32) NOT NULL -- 若为VARCHAR(20)则MD5后的32位字符串被截断比对永远失败 ALTER TABLE account MODIFY password VARCHAR(32);同时确认MD5Util.SALT值与建库SQL中插入的测试用户密码所用盐值一致sql/shop_db.sql第127行INSERT INTO account VALUES (1,admin, 21232f297a57a5a743894a0e4a801fc3, ...)其中21232f297a57a5a743894a0e4a801fc3是adminshop2024的MD5值。4.3 现象商品图片不显示路径为/shop/images/1.jpg但返回404原因Product.getImagePath()返回相对路径images/1.jpg而JSP中写成img src${p.imagePath}导致浏览器请求/shop/images/1.jpg正确但图片实际存放在WebContent/images/下解决两种方案任选其一方案A推荐在Product实体类中重写getImagePath()返回带上下文路径的绝对路径public String getImagePath() { return request.getContextPath() / imagePath; // 需在Servlet中传入request }方案B快速修复JSP中强制加前缀img src${pageContext.request.contextPath}/${p.imagePath} ...4.4 现象下单后订单状态始终为“待付款”后台无法修改状态原因OrderServlet.java中updateStatus方法SQL语句写错UPDATE order_info SET status? WHERE id?漏写了WHERE条件导致全表status被更新解决定位OrderServlet.java第89行修正为String sql UPDATE order_info SET status? WHERE id?; // 原代码为UPDATE order_info SET status?并确认request.getParameter(id)能正确获取订单ID前端表单必须有input typehidden nameid value${order.id}。4.5 现象Tomcat启动后控制台疯狂刷SEVERE: Error filterStart应用无法加载原因WEB-INF/lib/下存在多个版本的javax.servlet-api.jar如既有javax.servlet-api-3.1.0.jar又有servlet-api.jar类加载冲突解决进入WebContent/WEB-INF/lib/目录删除所有名称含servlet-api或javax.servlet的jar仅保留mysql-connector-java-5.1.47.jar因为Servlet API由Tomcat提供项目中只需编译时依赖运行时应Provided5. 数据库设计深度解析从ER图到事务边界控制5.1 五张核心表的关联逻辑与范式合规性项目数据库共7张表但业务核心为以下5张符合第三范式3NF表名主键外键关键约束设计意图accountid(INT PK AI)—usernameUNIQUE,emailUNIQUE用户主表存储登录凭证与基本信息product_typeid(INT PK AI)—nameNOT NULL商品分类支持无限层级本项目仅一级productid(INT PK AI)type_id→product_type.idprice 0,stock≥ 0商品主表关联分类shopping_cart(account_id,product_id)account_id→account.id,product_id→product.idquantity 0购物车中间表记录用户-商品多对多关系order_infoid(INT PK AI)account_id→account.idstatusENUM(待付款,已付款,已发货,已完成,已取消)订单头表记录用户下单行为特别说明shopping_cart表复合主键(account_id,product_id)确保同一用户不能重复添加同一商品无自增ID因业务上“用户A加购商品B”是原子操作无需额外标识quantity字段允许用户一次加购多件避免多次点击产生多条记录5.2 下单事务的ACID实现手写JDBC事务控制OrderServlet.java中createOrder方法是全项目唯一显式事务控制点public void createOrder(HttpServletRequest request, HttpServletResponse response) throws SQLException, IOException { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交 // 1. 插入订单头 String sql1 INSERT INTO order_info(account_id, total_amount, status, create_time) VALUES (?, ?, 待付款, NOW()); PreparedStatement ps1 conn.prepareStatement(sql1, Statement.RETURN_GENERATED_KEYS); ps1.setInt(1, accountId); ps1.setBigDecimal(2, totalAmount); ps1.executeUpdate(); // 2. 获取生成的订单ID ResultSet rs ps1.getGeneratedKeys(); rs.next(); int orderId rs.getInt(1); // 3. 插入订单明细循环购物车商品 String sql2 INSERT INTO order_item(order_id, product_id, quantity, price) VALUES (?, ?, ?, ?); PreparedStatement ps2 conn.prepareStatement(sql2); for (CartItem item : cartItems) { ps2.setInt(1, orderId); ps2.setInt(2, item.getProductId()); ps2.setInt(3, item.getQuantity()); ps2.setBigDecimal(4, item.getPrice()); ps2.addBatch(); } ps2.executeBatch(); // 4. 清空购物车 String sql3 DELETE FROM shopping_cart WHERE account_id ?; PreparedStatement ps3 conn.prepareStatement(sql3); ps3.setInt(1, accountId); ps3.executeUpdate(); conn.commit(); // 全部成功才提交 response.sendRedirect(order_success.jsp?orderId orderId); } catch (SQLException e) { if (conn ! null) conn.rollback(); // 任一环节失败则回滚 request.setAttribute(errorMsg, 下单失败 e.getMessage()); request.getRequestDispatcher(cart.jsp).forward(request, response); } finally { if (conn ! null) conn.close(); } }事务边界说明conn.setAutoCommit(false)是起点conn.commit()是终点conn.rollback()是异常出口ps1.getGeneratedKeys()获取自增ID是MySQL特有语法不可替换为SELECT LAST_INSERT_ID()并发时可能取错ps2.addBatch()批量插入订单明细比循环executeUpdate()性能提升5倍以上DELETE FROM shopping_cart必须在事务内否则出现“订单已生成但购物车未清空”的脏数据5.3 索引优化为什么product表要建联合索引(type_id, id)product_list.jsp分页查询常用语句SELECT * FROM product WHERE type_id ? ORDER BY id DESC LIMIT 0,10若只在type_id建单列索引MySQL仍需扫描所有type_id2的记录再排序id而联合索引(type_id, id)可直接定位type_id2的索引片并按id倒序取出前10条——避免filesort。创建索引命令-- 删除旧索引如有 DROP INDEX idx_type_id ON product; -- 创建联合索引 CREATE INDEX idx_type_id_id ON product(type_id, id DESC);验证索引生效EXPLAIN SELECT * FROM product WHERE type_id 2 ORDER BY id DESC LIMIT 10; -- 输出中key列应为idx_type_id_idExtra列应为Using index5.4 安全加固SQL注入与XSS的双重防护实证项目虽无框架但通过三层防御阻断常见攻击攻击类型防御位置实现方式验证方法SQL注入所有DAO类100%使用PreparedStatement无Statement.executeQuery(SELECT * FROM user WHERE namename)拼接尝试在登录框输入admin OR 11应返回“用户名或密码错误”而非登录成功XSS跨站所有JSP页面EL表达式${xxx}默认转义敏感输出用c:out表单提交前JS校验login.jsp第42行输入用户名scriptalert(1)/script页面显示为纯文本而非弹窗密码泄露MD5Util.java加盐MD5盐值shop2024非明文存储查看数据库account表密码字段为32位十六进制字符串非明文提示login.jsp中JS校验仅防误操作不可替代后端校验。真正的安全防线在UserServlet中request.getParameter()之后的逻辑。6. 毕设答辩高频问题预演与代码重构技巧从“能跑”到“可讲”6.1 导师必问的3个灵魂问题与满分回答模板问题1“为什么不用Spring Boot而坚持原生Servlet”答本项目定位是《Java Web开发》课程设计教学目标是掌握HTTP协议本质、Servlet生命周期、JDBC底层交互。Spring Boot自动配置掩盖了web.xml作用、DispatcherServlet分发机制、DataSource连接池原理。例如本项目DBUtil.getConnection()手动管理连接能让学生清晰看到“一次请求一次连接一次close”而Spring Boot的Transactional会隐藏这些细节。这就像学开车先练手动挡再开自动挡。问题2“购物车用Session还是数据库为什么”答本项目采用Session数据库混合方案。未登录用户购物车存于HttpSessionsession.setAttribute(cart, cartList)登录后自动合并到数据库shopping_cart表。这样既保证游客体验关闭浏览器即清空又满足登录用户跨设备同步需求。关键代码在CartServlet.java第53行if (user null) { sessionCart.mergeToDb(); }。问题3“订单状态流转如何保证一致性”答状态变更全部走OrderServlet统一入口且每个操作加数据库行锁。例如“付款”操作执行UPDATE order_info SET status已付款 WHERE id? AND status待付款AND status待付款是乐观锁若并发时另一请求已将状态改为“已取消”此SQL影响行数为0后端捕获getUpdateCount()0即返回“订单状态异常”。这比分布式锁更轻量适合单库场景。6.2 三个让代码“看起来很专业”的重构技巧答辩加分项技巧1把硬编码字符串提取为常量接口避免魔法值原代码ProductServlet.java中if (list.equals(action)) { ... } if (detail.equals(action)) { ... }重构为// 新建 interface ActionConstant public interface ActionConstant { String LIST list; String DETAIL detail; String ADD_TO_CART add_to_cart; } // 使用 if (ActionConstant.LIST.equals(action)) { ... }价值答辩时可说“通过常量接口统一管理业务动作降低维护成本避免拼写错误”。技巧2为DAO方法添加日志埋点体现工程素养在BaseDao.java所有DAO父类中protected void logSQL(String sql, Object... params) { StringBuilder sb new StringBuilder(SQL: ).append(sql); if (params.length 0) { sb.append( | Params: ); for (int i 0; i params.length; i) { sb.append(params[i]).append(i params.length - 1 ? : , ); } } System.out.println(sb.toString()); // 实际项目应接入log4j }调用logSQL(SELECT * FROM product WHERE type_id?, typeId);价值演示时打开Tomcat日志能看到每条SQL执行轨迹导师会觉得“这孩子懂监控”。技巧3用Filter实现统一编码过滤解决中文乱码终极方案新建EncodingFilter.javapublic class EncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); chain.doFilter(request, response); } }web.xml中配置filter filter-nameEncodingFilter/filter-name filter-classcom.shop.filter.EncodingFilter/filter-class /filter filter-mapping filter-nameEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping价值彻底解决GET/POST/URL参数三重乱码比在每个Servlet里request.setCharacterEncoding(UTF-8)更优雅。6.3 答辩演示清单5分钟内让导师眼前一亮的3个操作准备一台干净虚拟机或自己电脑按顺序执行打开数据库运行SELECT * FROM product WHERE stock 0 LIMIT 5;展示商品数据真实存在前台下单访问/shop/→ 点击“手机”分类 → 选一款商品加入购物车 → 结算 → 输入测试账号admin/admin→ 成功跳转订单页打开Navicat刷新order_info表指出新生成的订单记录及status待付款后台审核访问/shop/admin/login.jsp账号admin/admin→ 进入订单管理 → 找到刚下的订单 → 点击“发货”切回Navicat执行SELECT status FROM order_info WHERE idLAST_INSERT_ID();显示status已发货从那以后我每次帮学弟改毕设都强制他们先跑通这三步数据库查得到、前台下得成、后台改得动。只要这三点稳了答辩时导师问“这个功能怎么实现的”你就能指着代码说“您看这里就是这段逻辑”而不是支吾“好像是…大概…”。希望帮到你。本文还有配套的精品资源点击获取