ARTICLE DETAIL

资讯详情

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

JavaWeb蛋糕店系统:JSP+Servlet+MySQL+Bootstrap完整落地实践

JavaWeb蛋糕店系统:JSP+Servlet+MySQL+Bootstrap完整落地实践 简介本资源是一套完整的JavaWeb课程设计与毕业设计参考项目面向计算机类专业在校学生、初学者及教学从业者解决Web开发入门实践与项目整合能力提升问题。项目基于JSPServlet技术栈构建蛋糕店在线售卖系统涵盖用户注册登录、商品分类展示、热销/新品推荐、购物车管理、订单结算等核心电商功能代码经实际运行验证答辩平均分达96分适合作为课设、毕设原型或二次开发基础。压缩包共290个文件含58个JSP页面实现前后端交互、55个Java类含Servlet与DAO逻辑、20个XML配置文件如web.xml与数据库连接、14个JS脚本及12个CSS样式文件含Bootstrap与Layer UI组件整体大小18.95MB。目前已有182人学习下载资源附带详细文档说明与README指引结构清晰、注释充分便于理解MVC分层设计思想与传统JavaWeb开发全流程。1. 这不是“又一个JSP作业”一个能真实跑通、改得动、部署进Tomcat的蛋糕店售卖系统长什么样你搜“javaweb大作业 蛋糕店”时大概率会撞上一堆压缩包——解压后是乱码的docx文档、没配数据库的web.xml、index.jsp里写死的“欢迎光临XX蛋糕店”、连MySQL驱动jar都没放进去的lib目录。这不是教学案例是交付陷阱。真正能落地的《基于JSP和Servlet的JavaWeb蛋糕店售卖网站》必须同时满足三件事**第一HTTP请求能完整走通Servlet→JSP→MySQL闭环比如用户登录后跳转个人中心页订单提交后库存实时扣减第二Bootstrap 4/5的栅格和组件不是贴图式堆砌而是用.col-md-8控制商品列表宽度、用>dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdjavax.servlet.jsp/groupId artifactIdjsp-api/artifactId version2.2/version scopeprovided/scope /dependency提示scopeprovided/scope表示这些jar由Tomcat容器提供打包时不能打进WAR包否则会冲突。IDEA里右键项目 →Add Framework Support → Web Application (2.5)别选3.0或4.0——JSP 2.2对应Servlet 3.0但Tomcat 9默认支持Servlet 4.0向下兼容没问题强行选高版本反而触发web.xml解析失败。2.2 数据库初始化建库、建表、插测试数据一步到位别信“文档里说‘自行创建数据库’”这种话。直接执行以下SQL保存为cake_shop_init.sql它创建cake_shop库并建好5张核心表users用户、products蛋糕商品、orders订单主表、order_items订单明细、cart购物车临时表CREATE DATABASE IF NOT EXISTS cake_shop CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE cake_shop; CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, -- 存BCrypt加密后的密文 email VARCHAR(100), phone VARCHAR(20), role ENUM(user,admin) DEFAULT user, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE products ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, description TEXT, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, image_path VARCHAR(255), -- 存相对路径如 /images/cake1.jpg category VARCHAR(50), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 插入3款测试蛋糕图片路径已预设后续放webapp/images下即可 INSERT INTO products (name, description, price, stock, image_path, category) VALUES (经典巧克力蛋糕, 比利时黑巧法式奶油三层夹心, 128.00, 15, /images/chocolate.jpg, 生日蛋糕), (草莓慕斯蛋糕, 新鲜草莓果肉进口马斯卡彭, 98.00, 22, /images/strawberry.jpg, 节日蛋糕), (定制翻糖蛋糕, 支持照片打印、文字雕刻、尺寸定制, 298.00, 8, /images/fondant.jpg, 定制蛋糕);参数说明utf8mb4_unicode_ci是MySQL 8默认排序规则支持emojiprice用DECIMAL(10,2)避免浮点数精度丢失比如19.99存成19.990000000000002image_path存相对路径而非绝对URLJSP里用img src${pageContext.request.contextPath}${product.imagePath}安全拼接。2.3 IDEA配置Tomcat绕过“Artifact is not configured”红字警告很多人卡在“Run Configuration里找不到Artifact”。正确路径是File → Project Structure → Artifacts → 点击“” → Web Application: Archive → 给WAR包起名cake-shop.war在Output Layout里右键WEB-INF/lib→ Add Library → 选中mysql-connector-java-8.0.33.jar和jstl-1.2.jarJSTL用于JSP标签必须手动加回到Run Configurations → Templates → Tomcat Server → Local → Deployment → 点“” → Artifact → 选刚建的cake-shop:war exploded关键一步在Server选项卡里把On Update action和On frame deactivation都设为Update classes and resources别选Redeploy——后者会清空session导致登录后跳转失败。3. Servlet层用MVC模式拆解业务逻辑拒绝JSP里写Java代码3.1 登录Servlet如何用HttpSession存用户信息并跳转LoginServlet.java不是简单验证账号密码它要完成三件事查数据库、存session、重定向。重点看doPost方法WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); // 1. 调用DAO查用户此处简化实际应调UserDao User user UserDao.findByUsername(username); if (user ! null BCrypt.checkpw(password, user.getPassword())) { // 2. 存sessionkey必须是字符串value可以是任意对象 req.getSession().setAttribute(currentUser, user); req.getSession().setMaxInactiveInterval(1800); // 30分钟无操作自动失效 // 3. 重定向到首页避免F5刷新重复提交 resp.sendRedirect(req.getContextPath() /index.jsp); } else { req.setAttribute(errorMsg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }逻辑说明req.getSession().setAttribute(currentUser, user)把用户对象存进session后续所有Servlet/JSP都能通过session.getAttribute(currentUser)取resp.sendRedirect()是客户端重定向URL会变且是GET请求而forward()是服务器端转发URL不变适合带错误信息回传。参数说明setMaxInactiveInterval(1800)单位是秒防止用户挂机太久占用内存BCrypt.checkpw()比明文比对安全得多需引入org.mindrot:jbcrypt:0.4依赖。3.2 商品列表Servlet分页查询分类筛选的硬核写法ProductListServlet.java要处理两种请求普通访问查全部和带?category生日蛋糕的筛选。核心是用LIMIT offset, size做分页WebServlet(/products) public class ProductListServlet extends HttpServlet { private static final int PAGE_SIZE 8; // 每页8个商品 Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String category req.getParameter(category); String pageStr req.getParameter(page); int currentPage (pageStr null || pageStr.isEmpty()) ? 1 : Integer.parseInt(pageStr); int offset (currentPage - 1) * PAGE_SIZE; // 构建动态SQL生产环境建议用MyBatis此处手写 String sql SELECT * FROM products WHERE 11; ListObject params new ArrayList(); if (category ! null !category.trim().isEmpty()) { sql AND category ?; params.add(category); } sql ORDER BY created_at DESC LIMIT ?, ?; params.add(offset); params.add(PAGE_SIZE); ListProduct productList JdbcUtils.query(sql, Product.class, params.toArray()); // 查总记录数用于计算总页数 String countSql SELECT COUNT(*) FROM products WHERE 11; if (category ! null !category.trim().isEmpty()) { countSql AND category ?; } long total (long) JdbcUtils.querySingle(countSql, params.toArray()); // 计算分页参数 int totalPages (int) Math.ceil((double) total / PAGE_SIZE); req.setAttribute(products, productList); req.setAttribute(currentPage, currentPage); req.setAttribute(totalPages, totalPages); req.setAttribute(category, category); req.getRequestDispatcher(/products.jsp).forward(req, resp); } }参数说明PAGE_SIZE8是经验值手机端建议6PC端可12offset (currentPage - 1) * PAGE_SIZE是分页核心公式JdbcUtils是自封装的JDBC工具类内部用PreparedStatement防SQL注入Math.ceil()确保17条数据时totalPages3881。注意req.setAttribute()传给JSP的数据在JSP里用${products}取不是requestScope.products。3.3 订单提交Servlet事务控制与库存原子性扣减OrderSubmitServlet.java是整个系统最脆弱的环节——用户下单瞬间必须保证“订单生成”和“库存扣减”要么全成功要么全失败。用MySQL的START TRANSACTION和ROLLBACKWebServlet(/order/submit) public class OrderSubmitServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session req.getSession(); User currentUser (User) session.getAttribute(currentUser); if (currentUser null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } Connection conn null; try { conn JdbcUtils.getConnection(); conn.setAutoCommit(false); // 关键关闭自动提交 // 1. 插入订单主表 String insertOrderSql INSERT INTO orders (user_id, total_amount, status, created_at) VALUES (?, ?, unpaid, NOW()); PreparedStatement psOrder conn.prepareStatement(insertOrderSql, Statement.RETURN_GENERATED_KEYS); psOrder.setInt(1, currentUser.getId()); psOrder.setBigDecimal(2, new BigDecimal(req.getParameter(totalAmount))); psOrder.executeUpdate(); // 2. 获取刚生成的订单ID ResultSet rs psOrder.getGeneratedKeys(); rs.next(); int orderId rs.getInt(1); // 3. 插入订单明细并扣减库存同一事务内 String[] productIds req.getParameterValues(productId); String[] quantities req.getParameterValues(quantity); for (int i 0; i productIds.length; i) { int pid Integer.parseInt(productIds[i]); int qty Integer.parseInt(quantities[i]); // 先查库存是否足够SELECT FOR UPDATE锁住该行 String checkStockSql SELECT stock FROM products WHERE id ? FOR UPDATE; PreparedStatement psCheck conn.prepareStatement(checkStockSql); psCheck.setInt(1, pid); ResultSet rsStock psCheck.executeQuery(); rsStock.next(); int currentStock rsStock.getInt(stock); if (currentStock qty) { throw new RuntimeException(商品ID pid 库存不足); } // 扣库存 插明细 String updateStockSql UPDATE products SET stock stock - ? WHERE id ?; PreparedStatement psUpdate conn.prepareStatement(updateStockSql); psUpdate.setInt(1, qty); psUpdate.setInt(2, pid); psUpdate.executeUpdate(); String insertItemSql INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?); PreparedStatement psItem conn.prepareStatement(insertItemSql); psItem.setInt(1, orderId); psItem.setInt(2, pid); psItem.setInt(3, qty); psItem.setBigDecimal(4, new BigDecimal(req.getParameter(price pid))); psItem.executeUpdate(); } conn.commit(); // 全部成功才提交 req.setAttribute(successMsg, 订单提交成功请前往支付); req.getRequestDispatcher(/order_success.jsp).forward(req, resp); } catch (Exception e) { if (conn ! null) { try { conn.rollback(); // 任一环节失败就回滚 } catch (SQLException ex) { ex.printStackTrace(); } } req.setAttribute(errorMsg, 订单提交失败 e.getMessage()); req.getRequestDispatcher(/cart.jsp).forward(req, resp); } finally { JdbcUtils.close(conn); } } }逻辑说明conn.setAutoCommit(false)开启手动事务SELECT ... FOR UPDATE在查库存时加行锁防止并发下单超卖psOrder.getGeneratedKeys()获取自增主键避免两次查询conn.rollback()在catch块里强制回滚保证数据一致性。参数说明FOR UPDATE是InnoDB行锁的关键字没它的话高并发下库存会扣成负数Statement.RETURN_GENERATED_KEYS告诉PreparedStatement要返回自增ID。4. JSPBootstrap层让页面不只是“能看”而是“能调、能适配、能debug”4.1 首页轮播图用Bootstrap 5 Carousel实现图片坐标精准定位很多同学把轮播图写成div classcarousel-item activeimg src...就完事结果图片拉伸变形、文字盖不住、移动端点击失效。正确做法是!-- index.jsp -- div idmainCarousel classcarousel slide>!-- products.jsp -- div classrow g-4 !-- g-4是列间距比默认g-3更宽松 -- c:forEach items${products} varp div classcol-md-3 col-sm-6 div classcard h-100 border-0 shadow-sm img src${pageContext.request.contextPath}${p.imagePath} classcard-img-top rounded-top alt${p.name} styleheight: 200px; object-fit: cover; !-- object-fit: cover保持比例裁剪 -- div classcard-body p-3 h5 classcard-title fs-6 mb-1${p.name}/h5 p classcard-text text-muted small mb-2${p.description}/p div classd-flex justify-content-between align-items-center span classh5 fw-bold text-primary¥${p.price}/span a href${pageContext.request.contextPath}/cart/add?pid${p.id}qty1 classbtn btn-outline-secondary btn-sm加入购物车/a /div /div /div /div /c:forEach /div/* css/custom.css */ .card-img-top { transition: transform 0.3s ease; } .card:hover .card-img-top { transform: scale(1.03); /* 鼠标悬停轻微放大 */ } .card-body { padding: 0.75rem 0.75rem 0.5rem; /* 压缩上下padding */ } .card-title { margin-bottom: 0.25rem; /* 缩小标题下边距 */ }逻辑说明object-fit: cover让图片等比缩放并填满200px高度不拉伸d-flex justify-content-between align-items-center让价格和按钮在同一行两端对齐fs-6是Bootstrap 5的字体大小工具类相当于font-size: 0.875rem。参数说明g-4是Bootstrap 5的间距类值越大列间距越宽h-100让卡片高度一致避免网格错位rounded-top只圆角顶部符合蛋糕图视觉习惯。4.3 表单验证用Bootstrap表单控件原生HTML5约束实现无JS校验register.jsp的注册表单不用写一行JavaScript靠HTML5属性和Bootstrap样式联动form action${pageContext.request.contextPath}/register methodpost novalidate div classmb-3 label forusername classform-label用户名/label input typetext classform-control is-invalid idusername nameusername required minlength3 maxlength20 pattern[a-zA-Z0-9_] title只能包含字母、数字、下划线 div classinvalid-feedback用户名至少3位只能用字母、数字、下划线/div /div div classmb-3 label foremail classform-label邮箱/label input typeemail classform-control idemail nameemail required div classvalid-feedback邮箱格式正确/div /div div classmb-3 label forpassword classform-label密码/label input typepassword classform-control idpassword namepassword required minlength6 div classinvalid-feedback密码至少6位/div /div button typesubmit classbtn btn-primary w-100立即注册/button /form参数说明required必填minlength6最小长度pattern[a-zA-Z0-9_]正则约束title是验证失败时的提示文本is-invalid类触发Bootstrap红色边框和invalid-feedback显示。注意novalidate禁用浏览器默认弹窗用Bootstrap样式反馈更美观valid-feedback在输入合法时显示绿色提示提升体验。5. 避坑指南那些让JavaWeb项目在答辩前夜崩溃的5个致命细节5.1 现象启动Tomcat后访问http://localhost:8080/cake-shop显示404但http://localhost:8080/cake-shop/index.jsp能打开原因web.xml里没配置welcome-file-list或者IDEA的Artifact输出路径没指向webapp目录。Tomcat默认找index.html或index.jsp但如果你的项目结构是src/main/webapp/index.jsp而Artifact里把webapp目录漏掉了就会404。解决检查Project Structure → Artifacts → Output Layout确认webapp文件夹已作为Available Elements添加到WEB-INF同级再检查web.xml是否有welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list5.2 现象登录成功后跳转到index.jsp但页面顶部不显示“欢迎张三”session.getAttribute(currentUser)返回null原因req.getSession().setAttribute()存的是User对象但JSP里用% session.getAttribute(currentUser) %直接输出对象会打印com.example.User1a2b3c这种哈希值而非用户名。更常见的是web.xml里session-config设置了tracking-modeCOOKIE/tracking-mode但浏览器禁用了Cookie。解决JSP里必须用EL表达式${currentUser.username}同时检查浏览器是否开启CookieChrome地址栏右侧点锁图标 → Site settings → Cookies → Allow若仍不行在web.xml里加session-config cookie-config http-onlytrue/http-only securefalse/secure !-- 开发环境设false生产环境用https时设true -- /cookie-config /session-config5.3 现象Bootstrap CSS不生效按钮还是浏览器默认样式Chrome开发者工具里看到bootstrap.min.css状态是(canceled)原因link标签的href路径错了。常见错误是写成link hrefcss/bootstrap.min.css相对路径但实际文件在webapp/css/bootstrap.min.css而当前JSP在webapp/products.jsp相对路径会去找webapp/products/css/bootstrap.min.css。解决一律用c:url value/css/bootstrap.min.css/或% request.getContextPath() %/css/bootstrap.min.css。例如link hrefc:url value/css/bootstrap.min.css/ relstylesheet link hrefc:url value/css/custom.css/ relstylesheet5.4 现象MySQL连接报Communications link failure但localhost:3306能telnet通原因MySQL 8默认认证插件是caching_sha2_password而mysql-connector-java 5.x不支持。你用的jar包版本是5.1.47但项目要求8.0.33。解决确认pom.xml里mysql-connector-java版本是8.0.33如果仍不行在MySQL命令行执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY your_password; FLUSH PRIVILEGES;5.5 现象购物车添加商品后刷新页面数量归零session.getAttribute(cartItems)每次都是空集合原因购物车逻辑写在CartServlet里但没把cartItems存到session而是存在局部变量ListItem里。或者用了request.getSession(false)当session不存在时返回null而非创建新session。解决CartServlet里必须用req.getSession(true)true表示不存在则创建存购物车用HttpSession session req.getSession(true); ListCartItem cartItems (ListCartItem) session.getAttribute(cartItems); if (cartItems null) { cartItems new ArrayList(); session.setAttribute(cartItems, cartItems); } // 后续add/remove操作都在cartItems上6. 最后一公里用Chrome DevTools真机调试Tomcat日志定位把“玄学问题”变成可复现的bug6.1 用Network面板抓取404请求5秒定位静态资源路径错误当你发现某张蛋糕图片不显示别急着改JSP里的img src。打开Chrome DevToolsF12→ Network标签 → 刷新页面 → 在Filter里输入images/→ 找到状态码为404的请求比如/cake-shop/images/chocolate.jpg。右键Copy → Paste到浏览器地址栏看是否返回The requested resource [/cake-shop/images/chocolate.jpg] is not available。如果是说明图片根本没放在webapp/images/目录下。我的习惯是所有静态资源路径统一用c:url value/images/xxx.jpg/然后在IDEA里右键webapp→ Show in Explorer手动确认文件存在。曾经有次因为Git忽略规则把.jpg文件过滤了本地有图提交后服务器上就是404——Network面板一眼揪出。6.2 用Console面板捕获JSP EL表达式错误避免“页面空白”却不知错在哪JSP里${product.price}如果product为null页面不会报错只会显示空白。这时打开Console面板会看到Uncaught ReferenceError: product is not defined如果用了JS或EL Expression failedTomcat日志里。更狠的招在web.xml里加开发模式配置让EL错误直接抛异常jsp-config jsp-property-group url-pattern*.jsp/url-pattern el-ignoredfalse/el-ignored scripting-invalidfalse/scripting-invalid /jsp-property-group /jsp-config这样${null.price}会直接500报错而不是静默失败。我在products.jsp里曾把${p.price}错写成${product.price}Console里立刻报p is not defined比翻100行JSP找变量名快10倍。6.3 用Tomcat logs/catalina.out实时盯住SQL执行排查“库存没扣减”类问题订单提交后库存没变别猜DAO代码。直接去tomcat/logs/catalina.out尾部追日志tail -f /path/to/tomcat/logs/catalina.out | grep UPDATE products如果看到UPDATE products SET stock stock - 1 WHERE id 1说明SQL执行了如果什么都没有说明事务回滚了或没走到那行代码。我遇到过一次conn.rollback()没写在finally块里异常时连接没关下次请求复用旧连接导致autoCommittrue库存就扣不掉——日志里UPDATE语句消失就是最直接的证据。6.4 用Elements面板实时修改CSS验证“字体大小适配1440px宽屏”是否成立热搜词里有“宽1440px,高810px”这不是摆设。在Chrome里按CtrlShiftM进响应式模式 → 设置Custom尺寸1440×810 → 看轮播图文字是否居中、商品卡片是否换行、按钮是否被截断。如果文字挤在一起选中元素 → 右侧Styles面板 → 找到font-size→ 直接双击修改为1.25rem即20px实时看效果。记住所有字体相关CSS必须用rem根元素html { font-size: 16px; }这样1rem16px1.25rem20px适配性最好。我调h3标题时发现display-6在1440px下太大就加了媒体查询media (min-width: 1440px) { .carousel-caption h3 { font-size: 1.5rem; /* 24px */ } }最后说一句实在的这个蛋糕店项目我带过3届学生从没人能在3天内从零跑通。大多数人卡在数据库连接、Bootstrap路径、session丢失这三关。但只要你按这篇的顺序一条命令一条命令敲一个坑一个坑填最后在Tomcat里看到“订单提交成功”弹窗那种踏实感比任何框架教程都强。希望帮到你。本文还有配套的精品资源点击获取
返回列表