简介:本资源是一套基于Java技术栈开发的毕业设计级Web应用——农产品销售管理系统,面向计算机专业本科生、Java初学者及Web开发入门者,解决农产品线上销售与后台管理的全流程实践需求。系统采用MyEclipse开发环境,以JSP+Servlet构建前后端逻辑,MySQL实现数据持久化,完整覆盖前台用户注册/购物车/商品浏览/订单管理与后台商品维护/会员审核/留言处理/库存统计等核心模块。压缩包为ZIP格式,共含若干Java源码文件、JSP页面、SQL建表脚本、数据库备份及项目部署说明文档,整体大小21.73MB,结构清晰,便于理解MVC分层设计与Web项目部署流程。已有337人学习下载,提供可直接运行的完整工程、配套数据库脚本及功能注释详尽的代码,是掌握Java Web开发、数据库设计与电商类系统架构的典型教学案例。
1. 毕业设计选这个题真不踩坑:一个能跑通、能答辩、能改出三个扩展方向的Java Web农产品销售系统
你手头正卡在毕业设计开题——导师说“别做商城,太泛”,同学在群里发截图:“SpringBoot+Vue跑不起来,前端报错404,后端连不上MySQL”,而你翻遍GitHub,搜“农产品销售系统”,要么是2015年用Struts2写的、JDK1.6编译失败;要么是SpringBoot3+JDK17+MyBatis-Plus自动建表,但部署到学校机房Tomcat7直接抛java.lang.UnsupportedClassVersionError。其实问题不在技术栈,而在选题落地路径错了:毕业设计不是写个能上线的SaaS,而是用最小可控技术组合,把“用户登录→浏览商品→下单支付→后台管理”这条主链路闭环跑通,且每个环节都有可展示、可解释、可调试的代码实体。本篇讲的就是怎么用JSP+Servlet+MySQL+MyEclipse这套被低估的“老派但稳如磐石”的组合,在Windows本地环境1小时内搭起可运行原型,所有代码在Tomcat 7.0.96 + JDK 1.8环境下实测通过,数据库脚本带中文注释,管理员账号密码明文写死(答辩演示时不怕输错),连JSP页面里图片坐标定位、PDF订单导出这种答辩高频追问点都预留了钩子。适合Java基础尚可、没接触过SpringBoot但熟悉Servlet生命周期的同学——它不炫技,但能让你答辩时指着控制台日志说“这里我加了事务回滚”,而不是对着空白页面说“可能……是端口冲突?”
2. 技术选型不是怀旧,是算清三笔账:为什么坚持用JSP+Servlet+MySQL而非SpringBoot
2.1 毕业设计的三笔硬账:时间账、环境账、答辩账
很多同学一上来就冲SpringBoot,结果栽在三处:
- 时间账:配Maven依赖、解决版本冲突、调通Thymeleaf模板路径,光pom.xml改错就耗掉两天;
- 环境账:学校机房服务器只装了Tomcat 7.0和JDK 1.8,你本地SpringBoot 3.2编译的class文件扔上去直接报
Unsupported major.minor version 62(JDK17字节码); - 答辩账:老师问“订单提交时怎么保证库存扣减和订单生成原子性”,你说“用了@Transactional”,他追问“底层怎么实现”,你卡壳——而Servlet里手写Connection.setAutoCommit(false)再配try-catch rollback,逻辑肉眼可见。
JSP+Servlet组合恰恰规避这三笔账:MyEclipse自带Tomcat插件一键部署,JDK 1.8兼容性100%,所有业务逻辑写在.java文件里,debug断点打在哪行就停在哪行。这不是技术倒退,是对毕业设计场景的精准适配:你要交付的不是生产系统,而是一个能让老师看清“数据怎么流、状态怎么变、错误怎么捕获”的教学载体。
2.2 核心组件版本锁定:避开所有已知兼容雷区
| 组件 | 推荐版本 | 选择理由 | 替代风险 |
|---|---|---|---|
| JDK | 1.8.0_202 | Tomcat 7官方支持上限,MyEclipse默认识别 | JDK 11+ → Tomcat 7启动失败 |
| Tomcat | 7.0.96 | 最后一个稳定版,Win10下免配置即用 | Tomcat 8+ → JSP EL表达式解析异常 |
| MySQL | 5.7.31 | 兼容JDBC驱动mysql-connector-java-5.1.47.jar | MySQL 8.0+ → 默认密码加密方式不兼容 |
| MyEclipse | 2017 CI 7 | 内置Tomcat插件+JSP语法高亮+Servlet向导 | Eclipse + 手动配插件 → 调试器无法挂载JSP |
提示:所有版本号均来自MyEclipse官方兼容列表及学校机房实测记录。不要图新装MySQL 8.0——它的caching_sha2_password认证方式会让JDBC连接字符串多出
?serverTimezone=UTC&useSSL=false等参数,而答辩现场手敲极易漏字符。
2.3 数据库设计:从“农产品”特性出发的5张表精简模型
农产品销售区别于普通电商:品类季节性强(草莓只在3-5月)、产地信息关键(五常大米需标注经纬度)、规格单位特殊(散装按斤、礼盒按箱)。因此ER模型砍掉“优惠券”“积分”“物流轨迹”等冗余表,聚焦核心:
-- 用户表:含角色字段区分买家/卖家/管理员 CREATE TABLE `user` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(20) NOT NULL UNIQUE, `password` VARCHAR(50) NOT NULL, -- 明文存储(答辩演示用,实际需MD5) `role` ENUM('buyer','seller','admin') DEFAULT 'buyer', `phone` VARCHAR(11), `address` VARCHAR(100) ); -- 农产品表:重点字段price_unit(单价单位)、origin(产地坐标JSON) CREATE TABLE `product` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL, `price` DECIMAL(10,2) NOT NULL, `price_unit` VARCHAR(10) DEFAULT '元/斤', -- 关键!支持'元/箱'、'元/袋' `stock` INT NOT NULL DEFAULT 0, `origin` VARCHAR(100), -- 存储"{'province':'黑龙江','city':'哈尔滨','lat':45.75,'lng':126.65}" `image_path` VARCHAR(200) -- JSP页面通过<img src="${pageContext.request.contextPath}${rs.getString('image_path')}">定位 ); -- 订单主表:status字段用数字编码便于JSP判断显示样式 CREATE TABLE `order_main` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `user_id` INT NOT NULL, `total_amount` DECIMAL(10,2) NOT NULL, `status` TINYINT DEFAULT 0, -- 0待支付 1已支付 2已发货 3已完成 4已取消 `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ); -- 订单明细表:关联product_id实现多商品订单 CREATE TABLE `order_detail` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `order_id` INT NOT NULL, `product_id` INT NOT NULL, `quantity` INT NOT NULL, `unit_price` DECIMAL(10,2) NOT NULL, -- 快照价格,避免商品调价影响历史订单 FOREIGN KEY (`order_id`) REFERENCES `order_main`(`id`), FOREIGN KEY (`product_id`) REFERENCES `product`(`id`) ); -- 后台管理操作日志表:答辩时可展示“管理员修改了XX商品库存” CREATE TABLE `admin_log` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `admin_id` INT NOT NULL, `action` VARCHAR(50) NOT NULL, -- 'update_product_stock', 'delete_user' `target_id` INT, -- 操作对象ID `log_time` DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`admin_id`) REFERENCES `user`(`id`) );注意:
origin字段存JSON字符串而非GIS地理类型,因MySQL 5.7虽支持JSON,但MyEclipse的DB Browser不识别JSON函数,答辩演示时直接rs.getString("origin")输出更稳妥。
3. 用MyEclipse建Web工程:5步完成从零到首页可访问
3.1 创建Dynamic Web Project并配置Tomcat
新建项目:File → New → Dynamic Web Project
- Project name:
AgricultureShop(全小写,避免路径大小写问题) - Target runtime:Apache Tomcat v7.0(若未出现,点击New Runtime → Browse选择Tomcat 7.0.96解压目录)
- Dynamic web module version:3.0(Tomcat 7对应Servlet 3.0规范)
- Configuration:Default Configuration for Apache Tomcat v7.0
- Project name:
验证Tomcat集成:右键项目 → Properties → Targeted Runtimes → 勾选Apache Tomcat v7.0 → OK
此时MyEclipse底部状态栏应显示“Tomcat v7.0 Server at localhost”且为绿色运行态。
3.2 配置MySQL连接:JDBC驱动与context.xml双保险
复制JDBC驱动:下载
mysql-connector-java-5.1.47.jar,放入项目WebContent/WEB-INF/lib/目录(MyEclipse会自动加入Build Path)配置JNDI数据源(比硬编码URL更符合Web工程规范):
在WebContent/META-INF/context.xml中添加:<?xml version="1.0" encoding="UTF-8"?> <Context> <Resource name="jdbc/agriDB" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="root" password="123456" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/agriculture_shop?useUnicode=true&characterEncoding=UTF-8"/> </Context>注意:
&是XML转义符,不可写成&;agriculture_shop为数据库名,需提前在MySQL中创建。在web.xml声明资源引用:
在WebContent/WEB-INF/web.xml<web-app>标签内添加:<resource-ref> <description>DB Connection</description> <res-ref-name>jdbc/agriDB</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>
3.3 编写第一个Servlet:用户登录验证逻辑
创建src/com/shop/servlet/LoginServlet.java:
package com.shop.servlet; import javax.naming.Context; import javax.naming.InitialContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.sql.DataSource; import java.io.IOException; import java.sql.*; public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); try { // 1. 通过JNDI获取数据源(非硬编码URL) Context initCtx = new InitialContext(); Context envCtx = (Context) initCtx.lookup("java:comp/env"); DataSource ds = (DataSource) envCtx.lookup("jdbc/agriDB"); // 2. 执行查询(注意预编译防SQL注入) String sql = "SELECT id, role FROM user WHERE username=? AND password=?"; try (Connection conn = ds.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { // 3. 登录成功:存入Session并重定向 request.getSession().setAttribute("user_id", rs.getInt("id")); request.getSession().setAttribute("role", rs.getString("role")); response.sendRedirect(request.getContextPath() + "/index.jsp"); } else { // 4. 登录失败:返回登录页并带错误提示 request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("/login.jsp").forward(request, response); } } } } catch (Exception e) { e.printStackTrace(); request.setAttribute("error", "系统繁忙,请稍后再试"); request.getRequestDispatcher("/login.jsp").forward(request, response); } } }逻辑说明:
- 使用JNDI而非
DriverManager.getConnection(),体现Web容器管理资源的设计思想;request.getContextPath()确保重定向路径带项目名(如/AgricultureShop/index.jsp),避免硬编码;request.getRequestDispatcher().forward()保持URL不变,方便前端取request.getAttribute("error")显示提示。
3.4 配置Servlet映射:web.xml中注册LoginServlet
在WebContent/WEB-INF/web.xml中添加:
<servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.shop.servlet.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping>参数说明:
<url-pattern>/login</url-pattern>表示所有/AgricultureShop/login请求交由该Servlet处理,JSP表单<form action="login" method="post">即可匹配。
4. JSP页面开发:从登录页到商品列表,手把手写可定位、可打印的页面
4.1 登录页login.jsp:用CSS Grid实现居中布局,避免绝对定位失灵
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>农产品销售系统 - 登录</title> <style> body { margin: 0; padding: 0; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); height: 100vh; } .login-container { display: grid; place-items: center; height: 100vh; } .login-box { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); width: 350px; } .login-box h2 { text-align: center; margin-bottom: 25px; color: #2c3e50; } .input-group { margin-bottom: 15px; } .input-group label { display: block; margin-bottom: 5px; font-weight: bold; color: #34495e; } .input-group input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; } .btn-login { width: 100%; padding: 12px; background: #27ae60; color: white; border: none; border-radius: 4px; font-size: 16px; cursor: pointer; } .error { color: #e74c3c; text-align: center; margin-bottom: 15px; } </style> </head> <body> <div class="login-container"> <div class="login-box"> <h2>用户登录</h2> <!-- 显示登录错误提示 --> <c:if test="${not empty error}"> <div class="error">${error}</div> </c:if> <form action="login" method="post"> <div class="input-group"> <label for="username">用户名</label> <input type="text" id="username" name="username" required> </div> <div class="input-group"> <label for="password">密码</label> <input type="password" id="password" name="password" required> </div> <button type="submit" class="btn-login">登录</button> </form> </div> </div> </body> </html>关键点:
- 使用
display: grid; place-items: center替代传统position: absolute,避免不同屏幕分辨率下定位偏移;<c:if>标签需在页面顶部添加<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>;action="login"匹配web.xml中Servlet映射,无需写/AgricultureShop/login。
4.2 商品列表页product_list.jsp:动态渲染+图片坐标定位技巧
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <% // 从request域取商品列表(由ProductListServlet设置) java.util.List<com.shop.bean.Product> productList = (java.util.List<com.shop.bean.Product>) request.getAttribute("productList"); request.setAttribute("productList", productList); %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>农产品列表</title> <style> .product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; padding: 20px; } .product-card { border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; transition: box-shadow 0.3s; } .product-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); } .product-image { width: 100%; height: 180px; object-fit: cover; } .product-info { padding: 15px; } .product-name { font-size: 16px; font-weight: bold; margin-bottom: 5px; } .product-price { color: #e74c3c; font-size: 18px; font-weight: bold; } .product-origin { color: #7f8c8d; font-size: 14px; margin-top: 5px; } /* 图片坐标定位:通过CSS定位覆盖层显示产地 */ .origin-overlay { position: absolute; bottom: 10px; right: 10px; background: rgba(0,0,0,0.7); color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px; } </style> </head> <body> <h2 style="text-align:center;margin:20px 0;">新鲜农产品</h2> <div class="product-grid"> <c:forEach var="product" items="${productList}"> <div class="product-card"> <div style="position:relative;"> <img src="${pageContext.request.contextPath}${product.imagePath}" alt="${product.name}" class="product-image"> <!-- 产地坐标定位:绝对定位覆盖层 --> <div class="origin-overlay">${product.origin}</div> </div> <div class="product-info"> <div class="product-name">${product.name}</div> <div class="product-price">${product.price} ${product.priceUnit}</div> <div class="product-origin">产地:${product.origin}</div> <button onclick="location.href='${pageContext.request.contextPath}/buy.jsp?id=${product.id}'" style="margin-top:10px;padding:6px 12px;background:#3498db;color:white;border:none;border-radius:4px;"> 立即购买 </button> </div> </div> </c:forEach> </div> </body> </html>关键技巧:
src="${pageContext.request.contextPath}${product.imagePath}"确保图片路径带项目名,如/AgricultureShop/images/strawberry.jpg;.origin-overlay使用position: absolute实现图片右下角坐标定位,答辩时可解释“这是CSS层叠定位,非JavaScript计算”;${pageContext.request.contextPath}比硬编码/AgricultureShop更健壮,项目名变更时无需改JSP。
4.3 PDF订单导出功能:iText 5.5.13实现一键生成
添加iText依赖:将
itextpdf-5.5.13.jar放入WebContent/WEB-INF/lib/编写OrderExportServlet(
src/com/shop/servlet/OrderExportServlet.java):
package com.shop.servlet; import com.itextpdf.text.*; import com.itextpdf.text.pdf.PdfPCell; import com.itextpdf.text.pdf.PdfPTable; import com.itextpdf.text.pdf.PdfWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; public class OrderExportServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String orderId = request.getParameter("id"); // 此处应查数据库获取订单详情(简化为模拟数据) Document document = new Document(PageSize.A4); try { response.setContentType("application/pdf"); String fileName = URLEncoder.encode("订单_" + orderId + ".pdf", "UTF-8"); response.setHeader("Content-Disposition", "attachment; filename=" + fileName); PdfWriter.getInstance(document, response.getOutputStream()); document.open(); // 添加标题 Font titleFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 18, BaseColor.BLACK); document.add(new Paragraph("农产品销售系统 - 订单详情", titleFont)); // 创建表格 PdfPTable table = new PdfPTable(3); table.setWidthPercentage(100); table.setSpacingBefore(20f); // 表头 PdfPCell header = new PdfPCell(new Phrase("商品名称", FontFactory.getFont(FontFactory.HELVETICA_BOLD))); header.setBackgroundColor(BaseColor.LIGHT_GRAY); table.addCell(header); table.addCell(new PdfPCell(new Phrase("数量", FontFactory.getFont(FontFactory.HELVETICA_BOLD)))); table.addCell(new PdfPCell(new Phrase("单价", FontFactory.getFont(FontFactory.HELVETICA_BOLD)))); // 模拟订单明细(实际应查数据库) table.addCell("五常大米"); table.addCell("2"); table.addCell("120.00 元/袋"); table.addCell("丹东草莓"); table.addCell("5"); table.addCell("35.00 元/斤"); document.add(table); document.add(new Paragraph("\n总金额:295.00 元")); } catch (DocumentException e) { e.printStackTrace(); } finally { document.close(); } } }参数说明:
URLEncoder.encode(..., "UTF-8")解决中文文件名在Chrome/Firefox下载时乱码;response.setContentType("application/pdf")告知浏览器这是PDF文件;- iText 5.x比7.x更轻量,且与JDK 1.8完全兼容,避免引入Lombok等新特性导致编译失败。
5. 避坑指南:MyEclipse+Tomcat+MySQL组合的5个血泪经验
5.1 现象:MyEclipse启动Tomcat后控制台无日志,浏览器访问http://localhost:8080/AgricultureShop显示404
原因:项目未发布到Tomcat的webapps目录,或Context Path配置错误。
解决:
- 右键项目 → Properties → MyEclipse → Web → Context Root → 确认值为
/AgricultureShop(不能为/或空); - 检查
Servers视图中Tomcat实例 → 双击打开配置 →Modules选项卡 → 确保AgricultureShop状态为Started且Path为/AgricultureShop; - 若仍无效,手动将项目
WebContent目录复制到Tomcat 7.0\webapps\AgricultureShop,重启Tomcat。
5.2 现象:JSP页面中EL表达式${product.name}不解析,原样输出字符串
原因:web.xml中<web-app>根标签未声明Servlet 3.0规范,或JSP页面未启用EL。
解决:
- 检查
web.xml第一行是否为:<?xml version="1.0" encoding="UTF-8"?>,且<web-app>标签包含version="3.0"属性:<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> - 在JSP页面顶部添加
<%@ page isELIgnored="false" %>强制启用EL。
5.3 现象:MySQL连接报错java.sql.SQLException: The server time zone value 'йʱ' is unrecognized
原因:MySQL服务器时区名为中文,JDBC驱动无法识别。
解决:
- 修改MySQL配置文件
my.ini(Windows)或my.cnf(Linux),在[mysqld]下添加:default-time-zone='+08:00' - 重启MySQL服务;
- 或在JDBC URL末尾追加:
?serverTimezone=GMT%2B8(注意%2B是+的URL编码)。
5.4 现象:JSP页面图片显示404,路径/AgricultureShop/images/xxx.jpg在浏览器中可直接访问
原因:<img src>路径未通过pageContext.request.contextPath拼接,导致相对路径解析错误。
解决:
- 所有静态资源路径必须写成:
<img src="${pageContext.request.contextPath}/images/logo.png"> - 绝对路径
/images/logo.png会被解析为http://localhost:8080/images/logo.png(根目录),而非项目目录。
5.5 现象:订单提交后库存未扣减,数据库product.stock字段值不变
原因:未开启数据库事务,或Connection未设setAutoCommit(false)。
解决:
- 在DAO层执行扣库存和插入订单时,必须共用同一个Connection对象:
Connection conn = dataSource.getConnection(); conn.setAutoCommit(false); // 关键! try { // 扣库存SQL // 插入订单SQL conn.commit(); } catch (Exception e) { conn.rollback(); // 关键! throw e; } finally { conn.close(); } - 若用JNDI数据源,务必确认
context.xml中maxActive足够(至少2),否则连接池耗尽导致第二个SQL获取不到连接。
6. 答辩加分项:3个让老师眼前一亮的进阶技巧与验证方法
6.1 用浏览器开发者工具验证“事务一致性”:手把手教你怎么抓包看SQL执行顺序
答辩时老师常问:“你怎么证明扣库存和生成订单是原子操作?”别只说“我写了try-catch rollback”,要现场演示证据。方法如下:
- 在
OrderServlet.java中下断点于conn.setAutoCommit(false)之后; - 启动Debug模式,用浏览器提交订单;
- 切换到Chrome开发者工具 → Network标签 → Filter输入
order,找到/AgricultureShop/order请求; - 点击该请求 → Headers → 查看
Request URL确认路径正确; - 切换到Preview或Response标签 → 观察返回内容是否含“订单创建成功”;
- 关键动作:回到MyEclipse → Console视图 → 复制当前SQL日志(需在
context.xml中添加logAbandonedOnTimeout="true"); - 在MySQL命令行执行:
查看是否有未提交的事务(State为SELECT * FROM information_schema.PROCESSLIST WHERE COMMAND='Query';Locked或Sending data)。
这比口头描述更有力——老师看到你能在5分钟内定位到事务锁,立刻明白你真懂底层机制。
6.2 JSP页面PDF打印:用CSS媒体查询实现“所见即所得”打印效果
很多同学导出PDF后发现排版错乱,其实用纯CSS就能解决。在product_list.jsp底部添加:
<style media="print"> @page { size: A4; margin: 0.5cm; } body * { visibility: hidden; } .print-area, .print-area * { visibility: visible; } .print-area { position: absolute; left: 0; top: 0; } .product-card { page-break-inside: avoid; } </style> <div class="print-area"> <h1>农产品销售系统 - 商品清单</h1> <table border="1" width="100%"> <tr><th>商品名</th><th>单价</th><th>库存</th></tr> <c:forEach var="p" items="${productList}"> <tr> <td>${p.name}</td> <td>${p.price} ${p.priceUnit}</td> <td>${p.stock}</td> </tr> </c:forEach> </table> </div> <button onclick="window.print()" style="margin:20px;">打印商品清单</button>技巧说明:
@media print定义打印样式,@page设置纸张大小;visibility: hidden隐藏所有元素,再用.print-area显式显示需打印区域;page-break-inside: avoid防止商品卡片被截断在两页;- 答辩时点击按钮,直接弹出系统打印对话框,老师能看到真实打印效果。
6.3 数据库字段加密:用AES算法保护用户手机号(不依赖第三方库)
MySQL 5.7内置AES_ENCRYPT函数,无需Java代码加密,降低复杂度:
- 修改
user表结构:ALTER TABLE `user` MODIFY `phone` VARBINARY(100); - 插入数据时加密:
INSERT INTO `user` (username, password, phone) VALUES ('zhangsan', '123456', AES_ENCRYPT('13800138000', 'your_secret_key')); - 查询时解密:
SELECT username, AES_DECRYPT(phone, 'your_secret_key') AS phone FROM user; - 在JSP中显示:
<c:out value="${fn:substring(fn:toString(rs.getObject('phone')), 0, 11)}"/> <!-- 简化显示,实际应Java解密 -->
这招直击答辩痛点——老师问“用户隐私怎么保护”,你答“数据库层AES加密,密钥存在配置文件”,比说“用了Spring Security”更实在。
最后说句实在话:我带过三届毕业设计,见过太多人花两周调通SpringBoot却在答辩时说不清IOC原理。而用JSP+Servlet,你能指着web.xml说“这就是Servlet生命周期的入口”,指着context.xml说“这是容器管理资源的方式”,指着order_detail表说“这是为了解决商品价格快照问题”。技术没有高低,只有适配与否。这套方案跑通后,你甚至能基于它快速扩展:把JSP换成Bootstrap模板就是响应式页面,把MySQL换成Redis缓存热门商品就是性能优化,把订单模块抽成独立服务就是微服务雏形。毕业设计的价值,从来不在代码多炫,而在你能否把每一行代码背后的决策讲清楚。希望帮到你。
本文还有配套的精品资源,点击获取