本文还有配套的精品资源,点击获取
简介:一个纯Java Swing开发的离线超市管理桌面程序,不用数据库也不连网络,所有数据都存在本地文本文件里。顾客能看商品、加购、付款;管理员能增删改查商品信息(名称、价格、库存)和用户账号。登录后自动跳转对应界面,顾客看到购物车页面,管理员进入后台管理页,不同角色背景图也不同(比如mainBackground.jpg、adminPage_Background.jpg)。项目自带完整源码,包括主入口Main.java和多个测试类(Test01.java到Test03.java),还有专门存放图片的backgroundPics和otherPics文件夹,用户信息存usersInfo.txt,商品数据在wares_data目录下。配置文件EX_2.0_jar.xml用于打包,workspace.xml记录开发环境设置,out和artifacts是编译输出结果。整个结构适合Java新手练手,重点覆盖GUI组件布局、按钮事件响应、文件读写(IO)、多窗口切换和简单权限分流逻辑。
1. 项目概述:为什么一个“不用数据库”的超市系统反而更值得学?
你可能刚学完Java基础,正琢磨着做点什么练手——写个计算器太简单,写个学生管理系统又绕不开MySQL配置、JDBC驱动、SQL语法这些“拦路虎”。这时候,一个纯用Java Swing写的本地超市系统,所有数据就存几个.txt文件里,连网络都不用连,反而成了最扎实的进阶跳板。它不是“简陋”,而是把核心逻辑从数据库抽象层里彻底剥出来,让你亲手摸到GUI事件流怎么触发、数据怎么落地成字节、权限怎么靠对象状态控制、页面怎么在内存里干净切换——这些才是桌面应用真正的骨架。
我带过不少刚毕业的学生做这个项目,发现他们最大的误区是:一上来就猛敲JFrame和JButton,结果界面堆满了却不知道按钮点击后数据到底去了哪。而这个系统恰恰反其道而行之:它用最朴素的方式告诉你——一个按钮的本质,就是一次方法调用;一次保存,就是把对象转成字符串再写进文件;角色切换,不过是根据登录返回的对象类型,决定new哪一个JFrame而已。没有ORM映射,没有连接池管理,没有事务回滚,只有你和BufferedWriter、ObjectInputStream、ActionListener面对面较劲。usersInfo.txt里明文存着用户名密码(当然实际项目要加盐加密,但初学阶段先理解流程),wares_data目录下每个商品一行文本,格式固定如苹果|5.8|120|水果,你甚至能直接用记事本打开修改,改完重启程序立刻生效。这种“所见即所得”的反馈,对建立编程直觉太关键了。它不教你如何造火箭,但确保你亲手拧紧每一颗螺丝——Swing布局管理器怎么让组件自适应窗口大小,FileReader读取时中文乱码怎么用InputStreamReader指定UTF-8编码,JTable表格模型怎么动态刷新数据,管理员删除商品后购物车里的同名商品要不要自动失效……这些问题的答案,全藏在那几行看似简单的IO代码和事件监听器里。所以别小看这个“土味”系统,它是一面镜子,照出你对Java核心机制的理解深度:是不是真懂对象生命周期?是不是清楚IO流的关闭顺序?是不是明白Swing线程安全的边界在哪?当你能把这些细节抠明白,再上手Spring Boot或JavaFX,才会真正游刃有余。
2. 整体架构与设计思路:没有数据库,怎么让数据“活”起来?
这个系统的精妙之处,在于它用极简的文本存储,模拟出了数据库的核心能力:持久化、查询、增删改查(CRUD)和关系映射。它没用任何框架,全靠Java原生API和合理的面向对象设计撑起整个数据层。我们来拆解它的三层结构:表现层(Swing GUI)、业务逻辑层(Service类)、数据访问层(DAO类)。重点在于,DAO层不对接数据库,而是对接文件系统——这才是它区别于其他教程项目的灵魂所在。
2.1 数据模型设计:用POJO承载业务语义
系统定义了两个核心实体类:User和Ware。User类包含username、password、role(”admin”或”customer”)三个字段,Ware类则封装name、price、stock、category。注意,这里没有用int存价格,而是double——因为超市商品价格常有小数(如9.9元),用整数分单位虽精确但计算繁琐,初学者用double更直观,后续可升级为BigDecimal。每个类都重写了toString()方法,这是文件存储的关键:Ware.toString()返回"苹果|5.8|120|水果",User.toString()返回"zhangsan|123456|customer"。这种竖线分隔的格式,简单、易解析、抗干扰强(比CSV少处理引号和逗号)。更重要的是,它让对象与文本之间形成了一对一的映射关系——序列化不是目的,可读性才是调试的生命线。你打开usersInfo.txt,一眼就能看出哪个用户是管理员,库存数字是否异常,这比二进制序列化或JSON格式对新手友好太多。
2.2 文件存储策略:目录即“数据库”,文件即“表”
系统将文件组织成微型数据库:
-usersInfo/目录是“用户库”,其中usersInfo.txt是“用户表”,每行一条用户记录;
-wares_data/目录是“商品库”,其中wares.txt是“商品主表”,每行一条商品记录;
-allUsers/目录看似冗余,实则是为未来扩展预留的“用户快照”区,比如记录用户历史订单(当前版本未实现,但目录结构已预留)。
这种设计规避了单文件臃肿问题。想象一下,如果所有商品都塞进一个超大wares.txt,每次增删都要读取全部内容再重写,效率极低。而按目录划分,wares_data/下可以分门别类建子目录(如wares_data/fruits/、wares_data/dairy/),虽然当前版本没用上,但结构已为扩展埋好伏笔。文件读写采用典型的“读-改-写”模式:以WareDAO为例,loadAllWares()方法先用Files.readAllLines(Paths.get("wares_data/wares.txt"))一次性读入所有行,再逐行split("\\|")解析成Ware对象并加入ArrayList;saveWares(List<Ware> wares)则遍历列表,对每个ware.toString()追加换行符后,用Files.write()覆盖原文件。这里有个关键细节:必须用StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING三个选项,确保文件被清空重写,避免旧数据残留。我见过太多初学者只写StandardOpenOption.WRITE,结果新数据追加在旧数据后面,导致程序读取时解析错乱。
2.3 权限控制逻辑:身份即状态,状态驱动视图
权限不是靠复杂的RBAC模型,而是最朴素的状态机。登录验证在LoginPanel中完成:用户输入账号密码后,UserDAO.authenticate(username, password)方法会遍历usersInfo.txt,逐行比对。一旦匹配成功,它不返回布尔值,而是直接返回一个完整的User对象,其中role字段已明确标识身份。这个对象被传递给主程序Main.java,后者根据user.getRole()的值,决定实例化CustomerMainFrame还是AdminMainFrame。这就是权限分流的本质——没有中间件拦截,没有注解扫描,只有一个if-else判断,却精准控制了整个应用的入口和行为边界。顾客界面禁用所有商品管理按钮,管理员界面则隐藏购物车结算功能。这种设计教会初学者一个真理:权限控制的起点,永远是用户认证后获得的那个“身份对象”,后续所有功能开关,都是对这个对象属性的条件判断。
3. 核心模块详解:从登录到结账,每一步都在教你怎么写健壮的Swing代码
这个系统的价值,不在它有多炫酷,而在它把Swing开发中最容易踩坑的环节,都用最直白的方式暴露出来。下面我带你走一遍从输入账号到完成支付的完整链路,重点讲透那些教科书里不会写、但实际开发天天遇到的细节。
3.1 登录模块:不只是验证,更是状态初始化的起点
登录界面LoginPanel是一个JPanel,里面放了JTextField(账号)、JPasswordField(密码)、JButton(登录)。关键不在布局,而在事件处理:
loginButton.addActionListener(e -> { String username = usernameField.getText().trim(); String password = new String(passwordField.getPassword()); // 注意!getPassword()返回char[],必须转String if (username.isEmpty() || password.isEmpty()) { JOptionPane.showMessageDialog(this, "账号或密码不能为空!", "输入错误", JOptionPane.WARNING_MESSAGE); return; } User user = UserDAO.authenticate(username, password); if (user != null) { // 认证成功,销毁登录面板,启动主框架 SwingUtilities.invokeLater(() -> { JFrame mainFrame = null; if ("admin".equals(user.getRole())) { mainFrame = new AdminMainFrame(user); } else { mainFrame = new CustomerMainFrame(user); } mainFrame.setVisible(true); ((JFrame) SwingUtilities.getWindowAncestor(this)).dispose(); // 安全关闭登录窗口 }); } else { JOptionPane.showMessageDialog(this, "账号或密码错误!", "登录失败", JOptionPane.ERROR_MESSAGE); } });这段代码藏着三个硬核知识点:
1.密码安全处理:JPasswordField.getPassword()返回char[]而非String,这是Java为防止密码被字符串常量池缓存而做的安全设计。必须用new String(char[])转换,且转换后应立即Arrays.fill(char[], '\0')清空数组(当前项目未做,但这是生产环境铁律)。
2.Swing线程安全:SwingUtilities.invokeLater()确保GUI更新在事件调度线程(EDT)中执行。如果直接new AdminMainFrame()并在当前线程setVisible(true),可能导致界面卡死或组件渲染异常——这是Swing新手第一大坑。
3.窗口生命周期管理:((JFrame) SwingUtilities.getWindowAncestor(this)).dispose()是安全关闭登录窗口的正确姿势。它通过this(即LoginPanel)向上查找最近的JFrame父容器,然后调用dispose()释放资源。绝不能用System.exit(0),那会粗暴杀死整个JVM。
3.2 顾客购物模块:购物车不是集合,而是状态同步的纽带
顾客主界面CustomerMainFrame的核心是JTable展示商品列表和JList显示购物车。难点在于:当用户双击商品列表某行,如何将该商品添加到购物车,并实时更新JList?这里涉及Swing的MVC思想。
购物车数据由Cart类管理,它内部持有一个List<Ware>。Cart不是简单容器,而是具备业务逻辑的实体:
-add(Ware ware):检查库存,若ware.getStock() <= 0则弹窗提示“缺货”,否则添加;
-remove(Ware ware):从列表移除,但不改变商品原始库存;
-calculateTotal():遍历购物车,累加ware.getPrice() * quantity(注意,当前版本购物车未记录数量,每次添加即+1,简化版)。
JTable的模型是DefaultTableModel,但它的数据源来自WareDAO.loadAllWares()。关键同步点在双击事件:
waresTable.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { // 双击 int row = waresTable.getSelectedRow(); if (row >= 0) { // 将视图行号转为模型行号(处理排序过滤时很重要) int modelRow = waresTable.convertRowIndexToModel(row); Ware selectedWare = allWares.get(modelRow); // allWares是loadAllWares()返回的列表 if (Cart.getInstance().add(selectedWare)) { // 添加成功,刷新购物车JList cartListModel.addElement(selectedWare.getName() + " x1"); // 简化显示 totalLabel.setText("总计:" + Cart.getInstance().calculateTotal() + " 元"); } } } } });这里convertRowIndexToModel()是精髓。JTable支持排序,用户点击列头会重排行序,此时getSelectedRow()返回的是视图行号,而allWares列表索引是模型行号,不转换就会选错商品。这个细节,90%的初学者教程都会忽略,导致排序后双击总是添加第一个商品。
3.3 管理员后台模块:增删改查背后的文件原子性保障
管理员界面AdminMainFrame提供四个核心按钮:添加商品、修改商品、删除商品、刷新列表。所有操作都直击文件IO痛点。
以“删除商品”为例,deleteWare()方法不能简单地从allWares列表里remove(),因为那只是内存操作,文件没变。它必须:
1. 从allWares中找到目标商品并移除;
2. 调用WareDAO.saveWares(allWares)重写整个wares.txt;
3. 刷新JTable模型,调用tableModel.setRowCount(0)清空,再遍历allWares逐行addRow()。
但这里有个致命风险:如果saveWares()执行到一半(如磁盘满),文件被截断,数据就永久丢失了。生产环境必须用原子写入。当前项目虽未实现,但作为进阶提示,我告诉你标准做法:先将新数据写入临时文件wares.txt.tmp,写入成功后,用Files.move(Paths.get("wares.txt.tmp"), Paths.get("wares.txt"), StandardCopyOption.REPLACE_EXISTING)原子替换。move操作在绝大多数文件系统上是原子的,要么全成功,要么全失败,绝不会出现半截文件。
另一个易错点是“修改商品”。用户在JTable上双击某行进入编辑,修改后点“保存”,此时不能直接waresTable.setValueAt(newName, row, 0),因为JTable的模型是DefaultTableModel,它只管显示,不管业务逻辑。正确流程是:获取用户修改后的值 → 找到allWares中对应的商品对象 → 修改其name字段 → 调用saveWares(allWares)→ 刷新表格。否则,内存对象和文件数据会严重不一致。
4. 实操过程与关键配置:从零开始搭建、运行、调试的完整路径
现在,我们把理论落到键盘上。假设你刚下载完项目压缩包,面对一堆文件夹有点懵——别急,按这个顺序操作,10分钟内就能跑起来,并理解每个步骤的意义。
4.1 环境准备与项目导入:IntelliJ IDEA下的标准流程
首先确认你的开发环境:JDK 8或11(Swing在JDK 11+仍完全兼容,无需模块化配置)。打开IntelliJ IDEA,选择Open,定位到解压后的项目根目录(即包含.gitignore、src、backgroundPics的文件夹)。IDEA会自动识别为Maven或Gradle项目吗?不会。因为这是一个纯Java项目,没有pom.xml或build.gradle。所以你要手动配置:
- 在
Project Structure(Ctrl+Alt+Shift+S)中,Project Settings→Project→Project SDK选择你安装的JDK; Modules→Sources标签页,将src文件夹标记为Sources(蓝色图标),resources(如果有)标记为Resources;Artifacts标签页,点击+→JAR→From modules with dependencies,选择Main类作为Main Class,勾选Include in project build。这一步对应项目中的EX_2.0_jar.xml,它定义了打包规则:把所有class文件和backgroundPics等资源打进一个jar包。
提示:
workspace.xml是IDEA的私有配置文件,记录了你的窗口布局、断点等,不必关注,也绝不应提交到Git。
4.2 运行前的必要初始化:创建缺失的文件与目录
项目首次运行必报错,因为usersInfo.txt和wares.txt默认不存在。这是故意为之的设计,逼你理解“文件不存在”这个常见异常。你需要手动创建:
- 在项目根目录下,新建文件夹
usersInfo; - 在
usersInfo内,新建空文件usersInfo.txt; - 新建文件夹
wares_data; - 在
wares_data内,新建空文件wares.txt。
然后,往usersInfo.txt里写一行测试数据:admin|admin123|admin(管理员账号);往wares.txt里写几行商品:大米|5.5|50|粮油、鸡蛋|6.8|200|生鲜。保存后,运行Main.java,输入admin/admin123即可登录管理员界面。这个手动创建过程,比任何教程都深刻地教会你:程序依赖的外部资源(文件、目录),必须在运行前由运维或安装脚本准备好,代码里要用Files.createDirectories()主动创建目录,用Files.exists()检查文件是否存在。
4.3 关键资源加载:背景图路径与编码的生死线
界面美观靠backgroundPics里的三张jpg图:mainBackground.jpg(登录页)、adminPage_Background.jpg(管理员页)、cusPage_Background.jpg(顾客页)。加载代码类似:
ImageIcon icon = new ImageIcon("backgroundPics/mainBackground.jpg"); JLabel backgroundLabel = new JLabel(icon);但这里埋着一个巨坑:路径是相对路径,相对于java -cp指定的classpath根目录。如果你在IDEA里直接运行Main.java,classpath根是项目根目录,backgroundPics/能找到;但如果你打包成jar运行,backgroundPics/必须在jar包内部。因此,正确的加载方式是:
URL imageUrl = getClass().getClassLoader().getResource("backgroundPics/mainBackground.jpg"); if (imageUrl != null) { ImageIcon icon = new ImageIcon(imageUrl); } else { // 回退到绝对路径或默认颜色 }getClass().getClassLoader().getResource()会从classpath中搜索,无论是在IDEA调试还是jar包运行,都能准确定位。另外,图片文件本身必须是UTF-8无BOM编码保存,否则某些Windows系统会因路径含中文而加载失败(虽然本项目图片名是英文,但养成习惯很重要)。
4.4 源码结构解读:从Main.java到Test类,谁在驱动一切?
项目源码看似散乱,实则层次分明:
-Main.java:程序唯一入口,负责创建JFrame,设置LoginPanel为内容面板,启动事件循环;
-pages/目录:存放所有GUI面板类,LoginPanel、CustomerMainFrame、AdminMainFrame都在此,遵循“一个页面一个类”原则;
-src/目录:核心业务类,UserDAO、WareDAO、Cart、User、Ware,纯粹的数据操作,与GUI解耦;
-Test01.java至Test03.java:这不是单元测试,而是教学演示脚本。Test01.java专门演示文件读写:WareDAO.loadAllWares()和saveWares();Test02.java演示Swing事件:一个按钮点击弹窗;Test03.java演示多窗口切换。它们是独立的main方法,你可以逐个运行,观察控制台输出,理解底层逻辑后再看GUI代码,事半功倍。
注意:
out/和artifacts/是IDEA编译输出目录,out/production/下是class文件,artifacts/下是打包好的jar。不要手动修改这些目录里的文件,它们由IDEA自动生成。
5. 常见问题与排查技巧实录:那些让我熬夜到凌晨三点的Bug
这个项目就像一面镜子,照出你对Java基础的真实掌握程度。下面这些坑,我当年一个没落下,全是在调试中用血泪总结出来的,现在毫无保留分享给你。
5.1 文件读写类问题:中文乱码、空指针、找不到文件
问题1:打开usersInfo.txt全是乱码,显示“鎵撳瘑閿欒”
-原因:Files.readAllLines()默认使用系统编码(Windows是GBK),而你的文件是UTF-8保存的。
-解决:显式指定编码:Files.readAllLines(Paths.get("usersInfo/usersInfo.txt"), StandardCharsets.UTF_8)。
-经验:永远不要依赖默认编码!在UserDAO.authenticate()里,读取每一行后,用line.trim()去除首尾空格和不可见字符(如BOM),再split("\\|"),否则"admin "和"admin"会被视为不同用户。
问题2:UserDAO.authenticate()返回null,但文件明明有数据
-原因:Files.readAllLines()读取时,如果文件最后一行没有换行符\n,readAllLines()会漏掉最后一行!
-解决:在saveWares()写入时,确保每行末尾都有换行符:writer.write(ware.toString() + "\n")。
-经验:用Files.lines()替代readAllLines(),它是Stream API,能更好处理边界情况,且支持try-with-resources自动关闭。
问题3:“Exception in thread ‘AWT-EventQueue-0’ java.lang.NullPointerException”
-原因:JTable的DefaultTableModel被设为null,或allWares列表为null。
-排查:在AdminMainFrame构造方法里,loadAllWares()后立刻加断点,检查allWares.size()是否为0。如果是,说明WareDAO.loadAllWares()抛异常被静默吞掉了。
-解决:在DAO方法里,catch (IOException e)后必须e.printStackTrace()或log.error("加载商品失败", e),绝不能空catch!
5.2 Swing GUI类问题:界面卡死、组件不显示、事件不响应
问题1:点击登录按钮没反应,控制台也没输出
-原因:ActionListener没正确绑定,或者loginButton是局部变量,在方法结束后被GC,监听器失效。
-排查:检查loginButton声明位置——必须是类的成员变量(private JButton loginButton;),不能在initComponents()里JButton loginButton = new JButton();这样声明。
-经验:用IDEA的“Find Usages”功能,搜索loginButton.addActionListener,确认只绑定了一次。重复绑定会导致事件触发两次。
问题2:管理员界面打开后,JTable一片空白,但数据明明加载了
-原因:DefaultTableModel的列名数组和数据行数不匹配。例如,你定义了{"名称","价格","库存","分类"}四列,但addRow()时只传了三个元素的数组。
-解决:在refreshTable()方法里,先model.setRowCount(0)清空,再确保addRow(new Object[]{ware.getName(), ware.getPrice(), ware.getStock(), ware.getCategory()})传入四个元素。
-经验:在JTable构造时,用new JTable(new DefaultTableModel(data, columnNames)),让模型和数据一起初始化,比后期setModel()更稳定。
问题3:切换到管理员界面后,程序假死,鼠标变成沙漏
-原因:在EDT线程里执行了耗时IO操作,如WareDAO.loadAllWares()直接在AdminMainFrame构造方法里调用,阻塞了整个GUI线程。
-解决:用SwingWorker异步加载:
SwingWorker<List<Ware>, Void> worker = new SwingWorker<>() { @Override protected List<Ware> doInBackground() throws Exception { return WareDAO.loadAllWares(); // 耗时操作放这里 } @Override protected void done() { try { allWares = get(); // 获取结果 refreshTable(); // 在EDT中更新UI } catch (Exception e) { e.printStackTrace(); } } }; worker.execute();- 经验:任何可能超过100ms的操作(文件读写、网络请求、复杂计算),都必须放到
SwingWorker或ExecutorService中执行,这是Swing响应性的生命线。
5.3 项目结构与构建类问题:打包后图片不显示、jar运行报错
问题1:用EX_2.0_jar.xml打包的jar,双击运行闪退,命令行运行显示“NoClassDefFoundError”
-原因:EX_2.0_jar.xml是IDEA的artifact配置,它可能没把backgroundPics目录包含进jar包。
-解决:在Project Structure→Artifacts里,展开你的jar条目,点击+→Directory Content,选择backgroundPics文件夹,确保它出现在jar包的根路径下。
-验证:用jar -tf your-app.jar | grep background查看jar包内容。
问题2:jar包运行后,背景图加载为空白,但控制台无报错
-原因:ImageIcon("backgroundPics/mainBackground.jpg")在jar包里找不到路径,因为jar内路径是backgroundPics/mainBackground.jpg,但代码试图在文件系统中找。
-解决:必须用getClass().getClassLoader().getResource(),如前所述。这是最常被忽视的路径陷阱。
问题3:Test01.java能正常读写文件,但Main.java运行时报AccessDeniedException
-原因:Test01.java在项目根目录运行,有写权限;而Main.java被打包成jar后,可能被放在Program Files等受保护目录,Windows阻止写入。
-解决:将数据文件存放在用户主目录下,如System.getProperty("user.home") + "/supermarket/usersInfo.txt"。这是桌面应用的标准实践,避免权限问题。
6. 进阶优化与扩展建议:从“能跑”到“能用”的跃迁路径
这个项目是绝佳的起点,但绝不是终点。当你能流畅运行、修改、调试它之后,下一步就是把它从“教学Demo”打磨成“可用原型”。以下是几条经过验证的、务实的升级路径,每一步都直击真实开发痛点。
6.1 数据安全加固:告别明文密码,拥抱基础加密
当前usersInfo.txt里密码是明文,这在任何场景下都是不可接受的。升级方案不是一步到位上SHA-256,而是分两步走:
第一步:加盐哈希(Salted Hash)
- 在User类中,password字段不再存明文,而是存哈希值;
- 创建工具类PasswordUtil,包含hash(String password, String salt)和verify(String inputPassword, String storedHash, String salt);
- 盐值(salt)不能写死,为每个用户生成随机盐,与哈希值一起存入文件,格式改为zhangsan|$2a$10$randomSalt$hashedPassword|customer;
- 使用BCryptPasswordEncoder(需引入spring-security-crypto依赖,轻量级)或Java原生MessageDigest(SHA-256+SecureRandom生成盐)。
第二步:敏感信息分离
- 将usersInfo.txt拆分为users_basic.txt(用户名、角色、盐)和users_secure.txt(哈希密码),后者设为系统隐藏文件,限制读取权限。这模拟了生产环境的“最小权限原则”。
6.2 UI体验升级:从“能用”到“顺手”的细节打磨
Swing的默认外观(Metal)早已过时。升级不求华丽,但求专业:
- 更换Look and Feel:在
Main.java开头,添加UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()),让程序使用Windows/macOS原生主题,瞬间提升专业感; - 表格增强:为
JTable添加TableRowSorter,支持点击列头排序;添加JScrollPane滚动条;为库存列设置红色字体警示(stock < 10); - 输入校验:
JTextField输入价格时,用DocumentFilter实时过滤非数字字符,避免用户误输字母导致NumberFormatException。
6.3 功能扩展:小步快跑,验证你的架构能力
不要一上来就加“会员积分”“促销活动”,先做三个高价值小功能:
- 购物车持久化:当前购物车关机就丢。新增
cart_data/目录,用户退出时自动保存购物车到cart_data/zhangsan_cart.txt,下次登录时恢复。这教会你“会话状态管理”的本质; - 操作日志:在
WareDAO的saveWares()方法里,追加写入logs/operation.log,记录“admin于2023-10-05 14:22:33 删除商品:苹果”。用SimpleDateFormat格式化时间,这是审计追踪的起点; - 配置中心化:将所有硬编码路径(
"usersInfo/","wares_data/")提取到config.properties文件,用Properties.load()读取。这让你第一次体会到“配置与代码分离”的威力。
6.4 工程化收尾:从个人项目到可交付产品的最后一步
一个能交付的桌面应用,必须有安装包和自检能力:
- 制作Windows安装包:用
Inno Setup(免费)将jar包、backgroundPics、config.properties打包成.exe安装程序,自动创建桌面快捷方式,设置开机自启选项(需用户授权); - 自检向导:程序启动时,自动检查
usersInfo/、wares_data/目录是否存在,不存在则创建;检查usersInfo.txt是否可读写,不可写则弹窗提示“请以管理员身份运行”。这能消灭80%的用户咨询。
我在实际带团队时,把这个项目作为新人入职第一周的考核任务:第一天跑通,第二天修复三个指定Bug,第三天加一个新功能,第四天写一份技术文档,第五天给团队做10分钟分享。它像一把手术刀,精准切开Java桌面开发的每一层肌肉——Swing的事件模型、IO的异常处理、面向对象的设计、工程化的思维。当你亲手把一个文本文件变成支撑起整个超市运转的数据中枢时,那种“创造”的实感,远胜于背诵一百条API文档。所以,别犹豫,现在就打开IDEA,创建那个usersInfo.txt,敲下第一行admin|admin123|admin,然后按下运行。接下来的每一个NullPointerException,每一次文件读写失败,都是你和Java世界建立真实连接的印记。
本文还有配套的精品资源,点击获取
简介:一个纯Java Swing开发的离线超市管理桌面程序,不用数据库也不连网络,所有数据都存在本地文本文件里。顾客能看商品、加购、付款;管理员能增删改查商品信息(名称、价格、库存)和用户账号。登录后自动跳转对应界面,顾客看到购物车页面,管理员进入后台管理页,不同角色背景图也不同(比如mainBackground.jpg、adminPage_Background.jpg)。项目自带完整源码,包括主入口Main.java和多个测试类(Test01.java到Test03.java),还有专门存放图片的backgroundPics和otherPics文件夹,用户信息存usersInfo.txt,商品数据在wares_data目录下。配置文件EX_2.0_jar.xml用于打包,workspace.xml记录开发环境设置,out和artifacts是编译输出结果。整个结构适合Java新手练手,重点覆盖GUI组件布局、按钮事件响应、文件读写(IO)、多窗口切换和简单权限分流逻辑。
本文还有配套的精品资源,点击获取