
简介这是一套面向JavaWeb初学者与课程设计需求的超市收银系统源码由狂神说团队开发适合作为JavaWeb综合练习项目或毕业设计参考。项目采用MVC设计模式将业务逻辑、数据访问与用户界面分离涵盖收银、商品库存与销售数据管理等核心功能并借助Maven管理依赖结构清晰、便于二次开发。压缩包共123个文件约561KB其中Java源文件33个承担主要业务逻辑另有21个jsp页面、23个js与5个css文件负责前端交互与样式18个png、4个jpg、3个gif等图像资源丰富界面表现9个xml及properties、sql等文件用于配置与数据库初始化。目前已有551人学习下载。读者可从中获得完整的项目目录结构、数据库脚本与前后端代码实现理解MVC分层与JavaWeb开发流程适合对照练习与查漏补缺。1. 从一份 JavaWeb 收银系统源码说起它到底能跑通什么如果你手头正好有一套基于 Java 的超市收银系统源码第一反应大概率不是“我要学习”而是“这玩意儿能不能直接跑起来、能不能改成我自己的课设或小项目”。我拿到这套狂神说风格的超市收银系统设计源码时也是这个心态。它本质上是一个典型的 JavaWeb 单体应用覆盖了商品管理、收银结算、库存扣减、订单查询这几条主线技术栈大概率落在 Servlet/JSP 或 Spring Boot MyBatis 这一档数据库用 MySQL前端是 JSP 或 Thymeleaf 加一点原生 JS。适合谁适合正在做 Java 课程设计的学生、想找一个完整 CRUD 闭环练手的初级开发者以及需要一套能二次改造的收银业务骨架的从业者。它不解决高并发也不解决分布式事务但它能把“一个收银台从扫码到出小票”的完整链路摆在你面前。这一章不展开代码先把这套源码的边界和预期讲清楚后面几章再逐层拆开。2. 环境搭建与数据库初始化把项目从压缩包变成可登录页面2.1 JDK、Tomcat 与 MySQL 的版本对齐这套源码最常见的翻车点不是代码本身而是版本对不上。我一般会先看pom.xml或WEB-INF/lib下的依赖确认它是 Spring Boot 还是传统 SSM。如果是 Spring BootJDK 选 8 或 11 基本都能跑如果是传统 Servlet 项目Tomcat 选 8.5 或 9.0别上来就 Tomcat 10因为javax.servlet和jakarta.servlet的包名变更会让整个项目编译不过。MySQL 建议 5.7 或 8.08.0 要注意连接串里的时区和 SSL 参数。下面是我常用的环境检查命令先确认本机版本再动手。java -version mvn -v mysql --version逻辑说明这三条命令分别确认 JDK、Maven 和 MySQL 是否在 PATH 里。参数说明java -version看主版本mvn -v看 Maven 绑定的 JDKmysql --version看服务端版本。如果mvn -v显示的 JDK 和java -version不一致后面编译大概率出玄学问题先把JAVA_HOME对齐。2.2 建库建表与初始数据导入源码包里通常会带一个.sql文件名字可能是supermarket.sql或cashier.sql。我习惯先建一个空库再导入避免字符集问题。CREATE DATABASE cashier_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE cashier_db; SOURCE /path/to/supermarket.sql;逻辑说明第一句建库并指定utf8mb4防止商品名里的生僻字或emoji变成问号。第二句切库。第三句导入表结构和初始数据。参数说明SOURCE后面的路径写你实际存放 sql 文件的绝对路径Windows 下用正斜杠或双反斜杠。导入后执行SHOW TABLES;确认表数量一般会有user、product、order、order_item、inventory这几张核心表。2.3 配置文件里的连接串与端口找到application.properties或jdbc.properties把数据库连接改成你自己的。spring.datasource.urljdbc:mysql://localhost:3306/cashier_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse spring.datasource.usernameroot spring.datasource.password你的密码 server.port8080逻辑说明serverTimezone不写会在 MySQL 8.0 上报时区错误useSSLfalse避免本地开发时的证书警告。参数说明server.port如果 8080 被占用改成 8081 或 9090但要注意后续前端请求的 baseURL 也要同步改。改完后启动项目访问http://localhost:8080能看到登录页就说明环境这一关过了。3. 收银核心链路拆解从扫码到订单落库的代码走读3.1 商品查询与购物车暂存收银台的第一步是输入商品条码或名称查出商品信息并加入当前订单的临时列表。常见做法是前端用 AJAX 请求/product/query?codexxx后端返回 JSON前端把商品 push 进一个cartList数组。这里的关键是条码唯一性校验和库存预检查。GetMapping(/product/query) public Result queryByCode(RequestParam String code) { Product product productService.getByCode(code); if (product null) { return Result.fail(商品不存在); } if (product.getStock() 0) { return Result.fail(库存不足); } return Result.success(product); }逻辑说明先按条码查商品查不到直接返回失败查到后检查库存库存为 0 不允许加入购物车。参数说明code是前端传入的条码字符串Result是统一返回包装类包含code、msg、data三个字段。这段代码的边界在于如果两个收银员同时查同一件最后库存为 1 的商品都会通过检查真正扣减时要靠数据库行锁或乐观锁兜底后面避坑章会展开。3.2 结算与库存扣减的事务边界结算动作是整个系统里最不能省事务的地方。下单、写订单明细、扣库存、写支付记录这四步要么全成功要么全回滚。Transactional(rollbackFor Exception.class) public Order settle(SettleDTO dto) { Order order buildOrder(dto); orderMapper.insert(order); for (OrderItem item : dto.getItems()) { orderItemMapper.insert(item); int affected productMapper.reduceStock(item.getProductId(), item.getQuantity()); if (affected 0) { throw new RuntimeException(库存扣减失败商品ID item.getProductId()); } } paymentMapper.insert(buildPayment(order)); return order; }逻辑说明Transactional标注在 service 方法上任何一步抛异常都会回滚前面的插入。reduceStock的 SQL 里带AND stock #{quantity}条件返回受影响行数为 0 就说明库存不够主动抛异常触发回滚。参数说明rollbackFor Exception.class确保受检异常也回滚默认只回滚运行时异常。这段代码的坑在于如果reduceStock写成了先查再减并发下会超卖必须用一条带条件的 UPDATE 语句。3.3 订单号生成与小票数据组装订单号一般用时间戳加随机数或序列避免用自增 ID 直接暴露业务量。小票数据组装就是把订单主表和明细表拼成一个前端可渲染的结构。public String generateOrderNo() { return CS System.currentTimeMillis() String.format(%04d, new Random().nextInt(10000)); }逻辑说明前缀CS表示收银中间是毫秒时间戳后面补四位随机数。参数说明%04d保证随机数不足四位时前面补零。这个方案在单机低并发下够用如果多台收银机同时下单时间戳可能重复常见做法是再加机器标识或改用 Redis 自增。小票组装就是把order和orderItems放进一个TicketVO前端用表格渲染后调用打印组件。4. 避坑与排查这套源码最容易翻车的五个地方4.1 中文乱码现象是商品名显示问号原因是字符集不统一现象登录后商品列表里的中文全部变成???或乱码。原因数据库、表、连接串、JSP 页面编码不一致。解决数据库建库时用utf8mb4连接串加characterEncodingutf8JSP 页面顶部加% page contentTypetext/html;charsetUTF-8 %Tomcat 的server.xml里 Connector 加URIEncodingUTF-8。四处对齐后重启基本能解决。4.2 库存超卖现象是库存扣成负数原因是先查后减现象两笔订单同时结算最后库存变成 -1。原因代码里先SELECT查库存再UPDATE扣减两个线程都查到 1都执行扣减。解决把扣减改成UPDATE product SET stock stock - #{qty} WHERE id #{id} AND stock #{qty}根据返回行数判断是否成功。这是血泪经验课设答辩时被问到并发直接翻车。4.3 事务不生效现象是抛异常后订单还在原因是自调用现象结算方法里抛了异常但订单表里已经插入了数据。原因Transactional方法被同一个类里的另一个方法直接调用走了this引用代理没生效。解决把事务方法抽到独立的 Service 里或者用AopContext.currentProxy()拿代理对象调用。更简单的做法是确保结算入口从 Controller 直接调 Service。4.4 登录拦截失效现象是不登录也能访问后台原因是拦截器路径没配全现象直接输入/admin/product/list能打开页面。原因拦截器只拦了/admin/*没拦/admin/**或者静态资源被误拦。解决拦截器配置里用/admin/**并排除/admin/login、/css/**、/js/**。排查时可以在拦截器里打日志看请求有没有进preHandle。4.5 时间字段差 8 小时现象是订单时间不对原因是时区没配现象下单时间是凌晨数据库里存的是前一天下午。原因JDK 默认时区和 MySQL 时区不一致或者连接串没写serverTimezone。解决连接串加serverTimezoneAsia/Shanghai实体类时间字段用java.util.Date或LocalDateTime时确认 JDBC 驱动版本支持。如果还不对检查 MySQL 的global time_zone设置。5. 二次改造与验证把这套收银系统变成你自己的项目5.1 加一个会员折扣模块的完整思路这套源码默认没有会员体系但收银场景里会员折扣是很自然的扩展点。我的做法是加一张member表字段包括id、phone、level、discount再在结算时根据手机号查会员把折扣应用到订单总价。关键改动点有三个结算 DTO 里加memberPhone字段Service 里查会员并计算折后价订单表加discount_amount和pay_amount两个字段。改完后用 Postman 或页面走一笔带会员的订单核对pay_amount total_amount * discount。BigDecimal discount BigDecimal.ONE; Member member memberService.getByPhone(dto.getMemberPhone()); if (member ! null) { discount member.getDiscount(); } BigDecimal payAmount totalAmount.multiply(discount).setScale(2, RoundingMode.HALF_UP);逻辑说明默认折扣为 1查到会员就替换。setScale(2, RoundingMode.HALF_UP)保证金额保留两位小数并四舍五入。参数说明discount存的是 0.95 这种小数不是 95。验证时重点看pay_amount和discount_amount是否对得上以及库存扣减是否仍然正确。5.2 用接口测试验证核心链路是否真的通页面点一遍只能证明“看起来能用”要确认事务和库存最好用接口测试跑一遍。我一般用 curl 或 Postman 模拟登录、查商品、结算、查订单四步。curl -X POST http://localhost:8080/login -d usernameadminpassword123456 -c cookie.txt curl -X GET http://localhost:8080/product/query?code6901234567890 -b cookie.txt curl -X POST http://localhost:8080/order/settle -b cookie.txt -H Content-Type: application/json -d {items:[{productId:1,quantity:2}]} curl -X GET http://localhost:8080/order/list -b cookie.txt逻辑说明第一条登录并保存 cookie第二条带 cookie 查商品第三条提交结算第四条查订单列表确认落库。参数说明-c保存 cookie-b携带 cookie-H指定 JSON 内容类型。跑完后去数据库执行SELECT stock FROM product WHERE id 1;确认库存从 10 变成 8再执行SELECT * FROM order ORDER BY id DESC LIMIT 1;确认订单存在。如果库存没变但订单有了说明事务边界有问题回头查Transactional是否生效。5.3 一个我每次改完必做的检查习惯从那以后我每次改完收银逻辑都强制走一遍“登录 → 查商品 → 加购物车 → 结算 → 查订单 → 查库存”这六步不管改动多小。因为收银系统的核心链路是环环相扣的改一个折扣计算可能影响库存扣减改一个订单号生成可能影响小票打印。这套源码的价值不在于它有多完美而在于它给了你一个能跑通的骨架你可以在上面加会员、加退货、加日结报表。希望帮到你。本文还有配套的精品资源点击获取