ARTICLE DETAIL

资讯详情

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

JSP化妆品库存系统实战:从环境搭建到过期预警

JSP化妆品库存系统实战:从环境搭建到过期预警 简介这是一份面向计算机专业本科生及Java初学者的毕业设计实战资源聚焦化妆品仓储场景下的库存管理业务需求提供一套可运行、可调试的B/S架构Web系统完整实现。资源基于JSPServletMySQL技术栈采用MyEclipse开发环境覆盖用户与管理员双角色权限体系支持商品信息登记、库存查询、数据修改等核心仓储管理功能。压缩包共164个文件含60个JSP页面实现前端交互、30个JPG/GIF图片界面素材与截图、15个JAR包依赖库、7个Java类与Class文件如ComServlet、AdminServlet、DBO等关键业务逻辑以及CSS、JS、XML等配套配置与样式文件整体大小5.25MB。已有123人学习下载内含可直接部署的项目源码、数据库脚本及配套设计报告结构规范、注释清晰便于理解MVC分层设计、JDBC连接池配置及基础Web安全过滤机制。1. 为什么用 JSP 做化妆品仓储库存系统不是“怀旧”而是稳扎稳打的毕业设计选择你手头有一堆口红、精华液、面膜要入库出库批次混、保质期短、SKU 多、退货频繁——这不是电商后台是真实小仓管每天面对的黑匣子。而你的毕业设计偏偏要把它变成一个能跑在 Tomcat 上、带登录权限、支持入库登记、库存查询、过期预警、导出 Excel 的 Web 系统。这时候选 JSP不是因为“老师教过”或“网上教程多”而是它在 Java Web 教学闭环里至今仍是最薄学习曲线 最高可交付性 最低部署门槛的组合不用 Spring Boot 的自动装配玄学不碰 Maven 依赖地狱不卡在前端框架选型上却能完整走通“页面 → Servlet → JDBC → MySQL”这条经典链路。尤其对计算机/信管专业本科生JSP 项目能清晰暴露数据流断点比如 session 丢失在哪、SQL 注入在哪、日期格式化翻车在哪比直接套 Vue Spring Boot 模板更利于答辩时讲清“我做了什么、怎么做的、哪里改了”。本文就带你从零搭起这个系统不绕开 JSTL 标签的坑不跳过 web.xml 配置细节不假装数据库字段命名很优雅——所有代码可复制粘贴所有报错有对应解法所有配置项标清版本边界JDK 8 Tomcat 8.5 MySQL 5.7 是当前高校实验室最稳组合。2. 搭建最小可运行骨架JDK、Tomcat、MySQL 三件套的版本锁死与验证2.1 JDK 8 与 Tomcat 8.5 的兼容性确认别让环境先翻车JSP 本质是 Servlet 容器编译.jsp文件为.java再编译成.class这个过程高度依赖 JDK 版本与 Tomcat 的匹配。实测发现JDK 11 的模块化机制会让 Tomcat 9 的jsp-api.jar加载失败而 Tomcat 10 已全面转向 Jakarta EE 命名空间javax.*→jakarta.*导致所有% page importjavax.servlet.* %报错。所以必须锁死JDK8u202 或更高更新版推荐 8u333确保javac -version输出1.8.0_XXXTomcat8.5.94最新 8.5.x下载地址为archive.apache.org/dist/tomcat/tomcat-8/v8.5.94/bin/apache-tomcat-8.5.94.zip验证方式解压后进入bin目录执行startup.batWindows或./startup.shLinux/macOS访问http://localhost:8080出现 Apache Tomcat 默认页即成功提示若启动报错Unsupported major.minor version 52.0说明 JDK 版本低于 Tomcat 要求若报错java.lang.NoClassDefFoundError: javax/servlet/Servlet说明 Tomcat lib 下缺少servlet-api.jarTomcat 8.5 自带勿额外添加。2.2 MySQL 5.7 创建专用库与初始化表结构字段类型直击化妆品业务痛点化妆品仓储核心矛盾在于批次分散、效期敏感、分类细碎、出入库频繁。因此表设计不能照搬通用库存模板。以下是经三次答辩打磨后的最小必要表全部用InnoDB引擎utf8mb4字符集-- 创建数据库显式指定字符集避免中文乱码 CREATE DATABASE IF NOT EXISTS cosme_stock CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE cosme_stock; -- 商品主表重点在 brand品牌、category类目、shelf_life保质月数 CREATE TABLE goods ( id INT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(20) NOT NULL UNIQUE COMMENT 商品编码如 SKIN-001, name VARCHAR(100) NOT NULL COMMENT 商品名称, brand VARCHAR(50) NOT NULL COMMENT 品牌, category ENUM(护肤,彩妆,香水,工具) NOT NULL DEFAULT 护肤, unit VARCHAR(10) NOT NULL DEFAULT 瓶 COMMENT 计量单位, shelf_life INT NOT NULL COMMENT 保质月数用于计算过期日, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 库存表关键字段 batch_no批次号、expire_date精确到日的过期日、stock_num实时库存 CREATE TABLE stock ( id INT PRIMARY KEY AUTO_INCREMENT, goods_id INT NOT NULL, batch_no VARCHAR(30) NOT NULL COMMENT 生产批次号唯一标识每批货, expire_date DATE NOT NULL COMMENT 过期日期由入库日期shelf_life自动计算, stock_num INT NOT NULL DEFAULT 0 COMMENT 当前库存数量, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (goods_id) REFERENCES goods(id) ON DELETE CASCADE ); -- 入库单表记录每次入库动作关联 goods 和 stock CREATE TABLE inbound_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(20) NOT NULL UNIQUE COMMENT 入库单号如 IB20240501001, goods_id INT NOT NULL, batch_no VARCHAR(30) NOT NULL, quantity INT NOT NULL COMMENT 入库数量, inbound_date DATE NOT NULL DEFAULT (CURRENT_DATE), operator VARCHAR(20) NOT NULL COMMENT 操作人, remark TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (goods_id) REFERENCES goods(id) );逻辑说明goods.shelf_life存月数而非天数因化妆品保质期通常以“24个月”“36个月”标注后续计算expire_date时用DATE_ADD(inbound_date, INTERVAL shelf_life MONTH)更准确stock.expire_date必须为DATE类型非DATETIME避免时间部分干扰过期判断stock.batch_no与goods_id组合唯一索引需手动添加ALTER TABLE stock ADD UNIQUE KEY uk_goods_batch (goods_id, batch_no);—— 这是防止同一批次重复录入的核心约束。2.3 Eclipse/IDEA 中创建 Dynamic Web ProjectWebContent 与 src 的物理边界必须清晰很多同学把 Java 类丢进WebContent/WEB-INF/classes导致编译失败。正确路径结构如下以 Eclipse 为例cosme-stock-system/ ├── WebContent/ ← 所有 .jsp、.html、CSS、JS、图片放这里 │ ├── index.jsp │ ├── login.jsp │ ├── WEB-INF/ │ │ ├── web.xml ← 部署描述符必须存在 │ │ └── lib/ ← 放 mysql-connector-java-5.1.47.jarJDK8兼容版 ├── src/ ← 所有 Java 类Servlet、DAO、Bean放这里 │ ├── servlet/ │ │ └── LoginServlet.java │ ├── dao/ │ │ └── GoodsDAO.java │ └── bean/ │ └── Goods.java关键配置新建项目时“Target runtime” 必须选中已配置好的 Tomcat 8.5“Dynamic web module version” 选3.1Tomcat 8.5 对应 Servlet 3.1 规范勾选 “Generate web.xml deployment descriptor” —— 后续所有 Servlet 映射都靠它web.xml中必须声明 JSP 编译参数解决中文注释乱码jsp-config jsp-property-group url-pattern*.jsp/url-pattern page-encodingUTF-8/page-encoding scripting-invalidfalse/scripting-invalid /jsp-property-group /jsp-config3. 实现核心业务流程从登录鉴权到库存查询的 Servlet-JSP 协作链3.1 登录功能用 HttpSession 管理用户状态拒绝 Cookie 伪造login.jsp表单提交到LoginServlet后者验证账号密码后将用户信息存入 session// src/servlet/LoginServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); // 简单校验实际应查数据库此处硬编码演示 if (admin.equals(username) 123456.equals(password)) { // 创建用户对象并存入 session User user new User(); user.setUsername(username); user.setRole(admin); // 可扩展为数据库字段 request.getSession().setAttribute(user, user); response.sendRedirect(main.jsp); // 登录成功跳转 } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); } }main.jsp顶部必须加 session 校验防直接 URL 访问% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8% % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % Object user session.getAttribute(user); if (user null) { response.sendRedirect(login.jsp); return; } %逻辑说明request.getSession()默认创建新 sessionsetAttribute将对象绑定到当前会话response.sendRedirect()是客户端重定向URL 变为main.jsp而forward()是服务器端转发URL 不变且 request 参数保留return在 JSP 脚本片段中立即终止页面执行避免后续 HTML 渲染。3.2 商品管理JSP 表单提交 Servlet 插入 DAO 封装 JDBCgoods_add.jsp提交到GoodsAddServlet后者调用GoodsDAO.add()// src/dao/GoodsDAO.java public boolean add(Goods goods) throws SQLException { String sql INSERT INTO goods (code, name, brand, category, unit, shelf_life) VALUES (?, ?, ?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, goods.getCode()); ps.setString(2, goods.getName()); ps.setString(3, goods.getBrand()); ps.setString(4, goods.getCategory()); ps.setString(5, goods.getUnit()); ps.setInt(6, goods.getShelfLife()); return ps.executeUpdate() 0; } }DBUtil.java封装连接关键Class.forName(com.mysql.jdbc.Driver)在 JDK8 可省略但显式加载更稳妥public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/cosme_stock?useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; public static Connection getConnection() throws SQLException { try { Class.forName(com.mysql.jdbc.Driver); // MySQL 5.x 驱动类名 } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL Driver not found, e); } return DriverManager.getConnection(URL, USER, PASSWORD); } }参数说明useSSLfalseMySQL 5.7 默认要求 SSL本地开发关闭即可serverTimezoneAsia/Shanghai强制时区避免java.sql.Timestamp与 MySQLDATETIME时差导致日期错乱PreparedStatement预编译防止 SQL 注入executeUpdate()返回影响行数0判断是否插入成功。3.3 库存查询用 JSTLc:forEach渲染动态表格处理空数据场景stock_list.jsp从StockListServlet获取ListStock并渲染% 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 % c:set varctx value${pageContext.request.contextPath} / !DOCTYPE html html headtitle库存列表/title/head body h2当前库存/h2 c:if test${empty stocks} p暂无库存记录/p /c:if c:if test${not empty stocks} table border1 trth商品名称/thth品牌/thth批次号/thth过期日/thth库存/thth操作/th/tr c:forEach items${stocks} varstock tr td${stock.goodsName}/td td${stock.brand}/td td${stock.batchNo}/td tdfmt:formatDate value${stock.expireDate} patternyyyy-MM-dd//td td${stock.stockNum}/td tda href${ctx}/servlet/StockDetailServlet?id${stock.id}详情/a/td /tr /c:forEach /table /c:if /body /html逻辑说明c:if test${empty stocks}判断 List 是否为空避免NullPointerExceptionfmt:formatDate格式化java.util.Date为yyyy-MM-dd比stock.expireDate.toString()更可控${pageContext.request.contextPath}获取应用上下文路径如/cosme-stock-system确保链接不因部署路径变化而失效。4. 关键避坑指南JSP 开发中 5 个高频血泪问题与现场解法4.1 现象JSP 页面中文显示为乱码如“??????”原因页面未声明pageEncodingUTF-8Tomcatconf/web.xml中默认JspServlet的init-param未设pageEncodingMySQL 连接 URL 缺少characterEncodingutf8mb4。解决所有 JSP 第一行加% page pageEncodingUTF-8 %修改Tomcat/conf/web.xml在servlet标签内JspServlet部分添加init-param param-namepageEncoding/param-name param-valueUTF-8/param-value /init-paramJDBC URL 补全jdbc:mysql://localhost:3306/cosme_stock?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4。4.2 现象c:forEach报错According to TLD or attribute directive in tag file, attribute items does not accept any expressions原因JSTL 标签库未正确引入或版本不匹配。Tomcat 8.5 需 JSTL 1.2jstl-1.2.jar而常见错误是下载了 JSTL 1.0 或混淆了standard.jarjstl.jar组合。解决删除WEB-INF/lib下所有 JSTL 相关 jar仅放入jstl-1.2.jarMaven 坐标javax.servlet:jstl:1.2JSP 顶部必须写% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %URI 不能写错。4.3 现象入库时expire_date比预期早一个月如 2024-05-01 入库 24个月 → 2026-04-01原因MySQLDATE_ADD(date, INTERVAL n MONTH)的月份加法按日历规则2024-05-31 1 MONTH 2024-06-306月无31日但2024-01-31 1 MONTH 2024-02-292月取最后一天。化妆品批次常为月末生产导致过期日计算偏差。解决入库时显式计算过期日不用DATE_ADD// Java 中计算先加月数再取当月最后一天 Calendar cal Calendar.getInstance(); cal.setTime(inboundDate); cal.add(Calendar.MONTH, shelfLife); // 设置为当月最后一天 cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH)); Date expireDate cal.getTime();然后将expireDate作为参数传入 INSERT。4.4 现象session.getAttribute(user)在某些页面返回 null但登录成功后明明存了原因web.xml中session-config的cookie-http-only或secure属性被误开启浏览器禁用 Cookie尤其 Chrome 80 默认阻止第三方 Cookie页面跳转用了response.sendRedirect()但未注意重定向后 session ID 传递机制。解决检查web.xml是否有http-onlytrue/http-only开发阶段设为false确保所有跳转使用request.getContextPath()构造绝对路径如response.sendRedirect(request.getContextPath() /main.jsp)在login.jsp表单中加隐藏域input typehidden namejsessionid value%session.getId()%备用方案非首选。4.5 现象GoodsDAO查询返回goodsName为空但数据库字段name有值原因Resultset 映射时列名大小写不一致。MySQL 在 Linux 下表名、字段名默认区分大小写而 Windows 不区分。若建表用NAMEJava 用rs.getString(name)可能返回 null。解决统一用小写字段名建表时name VARCHAR(100)非NAMEResultSet 取值时用rs.getString(name)小写或更稳妥用rs.getString(2)按 SELECT 顺序在DBUtil.getConnection()中添加?useUnicodetruecharacterEncodingutf8mb4参数强化字符集传递。5. 过期预警与导出 Excel两个让答辩加分的实战功能落地5.1 过期预警用 SQL 子查询实现“30天内过期商品”一键筛选单纯WHERE expire_date DATE_ADD(CURDATE(), INTERVAL 30 DAY)无法覆盖“已过期”和“即将过期”两类。更精准的写法是SELECT g.code AS 商品编码, g.name AS 商品名称, g.brand AS 品牌, s.batch_no AS 批次号, s.expire_date AS 过期日, DATEDIFF(s.expire_date, CURDATE()) AS 距过期天数 FROM stock s JOIN goods g ON s.goods_id g.id WHERE s.expire_date DATE_ADD(CURDATE(), INTERVAL 30 DAY) AND s.stock_num 0 ORDER BY s.expire_date ASC;在StockWarningServlet.java中执行此 SQL结果存入 request 并转发至warning_list.jsp。关键点DATEDIFF()直接计算天数差负数表示已过期如-5s.stock_num 0过滤零库存记录避免预警无效商品ORDER BY s.expire_date ASC让最近过期的排在最前符合仓管查看习惯。5.2 Excel 导出用 Apache POI 4.1.2JDK8 兼容版生成 .xls 文件ExportExcelServlet.java生成二进制 Excel 并设置响应头protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ListStockWarning warnings getWarningList(); // 调用上述 SQL 查询 HSSFWorkbook workbook new HSSFWorkbook(); HSSFSheet sheet workbook.createSheet(近30天过期预警); // 表头 HSSFRow headerRow sheet.createRow(0); String[] headers {商品编码,商品名称,品牌,批次号,过期日,距过期天数}; for (int i 0; i headers.length; i) { headerRow.createCell(i).setCellValue(headers[i]); } // 数据行 for (int i 0; i warnings.size(); i) { HSSFRow row sheet.createRow(i 1); StockWarning w warnings.get(i); row.createCell(0).setCellValue(w.getCode()); row.createCell(1).setCellValue(w.getName()); row.createCell(2).setCellValue(w.getBrand()); row.createCell(3).setCellValue(w.getBatchNo()); row.createCell(4).setCellValue(new SimpleDateFormat(yyyy-MM-dd).format(w.getExpireDate())); row.createCell(5).setCellValue(w.getDaysToExpire()); } // 设置响应头触发下载 response.setContentType(application/vnd.ms-excel); response.setHeader(Content-Disposition, attachment; filenamecosme_warning_ new SimpleDateFormat(yyyyMMddHHmmss).format(new Date()) .xls); workbook.write(response.getOutputStream()); }参数说明HSSFWorkbook对应 Excel 97-2003 格式.xls兼容性最好若需.xlsx改用XSSFWorkbook需 poi-ooxmlresponse.setContentType(application/vnd.ms-excel)告诉浏览器这是 Excel 文件Content-Disposition中attachment强制下载filename包含时间戳避免重名workbook.write()直接写入响应流无需FileOutputStream。5.3 答辩现场演示技巧三个让老师眼前一亮的细节数据库截图对比在答辩 PPT 中放两张图——左侧是SELECT * FROM stock WHERE expire_date CURDATE()的结果标红“已过期”右侧是WHERE expire_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY)的结果标黄“预警”直观体现系统价值导出文件命名cosme_warning_20240501143022.xls中的时间戳证明是实时生成不是静态文件异常处理演示故意在goods_add.jsp输入超长品牌名如 51 字符展示INSERT ... VALUES因VARCHAR(50)截断报错再修改为try-catch SQLException并提示“品牌名称不能超过50字”体现工程思维。我带过 7 届毕设最常被问的问题是“如果现在要加微信扫码入库你准备怎么改”——答案不在技术栈而在你是否清楚inbound_order表和stock表的耦合点。把batch_no生成逻辑从手工输入改成扫码解析如SCAN-20240501-ABC123再拆出scan_time字段整个链路就活了。希望帮到你。本文还有配套的精品资源点击获取
返回列表