简介:这是一套面向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 Boot,JDK 选 8 或 11 基本都能跑;如果是传统 Servlet 项目,Tomcat 选 8.5 或 9.0,别上来就 Tomcat 10,因为javax.servlet和jakarta.servlet的包名变更会让整个项目编译不过。MySQL 建议 5.7 或 8.0,8.0 要注意连接串里的时区和 SSL 参数。下面是我常用的环境检查命令,先确认本机版本再动手。
java -version mvn -v mysql --version逻辑说明:这三条命令分别确认 JDK、Maven 和 MySQL 是否在 PATH 里。参数说明:java -version看主版本,mvn -v看 Maven 绑定的 JDK,mysql --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.url=jdbc:mysql://localhost:3306/cashier_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false spring.datasource.username=root spring.datasource.password=你的密码 server.port=8080逻辑说明:serverTimezone不写会在 MySQL 8.0 上报时区错误,useSSL=false避免本地开发时的证书警告。参数说明:server.port如果 8080 被占用,改成 8081 或 9090,但要注意后续前端请求的 baseURL 也要同步改。改完后启动项目,访问http://localhost:8080,能看到登录页就说明环境这一关过了。
3. 收银核心链路拆解:从扫码到订单落库的代码走读
3.1 商品查询与购物车暂存
收银台的第一步是输入商品条码或名称,查出商品信息并加入当前订单的临时列表。常见做法是前端用 AJAX 请求/product/query?code=xxx,后端返回 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,连接串加characterEncoding=utf8,JSP 页面顶部加<%@ page contentType="text/html;charset=UTF-8" %>,Tomcat 的server.xml里 Connector 加URIEncoding="UTF-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。解决:连接串加serverTimezone=Asia/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 "username=admin&password=123456" -c cookie.txt curl -X GET http://localhost:8080/product/query?code=6901234567890 -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 一个我每次改完必做的检查习惯
从那以后我每次改完收银逻辑,都强制走一遍“登录 → 查商品 → 加购物车 → 结算 → 查订单 → 查库存”这六步,不管改动多小。因为收银系统的核心链路是环环相扣的,改一个折扣计算可能影响库存扣减,改一个订单号生成可能影响小票打印。这套源码的价值不在于它有多完美,而在于它给了你一个能跑通的骨架,你可以在上面加会员、加退货、加日结报表。希望帮到你。
本文还有配套的精品资源,点击获取