
简介基于JSPServlet架构的家用电器购物商城管理系统面向Java Web课程设计或毕业设计场景覆盖管理员和用户双角色包含商品管理、销售统计、购物车、在线付款等完整电商闭环。资源共255个文件压缩包约8.03MB主要包含42个Java源码、19个JSP页面、34个JavaScript脚本、CSS样式及SQL数据库脚本等目录结构清晰便于按模块参考学习。已有29人学习。借助该源码可理解Servlet请求处理、JDBC数据库操作、JSP页面渲染与LayuijQuery前端交互并掌握商品图片上传、订单状态跟踪、购物车结算等典型功能随包数据库脚本可直接导入MySQL验证运行。压缩包内含图片、字体等静态资源整体轻量适合初学者快速搭建项目骨架或作为毕业设计的基础版本。1. 为什么一个“jspservlet 的家用电器购物商城管理系统”还值得你动手做一遍一个人把“基于 jspservlet 的家用电器购物商城管理系统源码数据库”跑通是什么体验你不需要会 Spring Boot也不用理解自动配置那套魔法只靠 JSP 页面提交表单、Servlet 收参数、JDBC 读写 MySQL就能把登录、商品列表、购物车、下单、订单列表这些商城功能一个个点亮。对正在找 Java Web 课程设计或毕业设计的人来说这是成本最低、也最容易向答辩老师讲清楚的项目。对老项目维护者来说它也能帮你补上被框架遮住的那层底层请求链路。接下来我把这套源码怎么落地、关键参数怎么调、坑在哪里按实操顺序拆给你看。2. 技术选型与工程结构为什么还有项目在用 jspservlet 而不是 Spring Boot2.1 选型理由Servlet 做控制层JSP 做视图JDBC 做持久化现在用 Spring Boot 搭一个商城可能只要十几分钟但“基于 jspservlet 的家用电器购物商城管理系统”仍然大量出现在课程设计和毕业设计中。这不全是因为学校还按老教材教而是因为这个组合把 Web 应用最原始的那条链路完整暴露在你面前浏览器发请求 → Tomcat 根据 URL 找到 Servlet → Servlet 调用 DAO → DAO 查询数据库 → 结果放进 request 或 session → 转发到 JSP 渲染。每一步都发生在你眼皮底下不需要猜测框架替你做过了什么。有人会觉得它“土”但从学习角度看它其实是最好的底层教材。你见过 Spring MVC 的RequestMapping之后再回来看 web.xml 里的 servlet-mapping就会明白注解也好、自动配置也好底层都是把 URL 和一个处理类绑在一起。jspservlet 这套方案里控制层就是一个个 Servlet视图层就是 JSP 页面没有 AOP、没有依赖注入黑匣子。你在 doGet 里打断点能看到 HttpServletRequest 完整生命周期这对排查老项目和参加答辩都很有帮助。对于家电这个品类商品规格相对固定不需要像服装那样做 SKU购物车、下单的流程也不复杂用这套方案不会出现“代码写不下”的问题。数据库访问层的常见做法是写一个 DBUtil 工具类内部用 JDBC 的 Connection、PreparedStatement、ResultSet 完成增删改查。很多“源码数据库”的项目包里记录的是老写法Class.forName(com.mysql.jdbc.Driver); Connection conn DriverManager.getConnection(url, username, password);这段代码能跑但不适合生产环境。每次数据库操作都会新建物理连接Tomcat 重启、访问量稍大连接数就会超。如果只是交课程设计先保留它是可以的但后面想部署得稳定建议改成 c3p0 或 HikariCP 连接池。这样改不影响 DAO 层代码只改 DBUtil 的 getConnection 方法五分钟就能换完。2.2 工程目录与 web.xml先看懂源码从哪下手拿到 jspservlet 商城源码第一步不是启动 Tomcat而是用编辑器的文件树看一下目录。典型结构如下HomeApplianceShop/ ├── sql/ │ └── shop.sql ├── src/com/shop/ │ ├── servlet/ # 控制层LoginServlet、GoodsServlet、CartServlet │ ├── dao/ # 数据访问层UserDao、GoodsDao、OrderDao │ ├── entity/ # 实体类User、Goods、Order │ ├── filter/ # 登录拦截过滤器 │ └── util/ # DBUtil、MD5Util、验证码工具 └── WebContent/ ├── admin/ # 后台管理页面 ├── user/ # 用户个人信息、我的订单 ├── upload/ # 商品图片上传目录 ├── goods_list.jsp ├── login.jsp └── WEB-INF/web.xmlWebContent 就是发布到 Tomcat 的根目录。注意 WEB-INF 下面的资源有特殊规则浏览器不能直接通过 URL 访问 WEB-INF 里的 JSP只能由 Servlet 转发进入。所以 login.jsp 如果放在 WebContent 根目录访问/项目名/login.jsp没问题但 index.jsp 如果放在 WEB-INF 内部就必须先经过一个 Servlet 的 getRequestDispatcher。这个规则是很多新手把界面改成 404 的原因。web.xml 是整个项目最早的“路由器”。老版本项目里没有WebServlet注解全靠它配置映射。下面是最小配置?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee version3.1 display-nameHomeApplianceShop/display-name welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list session-config session-timeout30/session-timeout /session-config servlet servlet-nameLoginServlet/servlet-name servlet-classcom.shop.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern /servlet-mapping /web-app这里有三件事需要理解。第一welcome-file 决定访问根路径时先显示 login.jsp想用商品列表当首页把它改成goods_list.jsp或对应 Servlet 的 URL 即可。第二session-timeout 单位是分钟课程演示设 30 分钟既不会让评委等太久也不会频繁重复登录。第三servlet-mapping 里的 url-pattern 是虚拟路径表单里的actionlogin、地址栏里的/login都会进入 LoginServlet。注意它不带项目前缀重定向和转发时又是另一套规则重定向必须带req.getContextPath()转发则使用相对项目根的路径。这个老项目里还有一个常见页面叫“用户个人信息展示页面”在user/info.jsp里展示当前登录用户的信息和头像。很多人在这个页面踩了“jsp 图片如何对坐标定位”的坑硬调 CSS 位置结果发现是图片 URL 不对。比如img srcupload/avatar.jpg这种相对路径在http://localhost:8080/shop/user/info.jsp下会解析成http://localhost:8080/shop/user/upload/avatar.jpg当然找不到。解决方式是用 JSP 的 EL 表达式补上根路径img src${pageContext.request.contextPath}/upload/avatar.jpg /这比任何坐标定位都可靠。图片位置由 width、height 和 CSS 控制和 URL 无关。记住这一点整个项目里的图片坑会少一半。3. 数据库设计与初始化家电商品、用户订单四张表怎么建3.1 表结构设计字段、主键、外键关系基于 jspservlet 的商城系统数据库通常不会设计得太复杂。核心就是四张表用户表、分类表、商品表、订单表。订单表还会拆成订单主表和订单项表。下面这套建表 SQL 是这个品类最常见的形态可以直接拿它初始化数据库CREATE DATABASE IF NOT EXISTS shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE shop; CREATE TABLE user ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, phone VARCHAR(20), address VARCHAR(255), created_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE category ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL ); CREATE TABLE goods ( id INT AUTO_INCREMENT PRIMARY KEY, category_id INT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, pic VARCHAR(255), description TEXT, status TINYINT DEFAULT 1, sales INT DEFAULT 0, KEY idx_category (category_id) ); CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, order_no VARCHAR(32) NOT NULL, user_id INT NOT NULL, total_price DECIMAL(10,2), status TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_order_no (order_no) ); CREATE TABLE order_item ( id INT AUTO_INCREMENT PRIMARY KEY, order_id INT NOT NULL, goods_id INT NOT NULL, goods_name VARCHAR(100), price DECIMAL(10,2), quantity INT );这套设计里有两个容易被忽略的点。第一价格字段必须用 DECIMAL(10,2)不要用 FLOAT 或 DOUBLE。家电价格很多是 1999.00 这种精确值float 存入数据库后显示成 1998.999999后端一相加就会出怪事DECIMAL 是定点数做金额计算不会丢精度。第二我没有给表之间加物理外键约束只加了 KEY 索引。很多老项目也这么做原因很简单课程设计里删除商品、删除分类很频繁物理外键会把删除操作卡死用逻辑外键应用层自己保证引用关系效果一样但整个操作不会因为外键报错。订单主表和订单项表为什么拆开因为一个订单可以包含多件商品一张表根本没法把多条商品信息存进一行。订单项的 goods_name 和 price 字段是冗余的下单时把商品的名称和价格快照保存进去之后就算商品改名、改价历史订单仍然保留原来的信息。这一点在答辩时是加分项很多同学只存一个 goods_id结果商品一删订单明细立刻成谜。3.2 初始化演示数据和修改表结构几个必会的 SQL拿到源码附带的 sql 文件后一般分两步验证。第一步用命令行导入检查脚本本身有没有语法错误mysql -uroot -p shop sql/shop.sql第二步进数据库查字符集和关键表是否存在。家电商城为了做列表展示需要一些像样的演示数据比如电视、冰箱、洗衣机、空调四个分类每个分类下放两三个品牌商品。初始化插入语句大致是INSERT INTO category (name) VALUES (电视), (冰箱), (洗衣机), (空调); INSERT INTO goods (category_id, name, price, stock, pic, description, status, sales) VALUES (1, 55英寸 4K 智能电视, 2399.00, 50, tv_55.jpg, 支持 HDR 和语音遥控, 1, 120), (1, 65英寸 OLED 电视, 5499.00, 30, tv_65.jpg, 超高对比度影院级体验, 1, 80), (2, 双门冰箱 501L, 2899.00, 40, fridge_501.jpg, 一级能效, 1, 95), (3, 滚筒洗衣机 10kg, 2299.00, 35, wash_10kg.jpg, 变频安静, 1, 72);注意每个商品的 category_id 要和 category 表对应。如果导入后想调整表结构比如给商品表补一个销量字段做排序或者发现建表脚本没有 utf8mb4可以用 MySQL 常用命令直接改ALTER TABLE goods CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE goods ADD COLUMN sales INT DEFAULT 0;这是“mysql 数据库修改结构”的常见操作比删表重建更稳妥不会把已有商品数据丢掉。改完字符集记得执行SHOW CREATE TABLE goods;确认生效否则可能还在用旧配置。数据库层的最后一块就是 DAO 的增删改查。jspservlet 商城里的 DAO 类本质就是对上面四张表做最基本的 SELECT、INSERT、UPDATE、DELETE。比如 UserDao 里有 findByUsernameAndPasswordGoodsDao 里有 findGoodsByPage、updateStockOrderDao 里有 insertOrder、insertOrderItem。查询用 PreparedStatement 防止 SQL 注入列表查询用LIMIT ?, ?做分页。注意 PreparedStatement 的占位符下标从 1 开始很多“Parameter index out of range”报错都是这里写错数字。4. 核心模块实现登录、商品列表、购物车、下单的 Servlet 路由4.1 用户模块登录校验与 Session 状态用户模块是商城所有功能的前置条件。登录流程在控制层里最典型JSP 表单把用户名密码 POST 给 ServletServlet 校验成功就写 Session失败就带着错误信息转发回登录页。一个常见的 LoginServlet 核心方法如下WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password MD5Util.md5(req.getParameter(password)); UserDao dao new UserDao(); User user dao.findByUsernameAndPassword(username, password); if (user null) { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } else { HttpSession session req.getSession(); session.setAttribute(loginUser, user); if (admin.equals(username)) { resp.sendRedirect(req.getContextPath() /admin/goods_list.jsp); } else { resp.sendRedirect(req.getContextPath() /goods_list); } } } }解释一下参数和作用。req.setCharacterEncoding(UTF-8)必须放在读取参数之前负责把 POST 里的中文请求体按 UTF-8 解码漏掉的话用户名里的中文会直接乱掉。密码入库前用 MD5 处理虽然 MD5 已经不算安全但课程设计里大部分源码还是这么做更好的方案是加盐比如在密码前后拼接一个固定盐再做摘要。校验失败时用 forward 而不是 sendRedirect这样 request 里的 msg 能在 login.jsp 里通过${msg}显示地址栏也不会变成唯一的失败页。登录成功后用 sendRedirect 跳转防止刷新浏览器时表单被重复提交。还要注意一个细节重定向的 URL 必须带req.getContextPath()。如果项目部署名是 /shop那么重定向路径/goods_list最终会是/shop/goods_list直接写/goods_listTomcat 会去根路径找必然 404。很多新手在这里翻车跳转后白屏。4.2 商品列表与详情Servlet 计算分页JSP 展示数据商品列表页是商城最核心的展示页面也是分页查询的标准范例。Servlet 从请求里拿到 pageNo按 pageSize 查询当前页数据和总数再把结果放进 request 转发给 JSPWebServlet(/goods_list) public class GoodsListServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int pageNo 1; int pageSize 12; String pageNoStr req.getParameter(pageNo); if (pageNoStr ! null !pageNoStr.isBlank()) { pageNo Integer.parseInt(pageNoStr); if (pageNo 1) pageNo 1; } GoodsDao dao new GoodsDao(); int totalCount dao.countGoods(); int totalPage (int) Math.ceil(totalCount * 1.0 / pageSize); if (pageNo totalPage) pageNo Math.max(1, totalPage); ListGoods list dao.findGoodsByPage(pageNo, pageSize); req.setAttribute(list, list); req.setAttribute(totalPage, totalPage); req.setAttribute(pageNo, pageNo); req.getRequestDispatcher(/goods_list.jsp).forward(req, resp); } }pageSize 我固定成 12因为客厅家电的卡片做成四列12 正好能被 4 整除展示起来整齐。totalPage 用Math.ceil(totalCount * 1.0 / pageSize)计算乘以 1.0 是为了避免整数除法直接把余数吞掉。页码越界虽然是小概率但要在 Servlet 内部先做钳制否则用户手动把 pageNo 改成 999JSP 的分页按钮就会错位。查询部分对应 SQL 是SELECT * FROM goods WHERE status 1 ORDER BY id LIMIT ?, ?;注意第一个参数是偏移量(pageNo - 1) * pageSize不是页码。很多源码在这里少减一个 1导致第二页重复显示第一页的商品。商品列表 JSP 展示的时候图片路径也值得统一处理。下面的片段是一个比较稳的写法c:forEach items${list} varg div classgoods-item img src${pageContext.request.contextPath}/upload/${g.pic} width200 height200 alt${g.name}/ h3${g.name}/h3 p${g.price}/p a href${pageContext.request.contextPath}/cart?actionaddgoodsId${g.id}加入购物车/a /div /c:forEach这里已经用${pageContext.request.contextPath}把项目前缀补上了部署到任意上下文名都能正常显示。关于“jsp 图片如何对坐标定位”这类困惑真正关键的其实只有两点一是固定 img 的 width/height 或外层容器尺寸二是确保图片 URL 能拼出真实存在的文件路径。定位用 CSS 的 flex 就行和图片坐标没有关系。4.3 购物车与下单Session 购物车如何落库并扣库存购物车在这个项目里最常见的实现是放在 Session 里用Map商品Id, 数量表示。好处是无需额外建表坏处是用户关浏览器购物车就消失所以必须在“去结算”时把 Session 购物车一次性写入数据库订单。加购的 Servlet 核心代码WebServlet(/cart) public class CartServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); HttpSession session req.getSession(); MapInteger, Integer cart (MapInteger, Integer) session.getAttribute(cart); if (cart null) { cart new HashMap(); } if (add.equals(action)) { int goodsId Integer.parseInt(req.getParameter(goodsId)); cart.merge(goodsId, 1, Integer::sum); } if (clear.equals(action)) { cart.clear(); } session.setAttribute(cart, cart); resp.sendRedirect(req.getContextPath() /cart.jsp); } }MapInteger, Integer的 value 就是购物车数量用merge(goodsId, 1, Integer::sum)这种写法同一件商品再次加购时把已有数量加 1比先 get 再 put 更简洁。如果源码里用的是 JDK 7 老写法你也会看到if (cart.get(goodsId) null) cart.put(goodsId, 1); else cart.put(goodsId, cart.get(goodsId) 1);思路一样。Session 购物车不需要立刻查数据库判断库存生鲜电商那种强一致场景在课程设计里遇不到真正扣库存的校验要放在提交订单事务里。提交订单是整个系统最关键的转折点多张表要同步变更。jspservlet 里的标准做法是在 DAO 层拿到同一个 Connection 手动控制事务Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); OrderDao orderDao new OrderDao(); orderDao.insertOrder(conn, order); // 插入订单主表 for (CartItem item : cartItems) { orderDao.insertOrderItem(conn, item); // 插入订单项 orderDao.decreaseStock(conn, item.getGoodsId(), item.getQuantity()); } conn.commit(); // 全部成功才提交 } catch (Exception e) { conn.rollback(); // 任何一步失败全部回滚 throw e; } finally { conn.setAutoCommit(true); DBUtil.close(conn); }这里必须理解为什么要传同一个 Connection如果每个 DAO 方法内部单独 getConnection那么订单主表成功、订单项失败两个连接各自提交就产生了残废订单。手动事务的全部意义在于让“插入订单插入明细扣库存”变成要么全成功、要么全失败。扣库存的 SQL 别写成先 select stock 再 update尽量用一条原子语句UPDATE goods SET stock stock - ? WHERE id ? AND stock ?;如果stock ?不成立这条 SQL 影响行数为 0DAO 层检查到 0 就抛异常触发整体回滚。这样即使两个人同时下单也不会把库存扣成负数。做完这一步把 Session 购物车清空用户再刷新购物车页面就会看到空车订单已出现在“我的订单”里。5. 部署与避坑从 Tomcat 本地跑通到能交作业的检查单5.1 部署步骤JDK、Tomcat、MySQL 版本怎么配jspservlet 商城系统的部署比 Spring Boot 多不少手工步骤但顺序固定。常见做法是 JDK 8 Tomcat 8.5/9 MySQL 5.7/8.0这是兼容性最好的组合。第一次跑通整个流程是在 MySQL 里建库并导入 sql 脚本mysql -uroot -p shop sql/shop.sql。打开 DBUtil 或 db.properties把数据库账号、密码改成你本机的值。把整个工程目录拷到 Tomcat 的 webapps 下或者用 IDEA/Eclipse 导出 war 包放入 webapps。启动 Tomcat访问http://localhost:8080/项目名/login.jsp。Tomcat 的启动和停止脚本在 bin 目录下macOS/Linux 用 startup.sh / shutdown.shWindows 用 startup.bat / shutdown.bat# Linux/macOS $CATALINA_HOME/bin/startup.sh $CATALINA_HOME/bin/shutdown.sh # Windows # %CATALINA_HOME%\bin\startup.bat # %CATALINA_HOME%\bin\shutdown.bat数据库连接参数一般集中在 src 下的 db.properties 或 util/DBUtil.java 里。最常见配置是jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/shop?useUnicodetruecharacterEncodingUTF-8useSSLfalse jdbc.usernameroot jdbc.password123456这里有两个参数会直接决定项目能不能跑。第一个是 driver 类名老项目里写的是com.mysql.jdbc.Driver这个驱动在 MySQL 8 里已经废弃自动迁移会打警告想省心就换成com.mysql.cj.jdbc.Driver。第二个是characterEncodingUTF-8保证从数据库读出来的中文不乱码useSSLfalse 主要是让 MySQL 8 不再打印 SSL 警告。如果编码还乱同时检查 JSP 页面头部的pageEncodingUTF-8和 Tomcat 的 server.xml 中 Connector 的 URIEncoding最终做到三处都是 UTF-8。5.2 常见问题与排查5 个最经常翻车的位置这 5 个坑是我在带课程设计和维护老项目时遇到最多的现象基本覆盖了“源码数据库”这套组合 90% 的报错现场。第一个“HTTP 500 java.sql.SQLException: Access denied for user”。现象是登录页或列表页直接 500控制台打印数据库连接失败。原因几乎都是 db.properties 里的用户名密码没改成你本机的或者是 root 账号只允许 localhost 登录而你用了远程 IP。解决把配置改成实际可用的账号或者给 root 加root%权限课程设计用 localhost 即可。第二个登录成功后跳转到http://localhost:8080/goods_list而不是http://localhost:8080/项目名/goods_list然后 404。原因非常典型重定向时用了/goods_list而不是req.getContextPath() /goods_list。解决全局搜索sendRedirect(/把所有以斜杠开头的跳转都加上 getContextPath。注意转发getRequestDispatcher(/goods_list.jsp)不需要加因为它是服务端内部行为。第三个商品列表图片全部裂开控制台显示 upload/tv_55.jpg 404。原因是用相对路径upload/xxx.jpg时当前 URL 的子路径被拼进去实际请求跑到了/user/upload/xxx.jpg。解决统一改成${pageContext.request.contextPath}/upload/xxx.jpg并确认 upload 目录真的在 WebContent 下。如果图片是后台上传的还要检查上传时有没有把文件名真实写入数据库。第四个页面全部乱码包括中文商品名和用户地址。原因是编码不一致常见的是 JSP 用 UTF-8 而数据库连接没加 characterEncoding或者建表时用了 latin1。解决依次检查 SQL 文件头、数据库表字符集、JDBC URL 和 JSP pageEncoding统一成 UTF-8已经乱掉的数据可以用第 3 章的 ALTER TABLE CONVERT 修复但已经插入的脏数据可能要重导。第五个用 JDK 17 启动老 Tomcat报NoClassDefFoundError或java.lang.IllegalAccessError项目直接起不来。原因是老 Tomcat 8.5 基于 JDK8 编译遇到 JDK17 的模块化限制会翻车。解决装一个 JDK 8 设置成 JAVA_HOME或者升级到 Tomcat 9/10 对应新 Servlet 规范同时注意 JSP 的依赖包版本也要跟着升级。技术上的坑基本都是这个套路先看日志再查编码和路径最后确认环境版本。老项目只要版本匹配很快就能跑起来。6. 进阶技巧给系统加一个图形验证码顺便把订单号做得更可读跑通上面的部署后我的习惯是再花半小时补两个小功能图形验证码和可读订单号。先说验证码它能在登录、注册接口挡住批量脚本。原理是写一个 Servlet在 doGet 里生成 4 位随机字符画进 BufferedImage输出成 image/jpeg同时把字符存进 Session。JSP 页面用img src${pageContext.request.contextPath}/verifyCode就把它当成普通图片加载。关键生成片段如下String code ; for (int i 0; i 4; i) { code 23456789ABCDEFGHJKLMNPQRSTUVWXYZ.charAt( (int) (Math.random() * 36)); } req.getSession().setAttribute(verifyCode, code);然后登录逻辑里把用户输入和 Session 里的值用 equalsIgnoreCase 比较不区分大小写失败就转发回登录页并提示“验证码不正确”。随机字符去掉了 0、O、1、I是为了减少肉眼误读。生成图片时固定宽 80、高 30字体用 Arial 加粗保证答辩时能看清。再看订单号。默认的时间戳加随机数当然也行但加上用户和序号会更有辨识度String orderNo new SimpleDateFormat(yyyyMMddHHmmss).format(new Date()) userId String.format(%03d, (int) (Math.random() * 1000));这样的订单号后台能直接看出下单时间和用户。数据库已经为 order_no 建了唯一索引随机三位重复概率很小万一撞了会在插入时报 DuplicateKey捕获后重新生成一次即可。我当年因为没加验证码演示前一晚被脚本刷爆数据库所以现在每个系统入口都会补上这道防线。这两个改动不依赖任何新框架纯 jspservlet 就能做也正好把你的项目从“能跑”推到“能看”。希望帮到你。本文还有配套的精品资源点击获取