news 2026/9/12 2:07:53

JavaWeb蛋糕店系统课程设计实战:JSP+Servlet+MySQL完整实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaWeb蛋糕店系统课程设计实战:JSP+Servlet+MySQL完整实现

简介:这是一份基于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 为什么选JSP+Servlet+JDBC而不是Spring Boot

JavaWeb是一个宽泛的范畴,很多人默认Spring Boot也算,但课程设计里“基于javaweb”的要求,更常见的是考察你对Servlet容器、请求响应链路、HttpSession、Filter、JDBC这些经典组件的掌握程度。JSP+Servlet+JDBC能把一次请求从浏览器到Servlet、再到DAO、数据库,最后回到JSP渲染的过程完整暴露在代码里,评审问“Filter什么时候执行”“PreparedStatement为什么能防SQL注入”时,你可以直接指到对应代码解释。Spring Boot把这些都封装了,启动快是真快,但答辩时容易陷入“配置对了就能跑”的尴尬。

蛋糕店系统规模不大,用JSP直出页面比前后端分离更直观。不用Vue、不用REST接口,表单提交后request.setAttribute配合JSP的EL表达式,就能把商品列表渲染出来。这种传统方式部署也简单,打一个war包丢进Tomcat即可,不会出现静态资源和接口跨域的额外争论。JDBC连接直接用DriverManager写在一个DBUtil里,对课程设计完全够用,还能顺带解释连接池的意义。

2.2 功能边界:访客、会员、管理员三种角色

系统不能只做一个商品陈列页。课程设计要有闭环,就必须定义三种角色和各自的页面操作,否则后台管理无从谈起。访客能看到首页、分类、商品详情,但点击“加入购物车”会被拦截到登录页;会员登录后可以加购物车、提交订单、查看自己的订单列表;管理员则进入独立后台,维护分类和商品,处理订单状态。

角色可操作内容典型页面涉及数据表
访客浏览分类和商品,查看详情首页、列表页、详情页category, cake
会员登录后加购物车、下单、查看订单购物车、订单确认、我的订单cart(Session实现)、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) ) ENGINE=InnoDB 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) ) ENGINE=InnoDB 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) ) ENGINE=InnoDB 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) ) ENGINE=InnoDB 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) ) ENGINE=InnoDB COMMENT='订单明细表';

这个脚本里的几个关键点值得在文档里单独写出来。价格字段必须用DECIMAL(10,2),不能用FLOATDOUBLE,否则累计金额可能出现精度误差。订单状态用TINYINT加注释,而不是直接用字符串,是因为后台要支持状态流转,数字比较和索引效率更高。订单明细表里的cake_nameunit_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驱动版本要和本机数据库匹配。

<groupId>com.cake</groupId> <artifactId>cake-shop</artifactId> <version>1.0.0</version> <packaging>war</packaging> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.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?useUnicode=true&characterEncoding=utf8&useSSL=false"; 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); } }

写注释时不要解释“这行代码做了什么”,要解释“为什么这么写”。比如useUnicode=true&characterEncoding=utf8是为了让JDBC传输中文时使用UTF-8编码,否则页面显示乱码。useSSL=false是避免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 '1'='1,也只会被当作普通字符串查询。这也是“sql注入万能密码绕过”的防线。不要在代码里用Statement去拼接SQL,不然演示时一旦被问到“怎么防止SQL注入”,你会当场改代码。

4. 商品展示、购物车与订单提交的Servlet+JSP实现

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;charset=UTF-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"); List<Cake> 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(); Map<Integer, Integer> cart = (Map<Integer, 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 contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <div class="cake-grid"> <c:forEach items="${cakes}" var="cake"> <div class="cake-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" method="post"> <input type="hidden" name="cakeId" value="${cake.id}"> <input type="number" name="quantity" value="1" min="1" max="${cake.stock}"> <button type="submit">加入购物车</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对象放进Session:

User 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-timeout>30</session-timeout> </session-config>

5.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的结果里要看三列:typeALL说明全表扫描,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}后缀。把这条完整路径录屏或截图放进文档说明,评分老师不用自己动手点,就能快速理解系统全貌。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 2:04:00

热电联产经济调度:PSO与遗传算法的混合优化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 2:00:23

C++ STL中set和map容器的核心原理与工程实践

1. STL容器概述&#xff1a;为什么需要set和map&#xff1f;在C标准模板库(STL)中&#xff0c;set和map属于关联式容器&#xff0c;它们与序列式容器(vector/list等)最大的区别在于其底层采用红黑树实现&#xff0c;能够自动维护元素的有序性。我在处理电商平台的商品分类系统时…

作者头像 李华
网站建设 2026/9/12 1:59:54

触摸开关芯片抗干扰与灵敏度调节:从ADC采样到实战调参

最近在产线上有一批触摸面板的样品出了怪问题&#xff1a;用可调电源供电时一切正常&#xff0c;一插上客户的开关电源适配器&#xff0c;按键就开始乱跳&#xff1b;更麻烦的是&#xff0c;样机在桌上放了一夜&#xff0c;第二天早上起来第一个键按下没反应&#xff0c;热机五…

作者头像 李华