简介:用Java Swing做的图形界面超市管理系统,商品信息和用户账号都直接写进goods.txt和user.txt两个文本文件,不依赖MySQL、SQLite等任何数据库软件。项目带完整源码(src目录)、编译好的class文件(bin目录)、Eclipse工程配置(.classpath/.project/.settings),下载解压后配好JDK 8+就能运行——直接启动SuperManagementSwingTxt主类,打开就是登录页。功能包括商品的添加、删除、修改、查询,支持多用户登录和基础权限区分(管理员/普通员工)。所有数据读写都通过Java标准IO实现,适合练手Swing事件响应、文件流操作、简单状态管理。小型便利店、校园小卖部或教学演示场景下,拿来记库存、管员工账号够用,代码结构清晰,注释到位,新手照着改一改就能上手。
1. 项目概述:为什么一个“没数据库”的超市工具,反而更值得新手反复拆解?
你有没有试过——刚学完Java基础语法,对着Swing组件手册发呆:JButton点了怎么响应?JTable怎么动态刷新?数据改了,下次启动怎么还在?查资料一搜全是“Spring Boot + MySQL + MyBatis”,配置文件堆成山,连pom.xml里dependency版本冲突都能卡你半天。这时候,如果突然看到一个点开就能跑、双击jar就登录、所有数据就躺在goods.txt里明明白白躺着的超市管理工具,第一反应不是“这也太简陋了吧”,而是:“等等……它到底是怎么把‘增删改查’四个字,用纯Java IO和Swing事件链,一气呵成串起来的?”
这就是SuperManagementSwingTxt的价值所在:它不追求企业级架构,而专注还原一个真实业务闭环中最朴素的逻辑链条——用户点击“添加商品”按钮 → 弹出表单 → 填完点确定 → 数据写进txt → 列表实时刷新 → 关闭程序再打开,数据还在。整个过程没有ORM映射、没有SQL解析、没有连接池管理,只有BufferedWriter.write()和ActionListener.actionPerformed()之间干净利落的握手。我带过十几届Java实训班,发现初学者真正卡壳的从来不是“不会写代码”,而是“不知道代码该在哪个时机、以什么顺序、对哪个对象做哪件事”。这个项目就像一张手绘的电路图,电阻在哪、电流走向哪、开关一按整个回路怎么通电,全都裸露在外,连注释都写在关键行右边:“// 这里必须先清空原有列表,否则会重复添加”。
它适合三类人:一是刚写完“Hello World”想摸到真实界面的同学;二是小店老板临时要记个库存,又不想折腾安装MySQL服务;三是教学场景下需要一个“可讲、可改、可断点调试”的最小可行案例。关键词里的“文本存储”不是妥协,而是刻意设计——当你亲手把String.format("%s,%s,%.2f,%d", name, unit, price, stock)拼成一行写进txt,再用split(",")逐字段解析回来时,你才真正理解“结构化数据”四个字的分量。这不是玩具,是把复杂系统剥到只剩骨架后,给你递上的第一把解剖刀。
2. 整体架构与设计思路:为什么放弃数据库,反而让逻辑更透明?
2.1 核心设计哲学:用“文件即数据库”的思维重构数据层
很多初学者误以为“不用数据库”等于“随便存”,但这个项目恰恰反其道而行之:它把txt文件当作一个极简数据库来设计。goods.txt和user.txt不是随意追加的日志,而是严格遵循固定格式+字段分隔+行级原子性的微型数据表。打开goods.txt你会看到:
苹果,斤,5.80,120
可口可乐,瓶,3.50,85
卫生纸,提,22.00,32
每行代表一条商品记录,字段间用英文逗号分隔,顺序固定为:名称,单位,单价,库存。这种设计背后有三层深意:
- 可读性优先:老板用记事本打开就能看懂、能手动修改,不需要任何额外工具。某天发现“卫生纸库存输错了”,直接双击txt改数字,保存即可——这比登录数据库客户端执行UPDATE语句快十倍。
- 解析零成本:Java中
line.split(",")返回字符串数组,索引0就是名称、索引2转成double就是单价,无需正则匹配或JSON解析库,降低学习门槛。 - 事务简化:真正的数据库要处理并发写入冲突,而这个工具默认单用户操作(登录后独占),所以“写入前备份原文件→写新文件→原子替换”就成了最稳妥的伪事务方案。源码里
FileUtils.saveGoodsList()方法就做了这件事:先goods.txt.bak备份,写完goods.txt.tmp,最后renameTo()覆盖原文件——三步完成,失败可回滚。
对比SQLite方案,看似省了数据库安装,实则引入了驱动加载、连接管理、SQL注入防护等新知识点。而文本方案把所有复杂度收束到IO流操作上,恰好是Java基础课程已覆盖的内容,形成完美知识闭环。
2.2 界面与业务逻辑的耦合策略:Swing不是摆设,而是状态控制器
Swing常被诟病“过时”,但在这个项目里,它承担了远超UI渲染的角色。整个系统的状态流转完全由Swing组件驱动:
JComboBox<String> roleCombo(角色下拉框)的选中值,直接决定后续登录验证的权限等级;JTable的TableModel不是静态数据,而是实时绑定ArrayList<Goods>,每次增删改操作后调用fireTableDataChanged()触发重绘;- 登录成功后,
CardLayout切换到主界面卡片,同时JMenuBar根据用户角色动态启用/禁用“用户管理”菜单项。
这种设计让初学者一眼看清“事件→状态→视图”的完整链条。比如点击“删除商品”按钮,源码中这段逻辑清晰得像伪代码:
deleteBtn.addActionListener(e -> {
int selectedRow = goodsTable.getSelectedRow();
if (selectedRow == -1) return; // 未选中行
Goods target = goodsList.get(selectedRow); // 从模型取数据
goodsList.remove(selectedRow); // 模型层删除
goodsTableModel.fireTableRowsDeleted(selectedRow, selectedRow); // 视图层刷新
FileUtils.saveGoodsList(goodsList); // 持久化到txt
});
没有MVC分层,没有接口抽象,所有动作都在一个方法体内完成。这不是架构缺陷,而是教学必需——当学生第一次看到fireTableRowsDeleted()如何让界面上那行数据瞬间消失时,那种“原来如此”的顿悟感,远胜于背诵十遍MVC定义。
2.3 权限模型的极简实现:用字符串比较代替RBAC
权限管理常被设计得无比复杂,但这里只用一行代码解决:
if ("admin".equals(currentUser.getRole())) {
userMenu.setEnabled(true);
} else {
userMenu.setEnabled(false);
}
user.txt中用户记录格式为:admin,admin123,admin 或 staff,zhangsan123,staff,第三字段即角色标识。登录验证时,UserDAO.login(username, password)方法返回的User对象自带role属性,后续所有权限判断都基于此字符串。这种设计牺牲了扩展性(未来加“财务”角色需改多处代码),却换来绝对的可追溯性——你在任意一个if判断处打个断点,立刻能看到当前用户角色是什么、从哪行代码进入分支。对于教学场景,清晰性永远优于灵活性。
3. 核心模块深度解析:从文件读写到界面刷新的全链路拆解
3.1 数据持久层:文本文件的“增删改查”如何精准落地
文件读取:FileUtils.loadGoodsList()的健壮性设计
读取goods.txt看似简单,但实际要考虑五种边界情况:
- 文件不存在:首次运行时
goods.txt为空,不能抛异常,而应返回空ArrayList; - 空文件:文件存在但内容为空,
readLine()返回null,需提前退出循环; - 格式错误行:某行字段数不足4个(如
香蕉,公斤),跳过该行并记录日志,避免整个列表加载失败; - 数值解析异常:单价或库存非数字(如
苹果,斤,abc,120),用try-catch捕获NumberFormatException,跳过该行; - 中文乱码:Windows记事本默认GBK编码,而Java
FileReader用平台默认编码,可能导致中文显示为??。源码中明确指定new InputStreamReader(new FileInputStream(file), "UTF-8"),强制统一编码。
核心代码片段如下:
public static List<Goods> loadGoodsList(File file) {
List<Goods> list = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(file), "UTF-8"))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) continue; // 跳过空行
String[] parts = line.split(",");
if (parts.length < 4) {
System.err.println("警告:goods.txt第" + (list.size()+1) + "行格式错误,跳过:" + line);
continue;
}
try {
String name = parts[0].trim();
String unit = parts[1].trim();
double price = Double.parseDouble(parts[2].trim());
int stock = Integer.parseInt(parts[3].trim());
list.add(new Goods(name, unit, price, stock));
} catch (NumberFormatException e) {
System.err.println("警告:goods.txt数值解析失败,跳过:" + line);
}
}
} catch (FileNotFoundException e) {
System.out.println("提示:goods.txt文件不存在,将创建新文件");
} catch (IOException e) {
e.printStackTrace();
}
return list;
}
提示:初学者常忽略
trim()的重要性。用户可能在记事本中多敲了空格,导致"苹果 "和"苹果"被视为不同商品。trim()确保前后空格被清除,这是生产环境必备细节。
文件写入:saveGoodsList()的原子性保障
写入比读取更危险——万一写到一半程序崩溃,原文件就毁了。项目采用“备份-写入-替换”三步法:
- 将原
goods.txt重命名为goods.txt.bak; - 将新数据写入临时文件
goods.txt.tmp; - 用
tmp.renameTo(original)原子替换,该操作在大多数文件系统上是原子的。
关键代码:
public static void saveGoodsList(List<Goods> list, File originalFile) {
File backupFile = new File(originalFile.getParent(), originalFile.getName() + ".bak");
File tempFile = new File(originalFile.getParent(), originalFile.getName() + ".tmp");
// 步骤1:备份原文件
if (originalFile.exists()) {
originalFile.renameTo(backupFile);
}
// 步骤2:写入临时文件
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(tempFile), "UTF-8"))) {
for (Goods g : list) {
writer.write(String.format("%s,%s,%.2f,%d",
g.getName(), g.getUnit(), g.getPrice(), g.getStock()));
writer.newLine();
}
} catch (IOException e) {
e.printStackTrace();
// 写入失败,恢复备份
if (backupFile.exists()) {
backupFile.renameTo(originalFile);
}
return;
}
// 步骤3:原子替换
if (tempFile.renameTo(originalFile)) {
System.out.println("商品数据保存成功");
if (backupFile.exists()) backupFile.delete(); // 清理备份
} else {
// 替换失败,恢复备份
if (backupFile.exists()) {
backupFile.renameTo(originalFile);
}
System.err.println("错误:无法替换goods.txt,请检查文件权限");
}
}
注意:
renameTo()在Windows上成功率高,但在Linux某些挂载方式下可能失败。教学场景下可接受,若用于生产需改用Files.move()配合StandardCopyOption.REPLACE_EXISTING。
3.2 界面交互层:Swing事件如何驱动业务状态流转
登录模块:LoginFrame的双重验证逻辑
登录界面不只是输入框+按钮,它承载着两个关键职责:
- 前端校验:用户名密码非空、长度限制(源码中
usernameField.getText().length() < 3提示“用户名至少3位”); - 后端验证:调用
UserDAO.login()查询user.txt,匹配成功后设置全局CurrentUser单例。
CurrentUser类设计为饿汉式单例,确保整个应用生命周期内只有一个用户上下文:
public class CurrentUser {
private static final CurrentUser INSTANCE = new CurrentUser();
private User user;
private CurrentUser() {}
public static CurrentUser getInstance() {
return INSTANCE;
}
public void login(User u) {
this.user = u;
}
public User getUser() {
return user;
}
public boolean isLoggedIn() {
return user != null;
}
}
登录成功后,LoginFrame不是直接dispose(),而是先setVisible(false)隐藏,再new MainFrame().setVisible(true)启动主界面。这样保证登录窗口始终在后台存活,方便用户登出后重新显示——这是Swing桌面应用的典型模式,区别于Web的页面跳转。
商品管理:JTable与TableModel的动态绑定
GoodsTableModel继承自AbstractTableModel,需重写三个核心方法:
getRowCount():返回goodsList.size();getColumnCount():固定为4列(名称、单位、单价、库存);getValueAt(int row, int column):根据行列索引从goodsList中取值。
最关键的刷新机制在于:当外部调用goodsList.add(newGoods)后,必须通知表格更新。项目中采用两种方式:
- 批量变更:调用
fireTableDataChanged(),整个表格重绘; - 单行变更:调用
fireTableCellUpdated(row, col),仅刷新指定单元格,性能更好。
例如修改单价时:
// 双击表格单元格进入编辑模式
goodsTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) { // 双击
int row = goodsTable.rowAtPoint(e.getPoint());
int col = goodsTable.columnAtPoint(e.getPoint());
if (col == 2) { // 单价列
String oldPrice = goodsTable.getValueAt(row, col).toString();
String newPrice = JOptionPane.showInputDialog("请输入新单价:", oldPrice);
if (newPrice != null && !newPrice.trim().isEmpty()) {
try {
double price = Double.parseDouble(newPrice.trim());
Goods g = goodsList.get(row);
g.setPrice(price);
goodsTableModel.fireTableCellUpdated(row, col); // 只刷新单价列
FileUtils.saveGoodsList(goodsList);
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(null, "请输入有效数字!");
}
}
}
}
}
});
实操心得:初学者常犯的错误是修改
goodsList后忘记调用fireXXX()方法,导致界面上数据没变。建议在所有增删改操作后,先加一行System.out.println("列表大小:" + goodsList.size());确认模型已更新,再排查视图刷新问题。
3.3 权限控制层:角色驱动的菜单动态启停
主界面MainFrame的菜单栏不是静态构建的,而是根据CurrentUser.getInstance().getUser().getRole()动态调整:
private void initMenuBar() {
JMenuBar menuBar = new JMenuBar();
// 商品菜单(所有人可见)
JMenu goodsMenu = new JMenu("商品管理");
goodsMenu.add(new JMenuItem("添加商品"));
goodsMenu.add(new JMenuItem("查询商品"));
menuBar.add(goodsMenu);
// 用户菜单(仅管理员可见)
JMenu userMenu = new JMenu("用户管理");
userMenu.add(new JMenuItem("添加用户"));
userMenu.add(new JMenuItem("删除用户"));
String role = CurrentUser.getInstance().getUser().getRole();
if ("admin".equals(role)) {
userMenu.setEnabled(true);
menuBar.add(userMenu);
} else {
userMenu.setEnabled(false); // 禁用但保留菜单项,体现权限差异
}
setJMenuBar(menuBar);
}
这里有个精妙细节:userMenu.setEnabled(false)只是让菜单项变灰不可点击,但依然保留在界面上。这比menuBar.remove(userMenu)更符合用户体验——普通员工能看到“哦,这个功能是管理员专用的”,而不是疑惑“我的菜单怎么少了一块?”。
4. 实操部署与功能验证:从零开始跑通全流程
4.1 环境准备:JDK 8+的极简配置
无需IDE,纯命令行也能运行。以Windows为例:
-
确认JDK已安装:
打开CMD,输入java -version,输出类似java version "1.8.0_361"即通过;
若提示“不是内部命令”,需配置环境变量:
- 新建系统变量JAVA_HOME,值为JDK安装路径(如C:\Program Files\Java\jdk1.8.0_361);
- 编辑Path变量,末尾添加%JAVA_HOME%\bin。 -
编译源码(可选,资源包已含bin):
进入项目根目录,执行:
bash javac -d bin -sourcepath src src/SuperManagementSwingTxt.java
-d bin指定class文件输出到bin目录,-sourcepath src告诉编译器源码位置。 -
运行程序:
bash java -cp bin SuperManagementSwingTxt
-cp bin表示类路径为bin目录,SuperManagementSwingTxt是主类全名(不含.java后缀)。
注意:若遇到
UnsupportedClassVersionError,说明JDK版本过高(如用JDK 17编译),需降级到JDK 8或修改编译参数-target 1.8 -source 1.8。
4.2 功能验证清单:手把手跑通核心场景
按顺序验证以下场景,确保每个环节数据流向正确:
| 场景 | 操作步骤 | 预期结果 | 关键验证点 |
|---|---|---|---|
| 1. 首次启动 | 解压后直接运行java -cp bin SuperManagementSwingTxt | 弹出登录窗口,goods.txt和user.txt自动生成(内容为空) | 检查项目根目录是否出现两个空txt文件 |
| 2. 管理员登录 | 用户名admin,密码admin123 | 进入主界面,顶部菜单显示“商品管理”和“用户管理” | 查看user.txt是否已有admin,admin123,admin行 |
| 3. 添加商品 | 点击“商品管理→添加商品”,填入牛奶,盒,5.50,200 | 商品列表新增一行,goods.txt末尾追加牛奶,盒,5.50,200 | 用记事本打开goods.txt确认内容 |
| 4. 修改库存 | 在商品列表双击“牛奶”行的库存列,改为180 | 表格中库存立即变为180,goods.txt对应行更新 | 观察控制台是否打印“商品数据保存成功” |
| 5. 普通员工登录 | 关闭程序,重启,输入staff,zhangsan123,staff | 进入主界面,但“用户管理”菜单置灰不可点击 | 尝试点击该菜单,确认无响应 |
实操心得:验证时务必关闭所有Java进程(任务管理器结束
java.exe),否则旧实例可能占用txt文件导致写入失败。我曾因忘记这步,在saveGoodsList()里死循环重试,浪费两小时排查“文件被占用”问题。
4.3 Eclipse工程导入指南:零配置开箱即用
资源包中包含完整的Eclipse工程元数据(.project, .classpath, .settings),导入步骤极简:
- 启动Eclipse,选择
File → Import → General → Existing Projects into Workspace; - 点击
Browse,定位到解压后的项目根目录(含src和bin文件夹的目录); - 勾选项目名(如
SuperManagementSwingTxt),点击Finish; - 右键项目 →
Run As → Java Application,在弹出列表中选择SuperManagementSwingTxt。
Eclipse会自动识别JDK版本(.classpath中已声明<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>),无需手动配置。若提示“JRE not found”,右键项目 → Properties → Java Build Path → Libraries → Add Library → JRE System Library → Alternate JRE,选择已安装的JDK 8。
5. 常见问题与避坑指南:那些文档里不会写的实战教训
5.1 文件编码引发的“中文乱码”血泪史
现象:在Windows记事本中添加商品大米,袋,3.20,500,程序启动后显示为???,袋,3.20,500。
根本原因:Windows记事本保存UTF-8文件时,默认添加BOM(Byte Order Mark)头,而Java InputStreamReader读取BOM会将其作为字符解析,导致首字段乱码。
解决方案:在FileUtils.loadGoodsList()中跳过BOM:
// 在BufferedReader创建前,检测并跳过BOM
InputStream is = new FileInputStream(file);
// 检测UTF-8 BOM (EF BB BF)
if (is.available() >= 3) {
byte[] bom = new byte[3];
is.read(bom);
if (bom[0] == (byte)0xEF && bom[1] == (byte)0xBB && bom[2] == (byte)0xBF) {
// 是UTF-8 BOM,跳过
} else {
// 不是BOM,重置流位置
is = new FileInputStream(file);
}
}
BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
更简单的规避方法:用VS Code或Notepad++保存txt文件时,选择“UTF-8 无BOM”编码。这是Windows环境下最实用的技巧。
5.2 Swing线程安全陷阱:为什么界面卡死在“添加中…”
现象:点击“添加商品”后,按钮文字变成“添加中…”,但界面冻结,几秒后才响应,期间无法操作任何控件。
原因分析:Swing是单线程模型,所有UI更新必须在Event Dispatch Thread(EDT)中执行。但FileUtils.saveGoodsList()是IO密集型操作,耗时可能达数百毫秒,若在EDT中直接调用,整个UI线程被阻塞。
修复方案:用SwingWorker异步执行IO,完成后在EDT更新UI:
SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
FileUtils.saveGoodsList(goodsList); // 在后台线程执行
return null;
}
@Override
protected void done() {
try {
get(); // 获取执行结果,处理异常
JOptionPane.showMessageDialog(null, "保存成功!");
addDialog.dispose(); // 关闭添加对话框
} catch (Exception ex) {
JOptionPane.showMessageDialog(null, "保存失败:" + ex.getMessage());
}
}
};
worker.execute(); // 启动异步任务
注意:
doInBackground()中不能操作任何Swing组件(如JLabel.setText()),所有UI更新必须放在done()方法中。这是Swing开发铁律。
5.3 数据一致性危机:多人同时操作同一txt文件怎么办?
现实场景:小店有两个员工,A在电脑前修改商品价格,B在手机记事本里直接编辑goods.txt,两人同时保存,谁的数据会丢失?
答案:后保存者覆盖前者,且无任何提示。这是文本存储的天然缺陷。
教学启示:这恰恰是引导学生思考“为什么需要数据库”的绝佳案例。可以布置拓展作业:
- 方案1:添加文件锁机制(FileChannel.lock()),但Windows下锁不跨进程;
- 方案2:引入轻量级嵌入式数据库H2,只需添加一个jar包,SQL语法几乎兼容MySQL;
- 方案3:设计中心化服务端,用Socket通信,文本文件转为服务端数据源。
我的建议:初学阶段坦然接受这个缺陷,重点掌握现有逻辑;进阶时再对比SQLite方案,体会“增加一个jar包,换来并发安全与查询能力”的工程权衡。
5.4 调试技巧速查表:快速定位问题的黄金组合
| 问题类型 | 快速定位方法 | 工具/命令 |
|---|---|---|
| 数据没保存 | 在saveGoodsList()开头加System.out.println("即将保存"+list.size()+"条数据"); | 控制台观察输出 |
| 界面不刷新 | 在fireTableDataChanged()后加System.out.println("表格已刷新,当前行数:"+goodsTable.getRowCount()); | 确认模型与视图同步 |
| 登录失败 | 在UserDAO.login()中打印System.out.println("尝试匹配:"+username+","+password); | 检查user.txt格式是否有多余空格 |
| 中文显示异常 | 用hexdump -C goods.txt(Linux/Mac)或Frida(Windows)查看文件十六进制 | 确认是否为UTF-8编码(e4 b8 ad对应“中”) |
6. 项目扩展与进阶方向:从小工具到生产力利器的演进路径
6.1 功能增强:三步升级为实用门店工具
第一步:增加销售记录(+1天工作量)
- 新建sales.txt,格式:日期,商品名称,数量,单价,总额;
- 主界面添加“销售登记”面板,输入商品名自动从goods.txt补全,选中后库存自动扣减;
- 关键点:销售时需加synchronized块锁定goodsList,防止库存超卖。
第二步:支持Excel导入导出(+3天)
- 使用Apache POI库,pom.xml添加依赖:
xml <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.4</version> </dependency>
- 导出时遍历goodsList生成XSSFWorkbook,设置货币格式(CellStyle.setDataFormat(workbook.createDataFormat().getFormat("¥#,##0.00")));
- 导入时解析Excel行,调用goodsList.add()并保存。
第三步:添加数据统计图表(+2天)
- 集成JFreeChart,绘制月度销售趋势图;
- 核心代码:CategoryDataset dataset = DatasetUtilities.createCategoryDataset("销售额", "月份", salesData);
- 将JFreeChart嵌入JPanel,替代原有静态统计面板。
6.2 架构演进:从文本存储到数据库的平滑迁移
当小店发展为连锁店,文本方案必然触顶。迁移路径应遵循“渐进式”原则:
- 保持API不变:新建
GoodsDaoJdbc类,实现与GoodsDaoTxt相同的接口(如loadAll(),save(Goods)),业务层代码零修改; - 复用领域模型:
Goods、User实体类完全复用,无需改动; - SQL脚本自动化:提供
init_db.sql创建表:
sql CREATE TABLE goods ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, unit TEXT, price REAL, stock INTEGER ); - 连接池接入:用HikariCP替代直连,
application.properties配置:
properties jdbc.url=jdbc:sqlite:supermarket.db jdbc.username= jdbc.password=
经验总结:我在帮社区便利店升级时,整个迁移耗时不到两天。关键在于前期文本方案已把业务逻辑锤炼得足够清晰,数据库只是换了个“存数据的地方”,而非重构整个系统。
6.3 教学价值延伸:如何把这个项目变成Java实训的“核心案例”
作为实训导师,我将此项目拆解为六个递进式实验:
| 实验编号 | 主题 | 学生任务 | 能力培养点 |
|---|---|---|---|
| Lab 1 | 文本文件读写 | 实现loadGoodsList(),支持空文件/格式错误处理 | 异常处理、流操作、健壮性思维 |
| Lab 2 | Swing事件编程 | 为“添加商品”按钮编写监听器,弹出输入对话框 | GUI事件驱动、组件交互 |
| Lab 3 | 表格动态刷新 | 创建GoodsTableModel,实现双击编辑单价功能 | MVC思想、模型-视图绑定 |
| Lab 4 | 权限控制实践 | 根据角色动态启用菜单项,并添加登录失败重试限制 | 状态管理、条件逻辑、用户体验 |
| Lab 5 | 多线程安全 | 用SwingWorker改造保存操作,避免界面冻结 | 并发编程、Swing线程模型 |
| Lab 6 | 架构演进设计 | 设计GoodsDao接口,分别实现Txt版和JDBC版 | 面向接口编程、可扩展性设计 |
每个实验提供“最小可运行代码框架”,学生只需填充核心逻辑。最终成果是一个可演示的、功能完整的超市系统,代码量从300行增长到2000行,但每一行都是亲手敲出来的肌肉记忆。
7. 结语:在“简陋”中看见软件工程的本质
写完这个项目的最后一行代码,我关掉IDE,打开goods.txt,用记事本删掉一行数据,保存,再启动程序——那行商品真的消失了。那一刻没有炫酷的动画,没有复杂的架构图,只有一种近乎原始的踏实感:我清楚地知道,每一个字节从键盘敲下,到屏幕显示,中间经过了多少次内存拷贝、多少次磁盘寻道、多少次事件分发。
这或许就是文本存储方案最珍贵的价值:它剥去了所有框架的糖衣,把软件开发还原成一场与字节、线程、事件的直接对话。当你不再被“Spring Boot自动配置”惯着,才会真正理解Class.forName("com.mysql.cj.jdbc.Driver")为何必要;当你亲手处理FileNotFoundException,才懂得数据库连接池为何要设置maxLifetime。
所以别急着嘲笑它“没技术含量”。真正的技术含量,往往藏在那些敢于用最朴素工具解决真实问题的勇气里。如果你正在学Java,不妨就从这个SuperManagementSwingTxt开始——先让它在你的电脑上跑起来,然后试着改一行代码,让“添加商品”按钮变成红色;再改一行,让库存为0的商品自动标红显示;最后,当你某天真的需要管一家小店时,你会发现,当年那个躺在txt里的超市系统,早已长成了你工程师生涯的第一块基石。
我个人在实际教学中发现,坚持用这个项目带完一轮实训的学生,后续学习Spring Boot时对“Controller-Service-DAO”分层的理解,明显比直接上手框架的学生深刻得多。因为他们见过没有框架的世界,才真正懂得框架存在的意义。
简介:用Java Swing做的图形界面超市管理系统,商品信息和用户账号都直接写进goods.txt和user.txt两个文本文件,不依赖MySQL、SQLite等任何数据库软件。项目带完整源码(src目录)、编译好的class文件(bin目录)、Eclipse工程配置(.classpath/.project/.settings),下载解压后配好JDK 8+就能运行——直接启动SuperManagementSwingTxt主类,打开就是登录页。功能包括商品的添加、删除、修改、查询,支持多用户登录和基础权限区分(管理员/普通员工)。所有数据读写都通过Java标准IO实现,适合练手Swing事件响应、文件流操作、简单状态管理。小型便利店、校园小卖部或教学演示场景下,拿来记库存、管员工账号够用,代码结构清晰,注释到位,新手照着改一改就能上手。
6802

被折叠的 条评论
为什么被折叠?



