简介这份资源是一套基于 JavaWeb 的超市收银系统完整源码面向计算机专业学生、JavaWeb 初学者及需要课程设计或毕业设计参考的开发者帮助解决收银结算、库存管理与销售统计等实际业务场景的实现问题。压缩包共 147 个文件约 1.61MB以 60 个 Java 源文件与 29 个 JSP 页面为主体配合 22 个 MyBatis 映射文件、16 个 CSS 与 7 个 JavaScript 前端资源另有 SQL 建表脚本、Maven 配置及说明文档结构完整、层次清晰。系统覆盖商品增删改查与库存预警、订单生成查询与多支付方式、多角色用户登录与权限管理以及销售数据统计分析等模块前端引入 Bootstrap 组件库便于二次开发与界面调整。目前已有 97 人学习下载适合作为 JavaWeb 综合实践的学习范本读者可据此理解 MVC 分层设计、数据库映射与前后端交互的完整实现思路。1. 超市收银系统这套 JavaWeb 源码拿到手到底能跑出什么很多人拿到「基于 JavaWeb 的超市收银系统」这类源码包第一反应是双击解压、找 main 方法、直接 run然后发现根本跑不起来——没有数据库、没有依赖、Tomcat 版本对不上折腾两小时连登录页都见不到。这不是你技术不行是 JavaWeb 项目和 Spring Boot 项目的启动逻辑完全不是一回事。JavaWeb 项目本质是「Servlet 容器 WAR 包」的模型它没有内嵌服务器你得自己配 Tomcat、自己建库、自己导 SQL少一步就是 404 或 500。这套源码能解决的核心问题很具体它把超市收银场景里最高频的几个动作——商品扫码录入、会员折扣计算、库存扣减、小票结算、日结报表——用 JSP Servlet JDBC 串成了一条完整链路。适合谁适合正在做课程设计的学生、想从「只会写 CRUD 接口」过渡到「理解请求怎么从浏览器走到数据库再走回来」的初中级开发者以及需要一套能改能扩的收银业务底座的独立开发者。它不适合想直接上生产的人因为收银系统涉及金额计算和并发扣库存源码级项目在这两块通常只做了演示级处理后面我会专门讲怎么补。2. 从 WAR 包到能登录环境搭建与数据库初始化2.1 先搞清楚这套 JavaWeb 项目的目录结构拿到源码包解压后你看到的目录大概率长这样src下按包名分层通常是com.xxx.entity、com.xxx.dao、com.xxx.servlet、com.xxx.utilWebContent或web目录下放WEB-INF、jsp页面、css/js静态资源和lib依赖 jar。这里有个新手最容易翻车的点WEB-INF目录下的 JSP 是不能被浏览器直接访问的必须通过 Servlet 转发如果你发现某个页面 404先确认它是不是被放在了WEB-INF里面。lib目录里的 jar 包决定了你能不能编译通过。常见的有mysql-connector-java、jstl、servlet-api、commons-dbutils或druid。注意servlet-api这个 jar 在 Tomcat 运行时是 provided 状态如果你把它打进 WAR 的WEB-INF/lib启动时可能报类冲突。我一般会先看lib里有没有servlet-api.jar有就把它从构建路径里移除只保留编译期引用。2.2 IDEA 里配置 Tomcat 并跑通第一个 JSP用 IDEA 跑 JavaWeb 项目的完整流程如下。先File - Project Structure - Modules确认src被标记为 Sources RootWebContent被标记为 Web Resource Directory并且web.xml的路径指向正确。然后在Artifacts里新建一个Web Application: Exploded输出目录默认即可。接着配置 Tomcat。Run - Edit Configurations - - Tomcat Server - Local在Server标签页指定你本地的 Tomcat 安装目录在Deployment标签页点把刚才的 Artifact 加进去Application context 建议设成/cashier这样访问路径就是http://localhost:8080/cashier/。# 确认 Tomcat 版本与项目 servlet-api 匹配 # 老项目常见 Tomcat 8.5 Servlet 3.1别硬上 Tomcat 10 cd /path/to/tomcat/conf grep -r Connector port server.xml # 默认 8080被占用就改成 8081启动后如果看到 Tomcat 日志里Deployment of web application archive成功且浏览器能打开登录页说明容器层通了。如果报ClassNotFoundException: com.mysql.jdbc.Driver说明驱动 jar 没进WEB-INF/lib或者版本太老换成mysql-connector-java-8.0.x并把驱动类名改成com.mysql.cj.jdbc.Driver。2.3 建库、导 SQL、改连接参数这三步一个都不能少数据库是这套系统能不能登录的关键。源码包里通常会有一个.sql文件里面包含建库语句、建表语句和初始数据。常见表有user收银员账号、product商品信息、stock库存、sale_order销售单、sale_item销售明细、member会员。-- 先建库字符集用 utf8mb4否则中文商品名会乱码 CREATE DATABASE cashier_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE cashier_db; -- 典型的商品表结构注意 price 用 decimal 不要用 float CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, barcode VARCHAR(50) UNIQUE NOT NULL, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 0, category_id INT ); -- 初始收银员账号密码字段看源码是明文还是 MD5 INSERT INTO user (username, password, role) VALUES (admin, 123456, cashier);导入完成后找到源码里的数据库连接配置。JavaWeb 项目一般有两种写法一种是在src下有个db.properties或jdbc.properties另一种是直接在DBUtil.java里硬编码。找到后把 URL、用户名、密码改成你本地的。# db.properties 典型内容 jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/cashier_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你的密码注意serverTimezone这个参数在 MySQL 8 驱动里必须加否则启动时报The server time zone value is unrecognized。这是 JavaWeb 老项目迁移到新驱动时最高频的坑。改完配置重启 Tomcat用初始账号登录。如果登录后跳转正常但页面样式全丢检查css/js路径是不是用了相对路径而你的 context path 不是/把页面里的${pageContext.request.contextPath}补上即可。3. 收银核心链路扫码、算价、扣库存、出小票3.1 扫码录入到购物车的数据流是怎么走的收银台的核心交互是收银员在输入框里扫条码或手输条码前端发一个请求到 ServletServlet 调 DAO 查商品查到后把商品加入当前会话的购物车前端刷新列表。这条链路看着简单但涉及三个关键设计点。第一购物车存哪里。常见做法是存在HttpSession里用一个ListCartItem或MapString, CartItem。用 Map 的好处是同一条码重复扫码时可以直接累加数量不用遍历。第二条码查询要加索引product表的barcode字段必须建唯一索引否则商品多了以后每次扫码都全表扫描。第三扫码请求要用 POST 而不是 GET因为条码可能包含特殊字符GET 的 URL 编码容易出问题。// ScanServlet 核心逻辑省略异常处理 protected void doPost(HttpServletRequest req, HttpServletResponse resp) { String barcode req.getParameter(barcode); ProductDao dao new ProductDao(); Product p dao.findByBarcode(barcode); if (p null) { resp.getWriter().write({\code\:404,\msg\:\商品不存在\}); return; } HttpSession session req.getSession(); MapString, CartItem cart (MapString, CartItem) session.getAttribute(cart); if (cart null) { cart new LinkedHashMap(); session.setAttribute(cart, cart); } CartItem item cart.get(barcode); if (item null) { item new CartItem(p, 1); cart.put(barcode, item); } else { item.setQty(item.getQty() 1); } resp.getWriter().write({\code\:200,\name\:\ p.getName() \}); }这段代码里LinkedHashMap保证购物车按扫码顺序展示CartItem里存商品快照和数量。参数说明barcode是前端传的条码qty是数量。失败时看什么如果返回 404 但你确认数据库有这条记录检查findByBarcode的 SQL 是不是用了like而不是或者条码字段有没有前后空格。3.2 金额计算为什么必须用 BigDecimal这是收银系统里最不能省的一步。很多源码为了省事用double算金额结果 0.1 0.2 得到 0.30000000000000004小票上就会出现「合计 19.999999」这种鬼东西。金额计算必须用BigDecimal而且要用String构造器不能用double构造器。// 正确的金额计算方式 BigDecimal total BigDecimal.ZERO; for (CartItem item : cart.values()) { BigDecimal price item.getProduct().getPrice(); // 已经是 BigDecimal BigDecimal qty new BigDecimal(item.getQty()); total total.add(price.multiply(qty)); } // 会员折扣比如 9 折 BigDecimal discount new BigDecimal(0.9); BigDecimal payable total.multiply(discount).setScale(2, RoundingMode.HALF_UP);参数说明setScale(2, RoundingMode.HALF_UP)表示保留两位小数并四舍五入这是收银场景的标准做法。RoundingMode不要用HALF_DOWN否则 0.125 会变成 0.12顾客会跟你吵架。如果源码里用的是double建议你直接重构这块别心存侥幸金额算错是收银系统的致命伤。3.3 结算时扣库存的并发问题与两种解法结算动作要在一个事务里完成三件事插入销售单、插入销售明细、扣减库存。前两步是纯插入问题不大第三步扣库存是并发重灾区。如果两个收银台同时卖最后一件商品不做控制就会出现超卖。// 方案一悲观锁在查询库存时就加行锁 String sql SELECT stock FROM product WHERE id ? FOR UPDATE; // 方案二乐观锁用版本号或库存条件更新 String updateSql UPDATE product SET stock stock - ? WHERE id ? AND stock ?; int rows update(updateSql, qty, productId, qty); if (rows 0) { throw new RuntimeException(库存不足扣减失败); }我一般会推荐方案二因为收银场景并发量不算极端乐观锁足够用而且不用长时间持有数据库行锁。参数说明stock ?这个条件保证了不会扣成负数rows 0就是扣减失败的信号此时要回滚整个事务并提示收银员。失败时看什么如果频繁出现扣减失败但库存明明够检查是不是有未提交的事务在锁行或者连接池配置的隔离级别有问题。3.4 小票生成从 JSP 渲染到打印样式适配小票本质是一段格式化的 HTML用 JSP 渲染后调浏览器打印。关键不在技术在样式。小票打印机通常是 58mm 或 80mm 热敏纸宽度固定所以 CSS 要用media print控制字体用等宽字体边距要压到最小。media print { page { size: 58mm auto; margin: 0; } body { width: 58mm; font-family: Courier New, monospace; font-size: 12px; } .no-print { display: none; } }参数说明size: 58mm auto告诉浏览器纸张宽度auto表示高度自适应。.no-print用来隐藏打印按钮本身。如果打印出来内容被截断检查是不是body有默认 margin或者商品名太长没有换行。常见做法是给商品名加word-break: break-all。4. 避坑与排查这套源码最容易翻车的五个地方4.1 中文乱码从请求到响应到数据库的三层排查现象商品名显示成??????或手机。原因通常有三层请求体编码、响应编码、数据库连接编码。解决顺序是先看数据库SHOW VARIABLES LIKE character%确认character_set_server是utf8mb4再看连接 URL 有没有characterEncodingutf8最后在 Servlet 里加req.setCharacterEncoding(UTF-8)和resp.setContentType(text/html;charsetUTF-8)。三层都对了乱码必消。4.2 登录后 Session 丢失不是代码问题是 Cookie 路径问题现象登录成功跳转到主页但主页又跳回登录页。原因多半是 Session 的 Cookie 路径和你的 context path 不一致。比如你的应用部署在/cashier但 Cookie 的 path 是/某些容器下会出问题。解决在web.xml里配session-config或者检查 Tomcat 的context.xml有没有改过sessionCookiePath。另一个可能是浏览器禁了 Cookie换无痕窗口试一下就能排除。4.3 商品条码重复扫码后数量不累加现象同一商品扫两次购物车里出现两条记录而不是数量变成 2。原因购物车用了List且每次扫码都add新对象没有做去重判断。解决把购物车改成MapString, CartItemkey 用条码扫码时先get再决定是新建还是累加。如果源码用的是 List找到add的地方改成先遍历查找。4.4 结算时报事务未回滚导致库存扣了但订单没生成现象库存少了但销售单表里查不到记录。原因扣库存和插订单不在同一个事务里或者事务方法没有加TransactionalJavaWeb 项目通常用 JDBC 手动控制事务。解决在 Service 层用Connection.setAutoCommit(false)开启事务所有操作成功后commit任何一步异常就rollback。检查 DAO 里有没有自己关了连接关了连接事务就断了。4.5 Tomcat 启动报端口占用或 JDK 版本不匹配现象启动时Address already in use或Unsupported major.minor version。原因8080 被别的进程占了或者项目编译用的 JDK 版本高于 Tomcat 运行的 JDK。解决netstat -ano | findstr 8080找到占用进程杀掉或改端口JDK 版本用Project Structure - Project统一设成 1.8 或 11和 Tomcat 的JAVA_HOME保持一致。老 JavaWeb 项目用 JDK 8 最稳别硬上 17。5. 把这套源码改造成能用的收银底座三个进阶动作第一个动作是加一层 Service。源码里大概率是 Servlet 直接调 DAO业务逻辑散在 Servlet 里。你可以在中间加一个SaleService把「算价、扣库存、生成订单」封装成一个方法Servlet 只负责收参数和返回结果。这样以后要加「满减」「第二件半价」这类促销规则只改 Service 就行不用动 Servlet。第二个动作是给库存扣减加一个对账机制。每天日结时跑一条 SQL对比product.stock和「初始库存 - 销售明细汇总」不一致的就报警。收银系统最怕账实不符这个对账脚本能帮你提前发现问题。-- 库存对账找出账面库存和实际销售不符的商品 SELECT p.id, p.name, p.stock AS book_stock, p.init_stock - IFNULL(SUM(si.qty), 0) AS should_stock FROM product p LEFT JOIN sale_item si ON p.id si.product_id GROUP BY p.id HAVING p.stock should_stock;第三个动作是把 JSP 里的 Java 代码片段逐步替换成 JSTL 和 EL 表达式。源码里如果到处是% ... %维护起来很痛苦。替换成c:forEach和${item.name}之后页面会干净很多也更容易交给前端改样式。我自己的习惯是拿到任何一套 JavaWeb 源码先不急着改功能先把它在本地跑通、把数据库对一遍、把登录和结算两条链路走通然后再动第一行代码。这套收银系统源码的价值不在于它现在有多完善而在于它给了你一个真实的、能摸到的业务骨架你在这个骨架上补的每一刀都比从零写一个 CRUD 学到的多。希望帮到你。本文还有配套的精品资源点击获取