尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
JavaWeb花店管理系统实战:从Servlet+JSP到完整项目源码
简介这套面向大学生毕设的JavaWeb花店管理系统整合Struts2、Spring与Hibernate框架提供带GUI界面的前后端完整源码与数据库脚本适合作为课程设计、毕业设计或JavaWeb学习的实战参考。系统已实现前台商品展示、分类搜索、购物车、下单购买以及后台经理登录、订单处理、顾客与掌柜管理、鲜花增删改和图片上传等完整功能数据库设计包含顾客、经理、鲜花、类目、订单五张核心表业务逻辑覆盖典型电商场景。压缩包共75个文件包含20个Java源码、20个class文件、JSP前端页面、Spring与Struts配置、SQL脚本及图片素材并附有db.properties样例、编码说明和readme文档方便在EclipseTomcatMySQL环境中快速导入部署。资源包仅219KB已有215人学习下载其分层结构清晰从Model、DAO、Service到Action与页面逐层递进对理解SSH框架整合和MVC思想很有帮助可直接作为二次开发或答辩演示基础。1. 用JavaWeb做花店管理系统这套前后端源码数据库到底要交什么大学生毕设里最常见的两个JavaWeb题目一个是图书管理另一个就是花店管理系统。这个题目的完整交付物通常写着基于JavaWeb带GUI界面前后端源码数据库业务上就是商品展示、会员登录、购物下单、后台管理这一条线技术上则是典型的JavaWeb项目前端页面在浏览器里展示后端用Servlet处理请求数据落在MySQL里。很多同学拿到题目时以为最难的是写代码其实最难的是搞清楚老师要的是什么。所谓带GUI界面在JavaWeb语境下通常指网页界面不需要你额外去写Swing桌面程序。这套方案适合课程设计、毕业设计以及想从练习题过渡到完整项目的从业者下面按我实际带项目时的顺序把技术选型、建库、骨架、业务闭环和踩坑一条条讲透。2. JavaWeb花店系统的技术选型为什么这个组合是毕设稳妥牌2.1 Servlet JSP MySQL 的老三样什么时候该用做JavaWeb毕设最常见也最稳的路线是Servlet JSP MySQL Tomcat而不是一上来就上Spring Boot。不是Spring Boot不好而是大部分课程设计/毕业设计的要求里基于JavaWeb通常等于B/S架构 服务端渲染用Servlet和JSP能把HTTP请求、Session、JDBC这些最基础的东西展示给答辩老师看Spring Boot把Tomcat和Bean管理都藏掉了学生反而讲不清原理被追问两句就容易翻车。另一种常见做法是SSMSpring SpringMVC MyBatis比ServletJSP稍微工业化一点但依赖注入、动态代理这些概念又成了新的讲解负担。我一般给学生的建议是如果任务书没写技术栈优先Servlet JSP如果任务书上写了SSM或Spring Boot那就按SSM走但每一层干了什么必须能画出来。选型时还要看学校用的JDK和Tomcat版本尽量用JDK 1.8 Tomcat 8/9 MySQL 5.7/8.0这一套网上能找到的javaweb项目完整案例mysql资源最多很多人也喜欢照着java课程设计案例源码去改但改之前一定先确认原项目的Servlet版本和包名不然导入IDEA后全是红叉。2.2 前端的GUI界面到底指什么浏览器网页 vs Swing桌面这里要专门说清楚带GUI界面这个词。在JavaWeb项目的任务书里GUI多半指浏览器渲染出来的网页界面也就是B/S架构的客户端部分页面可以是JSP在服务端渲染也可以是HTML AJAX Vue的富交互页面但访问方式都是打开浏览器输入URL而不是双击一个exe。只有少数老师题目里的GUI指的是Java Swing或JavaFX写的桌面窗体。区分方法很简单看任务书里有没有B/S架构网页端Web应用字样有就按Web做如果写的是CS架构桌面端才需要去想Swing JDBC MySQL的方案。也有学校要求网页端桌面管理端双端后者可以做成一个只做后台管理的Swing小工具共用同一个数据库作为加分项。从投入产出比看先保Web端桌面端有余力再加。2.3 花店业务的模块拆解把题目变成数据表拿到题目别急着建工程先把业务拆清楚。花店管理系统核心角色就两个普通用户买花和管理员管花。普通用户侧要做的功能注册登录、逛商品列表、看商品详情、加购物车、下单管理员侧要做的功能商品增删改查、订单状态处理、客户列表。在此基础上统计报表是可选的加分项。对应到数据库最少需要六张表用户表、分类表、花卉商品表、购物车表、订单表、订单明细表。订单和订单明细拆开是为了一个订单可以包含多种花也方便以后做退款单、售后单。所有表都用InnoDB引擎主键用自增int字符集统一utf8mb4排序规则utf8mb4_general_ci。这样设计的好处是每个模块都能对应上1到2张表答辩时讲我的业务怎么落到表上非常直观。我做过一次课程设计评审最扣分的不是功能少而是表之间主外键关系说不清。所以建表前建议把下面这个表格填一遍再动手业务模块涉及的页面核心表关键字段登录注册login.jsp, register.jspuserusername, password, role商品展示index.jsp, detail.jspcategory, flowername, price, stock, photo购物车cart.jspcartuser_id, flower_id, count下单confirm.jsp, order.jsporders, order_itemorder_no, total, status表设计好之后前后端的分工也就清楚了后端只负责把表数据变成Java对象前端只负责把对象渲染成网页。这一步做完项目的骨架基本有了。2.4 项目目录与运行环境一次配好少折腾三次环境建议固定为IDEA JDK 1.8 Tomcat 8.5/9 MySQL 5.7/8.0 Maven 3.6。不要用Tomcat 10因为Tomcat 10把javax.servlet包名改成了jakarta.servlet网上大部分Servlet教程都是javax开头代码直接搬过来会报ClassNotFoundException这个坑在idea运行javaweb项目配置时非常常见。目录结构按Maven的标准来src/main/java放包结构我习惯分成controller、service、dao、entity、util五层src/main/resources放数据库连接配置、日志配置src/main/webapp放JSP、CSS、JS、图片其中WEB-INF下面的页面不能通过URL直接访问只能由Servlet转发进去这样比把页面裸放在webapp根目录安全得多。在IDEA里跑起来的步骤是固定的Project Structure里添加Web模块把src/main/webapp配置成Web资源目录Artifacts选Exploded方式Fix出WEB-INF/lib再在Run Configurations里选Tomcat Server LocalDeployment里加上这个ArtifactApplication context填/flower_shop。这三个地方是新手最常见的翻车点任何一个没配对启动后都是404。注意本地运行优先选war exploded改JSP不用重新打包刷新页面就能看到效果如果用war每次改页面都要build一次。3. 从建库到跑通第一个页面花店系统的数据库与项目骨架3.1 创建数据库和六张核心表SQL可以直接抄下面这套SQL我按能直接运行的标准给出来数据库名flower_shop六张表全部utf8mb4。建库建表的时候注意MySQL 8.0默认字符集已经是utf8mb4但旧服务器上经常还是latin1所以每个表我都显式指定了CHARSET防止中文乱码。CREATE DATABASE IF NOT EXISTS flower_shop DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE flower_shop; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT 密码(MD5), phone VARCHAR(20), role INT DEFAULT 1 COMMENT 1-普通用户 2-管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB CHARSETutf8mb4; CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL ) ENGINEInnoDB CHARSETutf8mb4; CREATE TABLE flower ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, photo VARCHAR(200) COMMENT 图片相对路径, description TEXT, status INT DEFAULT 1 COMMENT 1上架 0下架, KEY idx_category (category_id), CONSTRAINT fk_flower_category FOREIGN KEY (category_id) REFERENCES category(id) ) ENGINEInnoDB CHARSETutf8mb4; CREATE TABLE cart ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, flower_id INT NOT NULL, count INT NOT NULL DEFAULT 1, UNIQUE KEY uk_user_flower (user_id, flower_id), CONSTRAINT fk_cart_user FOREIGN KEY (user_id) REFERENCES user(id), CONSTRAINT fk_cart_flower FOREIGN KEY (flower_id) REFERENCES flower(id) ) ENGINEInnoDB CHARSETutf8mb4; CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(40) NOT NULL UNIQUE COMMENT 订单号, user_id INT NOT NULL, total DECIMAL(10,2) NOT NULL, status INT DEFAULT 0 COMMENT 0待支付 1已支付 2已发货 3完成 4取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_order_user (user_id), CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES user(id) ) ENGINEInnoDB CHARSETutf8mb4; CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, flower_id INT NOT NULL, price DECIMAL(10,2) NOT NULL COMMENT 下单时快照价格, count INT NOT NULL, CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES orders(id), CONSTRAINT fk_item_flower FOREIGN KEY (flower_id) REFERENCES flower(id) ) ENGINEInnoDB CHARSETutf8mb4;这段SQL里有几个设计点值得说明。一是cart表用了联合唯一键uk_user_flower同一个用户把同一朵花加两次购物车直接在数据库这一层就挡掉了业务代码不用再去查重。二是有外键约束课程设计中基本都要求有外键所以我没有省略实际企业项目可能为了扩展性去掉外键但毕设里保留外键更容易通过检查。三是order_item保留了price字段这是下单时快照价格——商品价格后来涨价订单里仍然显示用户下单时的价格这个细节讲出来很加分。3.2 Maven项目骨架与pom.xml配置用IDEA新建Maven工程选maven-archetype-webapp或者直接建普通Maven工程再手动补webapp目录。项目中需要的最小依赖是Servlet API、JSP API、JSTL、MySQL驱动、连接池和JSON工具。Servlet/JSP依赖scope要标provided因为Tomcat自己带防止打包时把servlet-api塞进war包导致冲突。project modelVersion4.0.0/modelVersion groupIdcom.flowershop/groupId artifactIdflower-shop/artifactId version1.0-SNAPSHOT/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 version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.30/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.15/version /dependency dependency groupIdcom.alibaba/groupId artifactIdfastjson/artifactId version1.2.83/version /dependency /dependencies /project这里重点说两个参数。mysql-connector-java用8.0.x连接MySQL 5.7和8.0都行但驱动类名要写com.mysql.cj.jdbc.Driver而不是旧的com.mysql.jdbc.Driver。druid版本不要选太老的1.2.x修复了多个连接池在多线程下的bug。jstl的1.2版本同时包含standard实现JSP页面里直接taglib引入就行不用再额外引standard.jar。3.3 数据库连接池用Druid替掉DriverManager很多JavaWeb教程教的是Class.forName加DriverManager.getConnection每次请求都新建连接这个写法能在单机几万访问量下把数据库拖垮也不是企业中真实使用的做法。毕设里虽然访问量不大但用连接池是加分项。我用的是阿里Druid配置写在src/main/resources/druid.properties里。driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/flower_shop?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai usernameroot password123456 initialSize5 maxActive20 maxWait5000url里的三组参数都是有讲究的characterEncodingutf8保证中文正常读写useSSLfalse关掉SSL校验避免MySQL 8.0下出现SSL握手报错serverTimezoneAsia/Shanghai解决凌晨报错CONVERT_TZ和The server time zone value is unrecognized。这三项不写连接池启动时就会给你颜色看。对应的工具类写法package com.flowershop.util; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class DbUtil { private static DataSource dataSource; static { try (InputStream in DbUtil.class.getClassLoader() .getResourceAsStream(druid.properties)) { Properties props new Properties(); props.load(in); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(数据库连接池初始化失败: e.getMessage()); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }这段静态代码块在类加载时执行一次把连接池创建好之后每次getConnection都是从这个池子里借连接用完还回池子而不是关掉。注意这里我没有写关闭连接的代码调用方的习惯是finally里conn.close()——在连接池模式下这个close是归还连接而非断开连接逻辑上完全不同。写毕设时这个点经常被老师问答清楚了很加分。3.4 用IDEA配置Tomcat并跑通第一个JSP到这一步工程骨架已经能启动。IDEA里的配置步骤我按菜单路径写一遍先在Run/Debug Configurations里点加号选Tomcat Server - Local在Server选项卡的Application Server里选本地Tomcat目录再切到Deployment选项卡点加号选Artifact把flower-shop:war exploded加进去Application context填/flower_shop。启动后浏览器访问http://localhost:8080/flower_shop/index.jsp。这里有一个新手必踩的细节IDEA默认会让你选war还是war exploded毕设本地运行选exploded因为它解压到target目录改JSP不用重新打包刷新页面就能看到效果如果用war每次改页面都要build一次。Application context的名字最好和项目名对齐不然你会在flower_shop和flower-shop之间反复改链接。启动时如果端口被占换个8081端口再启动是最快的解法不用去杀进程。第一个JSP先别写业务放一个最简单的登录页能访问到就说明Tomcat、Artifact、Maven依赖三层都通了。后面的功能全在这个基础上长出来。4. 把花店业务做进页面登录、商品展示与下单的完整闭环4.1 实体类与DAO层数据库字段和Java对象对齐先写实体类字段和表结构一一对应。注意数据库字段叫create_timeJava属性名用createTimeJDBC映射时用别名把下划线转成驼峰比反射注解省事得多。package com.flowershop.entity; import java.math.BigDecimal; public class Flower { private Integer id; private Integer categoryId; private String name; private BigDecimal price; private Integer stock; private String photo; private String description; private Integer status; // getter/setter 省略IDEA快捷键生成 }花这个实体的属性类型和MySQL字段类型对应关系为int对应IntegerDECIMAL对应BigDecimalvarchar对应String这是JDBC类型映射的常规对应写错了会在getXxx时抛NumberFormatException。DAO层我习惯每个实体一个类只放裸的SQL操作。FlowerDao的查询方法如下package com.flowershop.dao; import com.flowershop.entity.Flower; import com.flowershop.util.DbUtil; import java.sql.*; import java.util.ArrayList; import java.util.List; public class FlowerDao { public ListFlower findHotFlowers(int limit) { String sql SELECT id, category_id, name, price, stock, photo, description, status FROM flower WHERE status 1 ORDER BY id DESC LIMIT ?; ListFlower list new ArrayList(); try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, limit); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Flower f new Flower(); f.setId(rs.getInt(id)); f.setCategoryId(rs.getInt(category_id)); f.setName(rs.getString(name)); f.setPrice(rs.getBigDecimal(price)); f.setStock(rs.getInt(stock)); f.setPhoto(rs.getString(photo)); f.setDescription(rs.getString(description)); f.setStatus(rs.getInt(status)); list.add(f); } } } catch (SQLException e) { throw new RuntimeException(查询商品失败, e); } return list; } }这里有两个容易踩的点。第一PreparedStatement的预编译SQL里占位符不能出现在LIMIT后面直接拼数字的地方MySQL的JDBC驱动会在LIMIT ?上报错所以limit我直接在SQL里拼成了常量不涉及用户输入就没有注入风险。第二try-with-resources自动关了Connection在连接池模式下就是自动归还你不用再写finally块。从ResultSet取字段时getInt、getBigDecimal这些方法按数据库真实类型来选不要统一getString再转避免精度丢失。4.2 登录功能Session 过滤器把谁在用系统管起来登录是整个系统里第一个跨页面共享状态的功能。逻辑是登录页提交用户名密码LoginServlet查user表校验通过把user对象塞进Session然后重定向到首页校验失败回到login.jsp并带一个error提示。密码存的是MD5不要明文入库。package com.flowershop.controller; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; import java.security.MessageDigest; 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); String md5 md5(password); User user new UserDao().findByUsernameAndPassword(username, md5); if (user null) { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } else { HttpSession session req.getSession(); session.setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /index); } } private String md5(String input) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(input.getBytes(UTF-8)); StringBuilder sb new StringBuilder(); for (byte b : digest) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (Exception e) { throw new RuntimeException(e); } } }代码里username和password都从request.getParameter拿这是POST表单的参数读法GET请求是另一套。注意校验失败用的是forward而不是sendRedirect转发可以携带request作用域里的error属性到JSP重定向会丢掉这个属性。登录成功后用重定向而不是转发是为了防止用户按F5刷新时表单被重复提交。守卫页面用一个Filter统一做只有Session里有loginUser才放行package com.flowershop.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebFilter(/admin/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(false); if (session ! null session.getAttribute(loginUser) ! null) { chain.doFilter(request, response); } else { resp.sendRedirect(req.getContextPath() /login.jsp); } } }Filter拦截的是/admin/*路径所以后台管理的Servlet URL都要带/admin前缀。这里有一个细节req.getSession(false)不会自动创建Session而直接getSession()会在每个未登录请求上创建空Session增加无效内存占用。在Filter里统一用false是这个写法的关键。4.3 商品列表页Servlet查数据JSP负责渲染商品列表页是花店的门面。流程是浏览器访问/indexIndexServlet从FlowerDao查出上架商品列表放到request作用域转发到index.jspJSP用JSTL标签把集合渲染成卡片。只贴核心部分。WebServlet(/index) public class IndexServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ListFlower flowers new FlowerDao().findHotFlowers(12); req.setAttribute(flowers, flowers); req.getRequestDispatcher(/index.jsp).forward(req, resp); } }% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html head title花店首页/title /head body div classproduct-grid c:forEach varflower items${flowers} div classcard img src${pageContext.request.contextPath}/${flower.photo} alt${flower.name} h3${flower.name}/h3 p价格¥${flower.price}/p a href${pageContext.request.contextPath}/flower/detail?id${flower.id}查看详情/a /div /c:forEach /div /body /html这里JSP页面里的${}是EL表达式从request作用域里取flowers这个List再遍历相当于Java里的for (Flower f : flowers)。图片路径故意加了一个pageContext.request.contextPath前缀这样部署到Tomcat后上下文改名图片也不会挂这是很多人漏掉的一点。如果页面出现空白先看Tomcat控制台有没有ClassNotFoundException: jakarta.servlet.jsp.jstl.core.LoopSupport有就说明JSTL jar版本和JSP版本不匹配。4.4 购物车与下单用事务保证库存不超卖购物车和下单是整个系统里事务最集中的地方。加购物车只是insert一条cart记录真正考验的是下单这个动作要同时做四件事——创建主订单、批量插入订单明细、扣减库存、清空购物车任何一个失败都不能让数据库停留在中间状态。所以下单的代码必须包在同一个数据库事务里。WebServlet(/order/create) public class CreateOrderServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session req.getSession(); User user (User) session.getAttribute(loginUser); if (user null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } Connection conn null; try { conn DbUtil.getConnection(); conn.setAutoCommit(false); OrderDao orderDao new OrderDao(); String orderNo FS System.currentTimeMillis(); double total 0; ListCartItem cartList new CartDao().findByUserId(conn, user.getId()); Order order orderDao.insertOrder(conn, user.getId(), orderNo); // 返回生成的主键 for (CartItem item : cartList) { orderDao.insertOrderItem(conn, order.getId(), item.getFlowerId(), item.getPrice(), item.getCount()); new FlowerDao().deductStock(conn, item.getFlowerId(), item.getCount()); total item.getPrice() * item.getCount(); } orderDao.updateTotal(conn, order.getId(), total); new CartDao().clearCart(conn, user.getId()); conn.commit(); resp.sendRedirect(req.getContextPath() /order/list); } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } throw new ServletException(创建订单失败, e); } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }这段代码的关键在conn.setAutoCommit(false)到conn.commit()之间。所有DAO方法都改成带Connection参数的重载让它们在这条事务连接上执行而不是各自去连接池里偷新连接——这是很多初次写事务的人最容易翻车的地方。如果DAO内部自己又getConnection事务就名存实亡了。finally里的setAutoCommit(true)是为了把连接还原成默认状态再还给连接池不还原的话下一个人从池子里拿到的连接还开着事务。4.5 后台管理商品增删改查与图片上传管理员模块放在/admin路径下被Filter拦着没登录无法访问。商品管理是花店后台最核心的部分功能上就是增删改查区别在于新增和编辑时涉及图片上传以及编辑时不能把status字段覆盖掉。WebServlet(/admin/flower/update) MultipartConfig(maxFileSize 5 * 1024 * 1024, maxRequestSize 10 * 1024 * 1024) public class FlowerUpdateServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); int id Integer.parseInt(req.getParameter(id)); String name req.getParameter(name); BigDecimal price new BigDecimal(req.getParameter(price)); int stock Integer.parseInt(req.getParameter(stock)); int status Integer.parseInt(req.getParameter(status)); Part part req.getPart(photo); String photoPath req.getParameter(oldPhoto); if (part ! null part.getSize() 0) { String fileName System.currentTimeMillis() _ Paths.get(part.getSubmittedFileName()).getFileName().toString(); String uploadDir req.getServletContext().getRealPath(/upload); part.write(uploadDir File.separator fileName); photoPath upload/ fileName; } Flower flower new Flower(); flower.setId(id); flower.setName(name); flower.setPrice(price); flower.setStock(stock); flower.setStatus(status); flower.setPhoto(photoPath); new FlowerDao().update(flower); resp.sendRedirect(req.getContextPath() /admin/flower/list); } }MultipartConfig注解的三个参数都可以调整maxFileSize限制单张图片5MBmaxRequestSize限制整个请求10MB超过会抛IllegalStateException所以前端表单也要做个图片大小检查两边配合才不会出现用户传了半天最后失败。取文件名时我用了Paths.get(part.getSubmittedFileName()).getFileName().toString()是为了干掉IE/Chrome传全路径文件名后暴露本机目录的问题。图片落盘的目录是getServletContext().getRealPath(/upload)这是IDEA里tomcat跑起来后的真实目录不是工程源码目录这点在答辩前记得打开目录给老师看视觉效果很直观。5. JavaWeb花店系统避坑指南从404到乱码的5个高频翻车现场5.1 现象Tomcat启动成功访问项目却一直404原因Tomcat运行起来了但你访问的URL路径和Deployment里配置的Application context对不上或者Artifact根本没有被部署到Tomcat。具体来说IDEA里Tomcat的Deployment选项卡如果没有加ArtifactTomcat就是一个空壳访问任何项目路径都是404加了Artifact但context填成/你又用/flower_shop去访问也会404。解决打开Run/Debug Configurations里的Tomcat Server配置Deployment选项卡下确认有flower-shop:war exploded条目并在下方Application context里统一填/flower_shop然后访问http://localhost:8080/flower_shop/。如果Tomcat控制台里印出了Deployment of web application directory... has finished说明部署成功。还有一种隐蔽情况web.xml或者注解WebServlet里的路径和JSP表单里的action少了一个斜杠也会变成404这个要靠看浏览器访问的URL逐段排查。我一般建议直接在浏览器F12的Network面板里找到那个404的请求把它的路径信息和项目里的Servlet映射逐步对齐。5.2 现象页面显示中文全部变成问号或乱码原因三个环节至少一个编码出问题JSP文件本身保存编码不是UTF-8JSP页面头里没设置contentType charsetJDBC连接数据库的url少了characterEncoding。花店系统里用户输入中文用户名、商品名含玫瑰康乃馨立刻就会触发。解决三处统一。第一处JSP第一行写成% page contentTypetext/html;charsetUTF-8 languagejava %并且IDEA里File Encoding设置为UTF-8第二处给自己写的CharacterEncodingFilter配置上拦截所有请求request.setCharacterEncoding(UTF-8)resp.setContentType(text/html;charsetUTF-8)第三处数据表的字符集是utf8mb4连接url带characterEncodingutf8。这三处任何一个断了乱码都会出现在不同的地方数据入库前乱码是请求解析问题入库后查出来乱码是连接编码问题。定位时先看数据库里的值库里就是乱的先查Filter和连接串库正常但页面乱就是JSP输出问题。5.3 现象启动时ClassNotFoundException: com.mysql.cj.jdbc.Driver或NoClassDefFoundError原因Maven依赖里写了mysql-connector-java但依赖没有进入最终运行的Artifact。IDEA里Maven依赖默认在compile阶段可见但Tomcat运行的是Artifact如果Artifact的Output Layout里没有把依赖打进WEB-INF/lib运行时类加载器找不到驱动类。另一种可能是驱动版本太老5.x驱动类名是com.mysql.jdbc.Driver你写成了cj那个。解决Project Structure - Artifacts选中你的war exploded点击右边列表里的Put into Output Root让依赖进入WEB-INF/lib如果已经在了确认驱动类名和版本一致。MySQL 8.0.30配com.mysql.cj.jdbc.DriverMySQL 5.7配5.1.49也能用但没必要。还有一种情况是多个Tomcat共用一个lib目录之前手动拷过旧版mysql jar要把Tomcat/lib里和项目WEB-INF/lib里的mysql驱动去掉一个避免版本冲突优先用项目内的。5.4 现象表单提交后Servlet收到请求但响应一直405原因doPost请求到了Servlet但Servlet只重写了doGet没重写doPost或者Servlet映射路径和表单action不一致请求被Tomcat兜底处理。Tomcat对没有对应do方法的请求返回405 Method Not Allowed这是Servlet规范规定的。解决首先看浏览器Network里显示的请求方法是POST还是GET如果form标签methodpostServlet里就补doPost方法。更省事的做法是让doGet里直接调doPost或者两者都写一行chain.doGet到doPost。然后是URL对齐WebServlet里的路径是/flower/detail表单的action就得是${pageContext.request.contextPath}/flower/detail注意千万别漏了contextPath。我自己见过太多人把这一行写成action/flower/detail部署后contextPath不为空请求于是打到了:8080/flower/detail上Tomcat找不到对应Context自然405或404。这是最典型的单机能跑、部署就挂的翻车点。5.5 现象Druid连接池初始化失败报GetConnectionTimeoutException或CommunicationsException原因通常是数据库没启动、连接数配太少、MySQL 8.0的时区/timezone配置缺失或者密码不对。其中密码不对的报错信息是Access denied for user时区问题是The server time zone value is unrecognized。连接池初始化失败的日志里通常会带cause一定要往下翻看到最底层那行Exception。解决第一步确认MySQL服务真的起来了navicat能连上端口3306没被改过。第二步检查druid.properties里的url参数MySQL 8.0必须带serverTimezoneAsia/Shanghai不然启动即失败修改后重启Tomcat即可。第三步把maxActive调大initialSize调小开发环境initialSize2、maxActive10就够maxWait5000毫秒避免池子不够时所有请求排队超时。这里还有一个开发期常见坑如果Tomcat开了热部署连接池跟着重启MySQL的旧连接没有及时释放会出现Too many connections这时到MySQL命令行执行SHOW PROCESSLIST;把Sleep状态的连接按id kill掉只是应急手段根因是连接池没配置空闲回收。6. 从能跑到能答辩三个升级方向和一个验证习惯到这里花店管理系统已经是一套完整可运行的JavaWeb项目。如果你还有时间我建议按下面的优先级加两个能力一是把下单扣库存做得更严谨比如扣库存前先用SELECT ... FOR UPDATE锁住商品行防止两个订单同时扣成负数——这是把事务讲出深度的最有效手段二是给后台订单管理页加一个简单的销量统计用ECharts画一张柱状图数据源就是order_item表按flower_id分组sum(count)前端用jQuery的$.ajax到后端Servlet取JSON这是前后端分离项目实战的最简形态答辩论题时非常讨喜。我自己带过的学生里最多的抱怨是代码能在IDEA里跑一打包部署就出问题。所以有一个验证习惯我希望你从今天开始养成每完成一个功能不要只在Tomcat里点一遍而是开浏览器F12盯住Network里那个请求看它的URL、请求方法、请求体、响应状态码。能说出index.jsp发起了POST到/flower/detailServlet返回了forward到detail.jsp这句话比代码跑通重要得多。这条习惯我用了十年每次半夜被线上问题叫醒靠的都是这个基本功。另外一个值得做的升级方向是把Servlet JSP这套代码迁成Spring Boot Vue前后端分离。迁移不是重写后端Entity、DAO的SQL逻辑可以原样保留把Servlet换成RestController把转发改成返回JSON前端JSP换成Vue页面走axios。迁移完你会发现原来每一步做了什么这时候你对JavaWeb的理解才算真正闭环。处理完记得自己走一遍完整购物流程注册→登录→浏览→加购→下单→后台发货确认每个环节的url都在心跳范围内。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

KMeans聚类算法与Python代码实践:从原理到sklearn调参避坑指南

KMeans聚类算法与Python代码实践:从原理到sklearn调参避坑指南

简介:面向机器学习初学者与数据分析人员的KMeans聚类算法Python实现与配套说明文档,解决无标签数据下自然分群的核心问题。内容覆盖算法原理、初始化与迭代更新流程、sklearn接口调用、数据标准化处理及可视化展示,并梳理了优缺点、适用场景&…

📅 2026/9/28 6:15:58
Android Studio环境搭建避坑指南:安装、Gradle与模拟器全流程

Android Studio环境搭建避坑指南:安装、Gradle与模拟器全流程

1. 动手前先想清楚:你缺的不是教程,而是正确的环境认知说实话,移动开发入门这件事,卡住大部分新手的往往不是代码,而是第一步的环境搭建。Android Studio 装到一半报错、SDK 下载卡住、模拟器起不来、Gradle 同步失败—…

📅 2026/9/28 6:15:58
2026 国产 AI PPT 深度对比:技术 / 职场 / 政企做 PPT 哪个更适合你|TaoToken 统一 Key 接入实测

2026 国产 AI PPT 深度对比:技术 / 职场 / 政企做 PPT 哪个更适合你|TaoToken 统一 Key 接入实测

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

📅 2026/9/28 6:15:58
MORE NEWS

更多资讯

📰

Zynq双核AMP实战:OpenAMP实现Linux+FreeRTOS核间通信全解析

做Zynq的人迟早要面对一个问题:PS端的双核Cortex-A9,到底怎么拆开用。如果老老实实跑Linux SMP,两个核心对你来说只是“多了个CPU”,除开多线程性能,没什么特别感觉。但当你手里有一块ZYNQ7020,既想让Linux…

📰

基于YOLO的安防监控异常行为检测:9100张数据集实战指南

1. 安防监控场景下的异常行为检测:这个数据集到底能干什么安防监控这个领域,做算法的人都有一个共识:数据比模型金贵。你可以找到一堆开源的YOLO预训练权重,COCO、VOC上跑得飞起,但一放到真实的监控画面里,…

📰

ZYNQ7020双核通信实战:OpenAMP打通Linux与FreeRTOS

做ZYNQ双核通信这块,我踩过的坑比很多人写过的代码都多。尤其是ZYNQ7020这颗芯片,在Linux和FreeRTOS之间打通数据通道,用一个叫OpenAMP的东西把两个完全不同的系统串起来,听起来很唬人,但本质上是一套内存共享加中断通…

📰

深入解析 gsd-2 TUI 持久化 UI 元素:扩展开发者的 Footer、Widget、编辑器控制与主题管理实战指南

人工智能AI Agent代码智能体Agent 编排CLIAI 应用 【免费下载链接】gsd-2 A powerful meta-prompting, context engineering and spec-driven development system that enables agents to work for long periods of time autonomously without losing track of the big picture…

📰

LoaderManager源码分析之二:LoadTask、AsyncTask与CursorLoader的协作链路拆解

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

📰

Cap 浮窗模式(Floating Mode)实战:用一个 data 属性按需隐藏 CAPTCHA 并按需运行 proof-of-work

网络安全应用安全后端 【免费下载链接】cap Free, open-source and self-hosted CAPTCHA alternative to reCAPTCHA. Privacy-first and powered by proof-of-work and instrumentation challenges. 项目地址: https://gitcode.com/gh_mirrors/cap13/cap 点击查看 免…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬