简介:这是一份基于Java开发的轻量级超市收银系统实战项目资源,面向Java初学者与课程设计学习者,解决零售场景下商品管理、用户登录、收银台操作及会员注册等核心业务逻辑实现问题。压缩包共27个文件,含9个.java源码文件(如DengLu.java登录模块、Shouyintai.java收银台主界面、Goods.java商品类)、14个编译后.class字节码文件、2个manifest.mf清单文件、以及.project和.classpath工程配置文件,完整呈现从编码、编译到运行的全流程结构,包体仅25KB,便于快速导入IDE学习调试。已有264人下载学习,读者可直接获取可运行的GUI收银系统源码,掌握Swing界面开发、JDBC数据库连接(预设DBUtil工具类)、MVC分层思想及基础事务处理逻辑,特别适合JavaSE阶段项目实训与期末课程设计参考。
1. 这不是演示项目,而是一套能跑通完整收银闭环的 Java 桌面系统
你解压chaoshi.rar后看到的不是空壳 Demo,也不是只有登录界面的“教学玩具”——它包含从商品录入、会员注册、收银台结算到库存扣减的全链路逻辑,且所有类文件(.class)已编译就绪,src/下对应.java源码完整可读。这意味着:无需重写业务逻辑,只要配好 JDBC 驱动、建好数据库表、启动主类Shouyintai.class,就能在 Windows 或 Linux 桌面环境直接扫码结账。它不依赖 Spring Boot、不嵌入 Tomcat、不走 HTTP 接口,而是用 AWT/Swing 构建本地 GUI,用DBUtil.class封装 JDBC 连接池(虽简但有效),用Goods.class和Member.class承载核心领域模型。适合两类人:一是 Java 初学者想拿真实业务练手(比“学生管理系统”更贴近商业场景),二是中小超市技术员需快速部署轻量收银终端(无云服务依赖,单机即可运行)。它不解决高并发或分布式事务,但把“一笔交易如何从扫码→查价→扣库存→打小票”这个最小闭环,用纯 Java SE + JDBC + Swing 拆解得足够清晰。
2. 从反编译源码到数据库建模:还原系统数据结构与交互流程
2.1 解包后关键类职责解析:不是命名堆砌,而是分层明确的 MVC 实践
chaoshi.rar解压后呈现典型的 Java SE 桌面应用结构:bin/存放编译产物,src/存放源码,.project和.classpath表明原工程基于 Eclipse 开发。我们先聚焦核心类的协作关系:
Shouyintai.java(收银台):主窗口类,继承Frame,含JTable显示购物车、JLabel实时总价、JButton触发结算。其内部匿名类Shouyintai$1.class是ActionListener,监听“结算”按钮点击事件。Goods.java与Member.java:实体类,字段直白映射数据库列(如Goods含id,name,price,stock;Member含cardNo,name,discount),无 Lombok 注解,靠手动 getter/setter 维护。DBUtil.java:关键基础设施类。静态块加载com.mysql.jdbc.Driver(注意:这是 MySQL Connector/J 5.x 旧版驱动类名),getConnection()方法返回Connection,close()方法显式释放资源。未使用连接池,但通过static变量缓存Connection对象,避免每次操作都新建连接——这是单机桌面应用的典型取舍。DengLu.java(登录)与Shouyinyuanzhuce.java(收银员注册):验证逻辑在客户端完成(JPasswordField.getText().equals("123")类似硬编码密码),安全性非重点,但流程完整:登录 → 进入收银台 → 商品扫码/搜索 → 加入购物车 → 结算 → 更新库存。
提示:
Shangpingtianjia$close.class是Shangpingtianjia.java(商品添加)中内部类,用于监听窗口关闭事件并执行dispose()。这种细粒度事件拆分,是 Swing 编程的惯用手法,而非冗余代码。
2.2 数据库表结构逆向推导:从 DAO 层 SQL 拼接还原 DDL
DBUtil.java中无建表语句,但Goods.java的insert()方法暴露了插入逻辑:
public int insert(Goods goods) { String sql = "INSERT INTO goods (name, price, stock, barcode) VALUES (?, ?, ?, ?)"; // ... PreparedStatement 设置参数 ... }同理,Member.java的insert()方法含"INSERT INTO member (cardNo, name, phone, discount) VALUES (?, ?, ?, ?)"。结合DengLu.java登录查询"SELECT * FROM user WHERE username = ? AND password = ?",可确定三张核心表:
| 表名 | 字段(类型) | 说明 |
|---|---|---|
goods | id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(50),price DECIMAL(10,2),stock INT,barcode VARCHAR(20) | 商品主表,barcode为扫码依据 |
member | id INT PRIMARY KEY AUTO_INCREMENT,cardNo VARCHAR(20) UNIQUE,name VARCHAR(30),phone VARCHAR(15),discount DECIMAL(3,2) | 会员表,discount存储折扣率(如 0.95 表示 95 折) |
user | id INT PRIMARY KEY AUTO_INCREMENT,username VARCHAR(20),password VARCHAR(50) | 收银员账号表,密码明文存储(仅作功能验证,非生产标准) |
建表 SQL 必须执行(以 MySQL 8.0 为例):
CREATE DATABASE IF NOT EXISTS chaoshi DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; USE chaoshi; CREATE TABLE goods ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, barcode VARCHAR(20) UNIQUE ); CREATE TABLE member ( id INT PRIMARY KEY AUTO_INCREMENT, cardNo VARCHAR(20) UNIQUE NOT NULL, name VARCHAR(30), phone VARCHAR(15), discount DECIMAL(3,2) DEFAULT 1.00 ); CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20) NOT NULL, password VARCHAR(50) NOT NULL ); -- 插入默认收银员(用户名 admin,密码 123) INSERT INTO user (username, password) VALUES ('admin', '123');注意:
DBUtil.java中getConnection()使用DriverManager.getConnection("jdbc:mysql://localhost:3306/chaoshi?useSSL=false&serverTimezone=GMT%2B8", "root", "123456")。必须确保 MySQL 服务运行,且 root 用户密码为123456,否则启动Shouyintai会抛出SQLException: Access denied。若修改密码,需同步更新DBUtil.java中的密码字符串并重新编译。
2.3 启动流程与类加载路径:绕过 IDE 直接运行的关键步骤
系统未打包成 JAR,需手动指定 classpath 运行。步骤如下:
准备运行环境:
- 安装 JDK 8(
java -version输出1.8.0_XXX),因DBUtil.java使用com.mysql.jdbc.Driver(MySQL 5.x 驱动),与 JDK 8 兼容性最佳; - 将
mysql-connector-java-5.1.47.jar(下载地址:https://dev.mysql.com/downloads/connector/j/5.1.html)放入chaoshi/目录; - 确保
chaoshi/bin/下存在所有.class文件(含manifest.mf,但此文件无Main-Class声明,故不能java -jar)。
- 安装 JDK 8(
命令行启动收银台:
cd chaoshi java -cp "bin:./mysql-connector-java-5.1.47.jar" Shouyintai-cp参数将bin/(含所有 class)和 MySQL 驱动 JAR 合并为类路径;Shouyintai是主类名(无.class后缀),JVM 自动查找bin/Shouyintai.class;- 若报错
Could not find or load main class Shouyintai,检查当前目录是否为chaoshi/,且bin/下确有Shouyintai.class。
首次运行验证:
- 启动后弹出登录窗口,输入
admin/123进入收银台; - 点击“商品管理” → “添加商品”,填入名称、价格、库存、条码(如
123456),点击保存; - 返回收银台,在“商品条码”输入框输入
123456,回车——商品自动加入购物车,总价实时计算; - 点击“结算”,弹出确认框,选择“现金支付”,系统扣减
goods表中对应stock并生成交易记录(虽无独立 transaction 表,但库存更新已执行)。
- 启动后弹出登录窗口,输入
3. 商品扫码与库存强一致性保障:事务控制与并发安全实操
3.1 默认无事务?用 try-catch + 手动 rollback 弥补 JDBC 原生缺陷
Shouyintai.java的结算逻辑位于actionPerformed()方法中,关键代码片段如下:
// 伪代码示意(实际在 Shouyintai$1.class 中) try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 关键:开启事务 // 步骤1:扣减库存(UPDATE goods SET stock = stock - ? WHERE id = ?) PreparedStatement ps1 = conn.prepareStatement("UPDATE goods SET stock = stock - ? WHERE id = ?"); ps1.setInt(1, quantity); ps1.setInt(2, goodsId); ps1.executeUpdate(); // 步骤2:记录销售(此处无 sales 表,故省略) conn.commit(); // 提交事务 } catch (SQLException e) { try { conn.rollback(); // 回滚 } catch (SQLException ex) { ex.printStackTrace(); } JOptionPane.showMessageDialog(null, "库存不足或数据库错误"); }但原始chaoshi.rar中的Shouyintai.java并未显式调用setAutoCommit(false)和commit()。这意味着:默认autoCommit=true,每条 SQL 独立提交,无法保证“扣库存”与“记销售”的原子性。必须手动增强:
修改Shouyintai.java的结算方法(以 Eclipse 为例):
- 在
// 结算逻辑开始处添加:Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交 - 在
// 扣库存 SQL 执行后添加:// 假设已有 PreparedStatement psUpdateStock 执行成功 psUpdateStock.executeUpdate(); // 此处可追加其他业务SQL,如插入 sales 记录 // PreparedStatement psInsertSale = conn.prepareStatement("INSERT INTO sales (...) VALUES (...)"); // psInsertSale.executeUpdate(); conn.commit(); // 显式提交 JOptionPane.showMessageDialog(this, "结算成功!"); } catch (SQLException e) { try { if (conn != null) conn.rollback(); // 发生异常则回滚 } catch (SQLException ex) { ex.printStackTrace(); } JOptionPane.showMessageDialog(this, "结算失败:" + e.getMessage()); } finally { try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } }
提示:
DBUtil.java的close()方法仅关闭Statement和ResultSet,未关闭Connection。因此在Shouyintai中必须显式conn.close(),否则连接泄漏。这是桌面应用常见疏漏,需补全。
3.2 库存超卖防护:乐观锁 vs 悲观锁的轻量级选型
当多台收银机同时操作同一商品时,UPDATE goods SET stock = stock - 1 WHERE id = 100可能导致超卖(如库存剩 1,两笔请求同时读到 1,均扣减为 0)。chaoshi原始代码无防护,需加固:
方案一:乐观锁(推荐,侵入小)
- 在
goods表新增version INT DEFAULT 0字段; - 查询商品时读取
version; - 更新时校验:
UPDATE goods SET stock = stock - 1, version = version + 1 WHERE id = ? AND version = ?; - 若
executeUpdate()返回 0,说明版本不匹配(已被他人更新),提示“库存变动,请刷新重试”。
方案二:悲观锁(简单直接)
- 在查询商品时加锁:
SELECT * FROM goods WHERE id = ? FOR UPDATE; - 此 SQL 会阻塞其他事务对同一行的
UPDATE,直到当前事务结束; - 注意:必须在同一个
Connection中执行SELECT ... FOR UPDATE和后续UPDATE,且事务未提交前锁不释放。
实操代码(悲观锁):
String sqlSelect = "SELECT * FROM goods WHERE id = ? FOR UPDATE"; PreparedStatement psSelect = conn.prepareStatement(sqlSelect); psSelect.setInt(1, goodsId); ResultSet rs = psSelect.executeQuery(); if (rs.next()) { int currentStock = rs.getInt("stock"); if (currentStock < quantity) { throw new SQLException("库存不足"); } // 继续执行 UPDATE }4. 从 Swing 到现代化 UI:替换 AWT 组件与集成打印模块的实战改造
4.1 用 IntelliJ IDEA 重构 GUI:告别 AWT,拥抱现代 Swing 组件
chaoshi原始界面使用Frame+Panel+Button等 AWT 组件,布局僵硬(FlowLayout)、字体模糊、无高 DPI 支持。升级为JFrame+JPanel+JButton并启用系统外观:
- 修改主类继承关系:
Shouyintai.java中public class Shouyintai extends Frame→public class Shouyintai extends JFrame; - 替换组件初始化:
// 原:Frame frame = new Frame("收银台"); // 改为: JFrame frame = new JFrame("超市收银系统"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new BorderLayout()); // 使用 BorderLayout 替代 FlowLayout - 启用系统外观(Windows/macOS/Linux 自适应):
在main()方法开头添加:try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { e.printStackTrace(); }
4.2 集成热敏小票打印:用 Java Print Service 输出标准 POS 小票
收银系统必须支持物理打印。chaoshi无打印模块,需自行添加。以下为兼容主流热敏打印机(如芯烨、佳博)的方案:
步骤 1:定义小票文本模板
public static String generateReceipt(List<CartItem> cart, double total) { StringBuilder sb = new StringBuilder(); sb.append("====== 超市收银系统 ======\n"); sb.append("时间:").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())).append("\n"); sb.append("----------------------------\n"); for (CartItem item : cart) { sb.append(String.format("%-10s %4d×%6.2f=%6.2f\n", item.getName(), item.getQuantity(), item.getPrice(), item.getTotal())); } sb.append("----------------------------\n"); sb.append(String.format("总计:¥%8.2f\n", total)); sb.append("感谢惠顾!\n"); return sb.toString(); }步骤 2:调用系统打印机(无需驱动,依赖 OS 打印服务)
public static void printReceipt(String receiptText) { try { DocFlavor flavor = DocFlavor.BYTE_ARRAY.AUTOSENSE; PrintService service = PrintServiceLookup.lookupDefaultPrintService(); if (service == null) { JOptionPane.showMessageDialog(null, "未找到默认打印机,请检查系统设置"); return; } Doc doc = new SimpleDoc(receiptText.getBytes(StandardCharsets.UTF_8), flavor, null); service.createPrintJob().print(doc, null); } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, "打印失败:" + e.getMessage()); } }调用时机:在Shouyintai结算成功后,JOptionPane.showMessageDialog(...)之后插入printReceipt(receiptText)。
注意:Windows 系统需在“设置 > 蓝牙和其他设备 > 打印机和扫描仪”中添加热敏打印机,并设为默认。Linux 用户需安装 CUPS 服务并配置打印机。
5. 生产环境加固:密码加密、日志追踪与离线模式下的本地缓存策略
5.1 密码明文存储风险与 BCrypt 加密迁移路径
user表中password字段明文存储(如123),严重违反安全规范。迁移方案需兼顾兼容性与平滑升级:
- 新增
password_hash字段(VARCHAR(100)):ALTER TABLE user ADD COLUMN password_hash VARCHAR(100) AFTER password; - 修改登录验证逻辑(
DengLu.java):// 查询时同时获取 password 和 password_hash String sql = "SELECT id, username, password, password_hash FROM user WHERE username = ?"; // 若 password_hash 为空(老用户),用明文比对并生成 hash 写回数据库 // 若 password_hash 不为空,用 BCrypt.checkpw(inputPassword, storedHash) 验证 - 引入 BCrypt 依赖:
下载bcprov-jdk15on-168.jar(Bouncy Castle)或更轻量的jbcrypt-0.4.jar(https://github.com/jeremyh/jBCrypt),添加至 classpath;// 生成 hash 示例 String hash = BCrypt.hashpw("123", BCrypt.gensalt());
5.2 用 Log4j2 实现操作审计日志:定位每一笔交易源头
chaoshi无日志,故障排查困难。添加log4j2.xml配置(放在src/下):
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <Appenders> <File name="FileAppender" fileName="logs/chaoshi.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </File> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="FileAppender"/> </Root> </Loggers> </Configuration>在Shouyintai.java结算方法中插入日志:
Logger logger = LogManager.getLogger(Shouyintai.class); logger.info("收银员[{}]结算订单:{}件商品,总金额¥{},支付方式[现金]", currentUser.getUsername(), cart.size(), total);效果:logs/chaoshi.log自动生成,记录每次结算时间、操作员、商品数、金额,便于事后审计。
5.3 离线模式兜底:H2 数据库嵌入式切换与同步机制
当网络中断时,MySQL 不可用。chaoshi需支持降级到本地 H2 数据库存储临时交易,待网络恢复后同步至 MySQL:
- 添加 H2 依赖:
h2-2.1.214.jar; - 修改
DBUtil.java的getConnection():public static Connection getConnection() { String url = isOnline() ? "jdbc:mysql://localhost:3306/chaoshi?..." : "jdbc:h2:./data/chaoshi_offline;DB_CLOSE_DELAY=-1"; return DriverManager.getConnection(url, "sa", ""); } - 设计同步任务:
启动时检查h2中是否存在未同步的offline_sales表记录,若有,则批量 INSERT 到 MySQL 的sales表,并清空h2表。
提示:H2 的
jdbc:h2:./data/chaoshi_offline会在当前目录创建data/文件夹存储数据库文件,无需额外服务进程,真正实现“开箱即用”的离线能力。
最后一行不要总结。
本文还有配套的精品资源,点击获取