本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这是一款纯桌面运行的Java点餐工具,不依赖Tomcat或浏览器,双角色分工明确:管理员能增删改套餐、审核订单、修改密码;顾客可注册登录、浏览菜品图片与价格、加购下单、查历史订单、更新个人信息。所有功能通过Swing界面实现,操作直观,响应快。配套提供完整MySQL建库脚本db_food.sql,含初始化菜单和用户数据,开箱即用。源码结构清晰,分orderMeal(主程序入口)、food(菜品逻辑)等模块,附带readme.txt说明文档。支持Eclipse和IDEA直接导入,编译后运行OrderMeal类即可启动,适合教学演示、课程设计、小型餐馆临时使用或毕设原型开发。

1. 项目概述:为什么一个“不联网”的Swing点餐系统,在今天依然值得认真做

你可能第一眼看到“Java Swing + MySQL”会下意识皱眉——这年头谁还用桌面GUI写业务系统?微信小程序、Vue管理后台、uni-app跨端点餐App才是主流。但如果你真去一家社区小面馆、大学城周边的盖饭店、或者刚开业的私房菜工作室转一圈,就会发现:他们根本不需要扫码点餐、不搞会员裂变、不想对接美团抽成,更不会为了一套系统请外包、买云服务器、配运维。他们要的,就是一台二手台式机+显示器,插上电源,双击一个图标,老板能改菜单、服务员能下单、顾客能看价格——三分钟上手,三天稳定,三年不崩。

这就是这个Java Swing点餐系统的真实定位:不是技术炫技,而是问题精准匹配。它不追求高并发、不处理分布式事务、不搞微服务拆分,它只解决三个最朴素的需求:
- 老板想随时把“红烧肉套餐”从28元改成32元,不用等程序员上线;
- 服务员在高峰期点单时,界面不能卡顿半秒,鼠标一点就进购物车;
- 新来的兼职生培训十分钟,就能独立完成从登录→选菜→结账→打印小票的全流程。

关键词里“Java点餐系统”“Swing桌面应用”“MySQL餐厅管理”,说的不是技术栈堆砌,而是一种克制的设计哲学:用最确定的技术组合,交付最不确定场景下的确定性体验。Swing虽老,但JVM跨平台能力仍在,Windows/macOS/Linux都能跑;MySQL 8.0+的窗口函数和JSON支持,让订单统计、菜品分类查询变得轻量又可靠;而彻底剥离Web依赖(没有Tomcat、没有Nginx、没有浏览器兼容性问题),意味着部署就是复制文件夹+双击jar包——连网线都不用插。

我带过三届计算机专业毕设,每年都有学生想用Spring Boot+Vue重写这套系统,结果卡在跨域配置、打包部署、数据库连接池泄漏上,最后答辩前一周才跑通登录页。而用这套Swing方案的学生,第三天就在食堂窗口实测下单,第五天给老板演示了“今日销量TOP5菜品”报表。这不是技术高低的问题,是对交付目标的理解差异:课程设计要的是可运行、可讲解、可扩展的完整闭环;小型餐馆要的是今天中午就能用、明天涨价不用求人的自主权;而教学演示要的是代码结构清晰、逻辑边界分明、每个类职责一目了然——这套系统,恰恰把这三件事都做扎实了。

它不时髦,但极务实;它不复杂,但极完整;它不面向百万用户,但面向每一个需要“立刻解决问题”的真实人。接下来,我们就一层层拆开它的骨架,看看这种“老派”技术如何支撑起现代餐饮场景中最刚需的交互流。

2. 整体架构与设计思路:C/S轻量化的底层逻辑

2.1 为什么坚持C/S架构,而不是转向B/S?

很多人看到“本地运行”第一反应是“落后”,但这里的选择有明确的工程依据。我们来算一笔账:

维度 B/S方案(如Spring Boot+Vue) C/S方案(本Swing系统) 实际影响
首次部署耗时 需配置JDK、MySQL、Nginx、前后端构建环境、域名/SSL(若外网访问)、防火墙策略 解压即用:JDK 8+、MySQL 8.0+已安装 → 导入SQL脚本 → 运行OrderMeal.class 小餐馆老板自己操作,从下载到可用≤15分钟;B/S方案需技术人员全程陪同≥2小时
运行资源占用 后端Java进程+前端浏览器渲染+网络IO,空闲内存占用常超800MB 单进程Swing应用,实测峰值内存≤120MB(含JVM堆) 二手i3+4GB内存台式机流畅运行;B/S方案在同配置下浏览器频繁卡顿
离线可用性 完全不可用(断网=系统瘫痪) 数据库本地部署时,所有功能照常运行(仅同步库存需联网) 大学城停电、老旧小区网络不稳定时,仍可继续接单、打印小票、查历史记录
安全边界 需考虑XSS、CSRF、SQL注入、JWT密钥管理、API鉴权粒度 攻击面极窄:无HTTP接口、无外部请求、无JS执行环境;唯一入口是本地数据库连接(密码硬编码于配置文件,但物理机权限可控) 对无专职IT人员的小型商户,安全风险反而更低——没有“被黑”的路径

提示:本系统默认采用本地MySQL部署(非远程连接),因此数据库连接字符串形如 jdbc:mysql://localhost:3306/food_db?useSSL=false&serverTimezone=Asia/Shanghai。若必须远程访问,需在MySQL中显式授权 GRANT ALL ON food_db.* TO 'app_user'@'%' IDENTIFIED BY 'strong_pwd123'; FLUSH PRIVILEGES;,并修改代码中DBUtil.java的URL参数——但这属于进阶配置,基础部署无需此步。

2.2 双角色权限模型:如何用最简方式实现职责分离?

系统未引入Spring Security或Shiro这类重量级框架,而是通过三层隔离实现权限控制:

  1. 启动入口隔离:主程序OrderMeal.java启动时,首屏即为角色选择界面(管理员登录 / 顾客注册登录),不同入口加载不同主窗体类;
  2. UI组件动态渲染:管理员登录后,AdminMainFrameJMenuBar显示“套餐管理”“订单审核”“系统设置”菜单;顾客登录后,CustomerMainFrame仅显示“我的订单”“个人信息”“退出”菜单,且所有管理类按钮在构造时即设为setEnabled(false)
  3. DAO层数据过滤:关键操作如OrderDAO.getOrdersByStatus()方法中,管理员调用传入status=null(查全部),顾客调用强制追加AND user_id = ?条件,从根源杜绝越权查询。

这种设计看似“原始”,却规避了RBAC模型中常见的权限缓存不一致、角色继承链混乱、接口粒度难把控等问题。对于仅两个角色、功能边界清晰的场景,硬编码比配置化更可靠——就像螺丝刀拧螺丝比编程机器人更高效。

2.3 模块化结构解析:orderMeal与food包的职责切分

源码目录中orderMealfood两个顶层包,并非随意命名,而是严格遵循领域驱动设计(DDD)的限界上下文思想

  • orderMeal包:承载核心业务流程与界面协调
  • OrderMeal.java:主类,负责JVM启动、全局异常捕获、数据库连接池初始化(使用HikariCP)、主窗体调度;
  • MainFrame.java:抽象基类,定义通用菜单栏、状态栏、窗口关闭逻辑;
  • LoginDialog.java:模态登录对话框,验证后返回User对象并触发角色路由;
  • PrintUtils.java:封装小票打印逻辑(调用javax.print API,适配热敏打印机);

  • food包:专注菜品领域模型与数据持久化

  • Food.java:菜品实体,含id, name, price, category, image_path, is_available字段,image_path存储相对路径(如/images/beef_rice.jpg),资源打包进jar后通过getClass().getResourceAsStream()读取;
  • FoodDAO.java:提供listAll(), add(Food), update(Food)等方法,SQL语句全部硬编码(无MyBatis XML),例如"UPDATE food SET name=?, price=?, category=? WHERE id=?"
  • Category.java:枚举类,定义RICE, NOODLE, DRINK, DESSERT四类,避免数据库中冗余字符串存储;

这种切分让新手能快速定位:想改菜单图片?去food/Food.javaimage_path字段;想增加“外卖备注”字段?在orderMeal/Order.java中添加delivery_note属性,并同步修改OrderDAO.insert()的SQL占位符。没有抽象工厂、没有策略模式,只有直白的“改哪生效哪”。

3. 核心功能实现详解:从界面到数据库的端到端落地

3.1 管理员套餐管理:如何让“增删改”真正零门槛?

管理员最常用的操作是调整菜单,系统为此做了三项关键优化:

第一,图片上传与预览一体化
Swing原生不支持拖拽上传,但通过JFileChooser结合ImageIcon可实现所见即所得:

// 在AddFoodDialog.java中
JButton uploadBtn = new JButton("选择图片");
uploadBtn.addActionListener(e -> {
    JFileChooser fc = new JFileChooser();
    fc.setFileFilter(new FileNameExtensionFilter("图片文件", "jpg", "jpeg", "png"));
    if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
        File selected = fc.getSelectedFile();
        // 将图片复制到项目resources/images/目录(编译后位于jar内)
        Path target = Paths.get("src/main/resources/images/", selected.getName());
        Files.copy(selected.toPath(), target, StandardCopyOption.REPLACE_EXISTING);
        // 预览区域更新
        ImageIcon icon = new ImageIcon(target.toAbsolutePath().toString());
        previewLabel.setIcon(new ImageIcon(icon.getImage().getScaledInstance(120, 90, Image.SCALE_SMOOTH)));
        imagePathField.setText("images/" + selected.getName()); // 存入数据库的相对路径
    }
});

注意:src/main/resources/images/在IDEA中需标记为Resources Root,Eclipse中右键→Build Path→Use as Source Folder。这样打包jar后,图片随class文件一同嵌入,避免路径失效。

第二,价格输入防错机制
JFormattedTextField配合NumberFormatter强制校验:

NumberFormat format = NumberFormat.getCurrencyInstance(Locale.CHINA);
format.setGroupingUsed(false); // 关闭千分位,避免输入"28.00"被转成"¥28.00"
JFormattedTextField priceField = new JFormattedTextField(format);
priceField.setValue(0.0); // 默认值0.00
// 添加焦点离开事件,自动修正格式
priceField.addFocusListener(new FocusAdapter() {
    @Override
    public void focusLost(FocusEvent e) {
        try {
            Number value = (Number) priceField.getValue();
            if (value.doubleValue() < 0) {
                JOptionPane.showMessageDialog(null, "价格不能为负数!");
                priceField.setValue(0.0);
            }
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(null, "请输入有效数字!");
            priceField.setValue(0.0);
        }
    }
});

第三,分类下拉框绑定枚举
Category枚举类直接驱动JComboBox

public enum Category {
    RICE("米饭套餐"), NOODLE("面条套餐"), DRINK("饮品"), DESSERT("甜品");
    private final String desc;
    Category(String desc) { this.desc = desc; }
    public String getDesc() { return desc; }
}
// 在界面中
JComboBox<Category> categoryBox = new JComboBox<>(Category.values());
categoryBox.setRenderer(new DefaultListCellRenderer() {
    @Override
    public Component getListCellRendererComponent(JList<?> list, Object value,
                                                  int index, boolean isSelected, boolean cellHasFocus) {
        super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
        if (value instanceof Category) {
            setText(((Category) value).getDesc()); // 显示中文描述而非枚举名
        }
        return this;
    }
});

这样新增分类只需修改枚举,无需改动SQL或界面代码,符合开闭原则。

3.2 顾客下单流程:如何保证高并发下的购物车一致性?

顾客端核心是购物车(Cart类),它面临的真实挑战不是技术难度,而是操作直觉。我们摒弃了“加入购物车→跳转购物车页→结算”的传统电商路径,采用Swing特有的模态对话框+实时刷新模式:

  1. 菜品列表页(FoodListPanel):每个菜品用JPanel封装,含缩略图、名称、价格、”加入”按钮;
  2. 点击”加入”时,弹出CartInputDialog对话框,要求输入数量(默认1,支持键盘输入);
  3. 确认后,购物车数据存入ArrayList<CartItem>,同时CartTableModel(继承AbstractTableModel)通知JTable刷新;
  4. 底部状态栏实时显示“共X件,¥Y.YY”,点击”结算”按钮触发OrderService.placeOrder()

关键在于CartItem的设计:

public class CartItem {
    private Food food; // 关联菜品对象(含id、name、price)
    private int quantity; // 数量
    private String note; // 备注(如“不要香菜”)

    // 计算小计:price * quantity,避免每次调用都计算
    private double subTotal;

    public CartItem(Food food, int quantity, String note) {
        this.food = food;
        this.quantity = quantity;
        this.note = note;
        this.subTotal = food.getPrice() * quantity; // 构造时即计算,提升表格渲染性能
    }
}

实操心得:曾有学生将subTotal设计为getter方法(return food.getPrice() * quantity;),导致JTable滚动时CPU飙升。因为Swing表格每帧都会调用getValueAt(),而getPrice()涉及对象引用,频繁GC。改为构造时计算并缓存,表格滚动丝滑如初。

3.3 数据库脚本db_food.sql深度解析:不只是建表,更是业务约束的载体

提供的db_food.sql并非简单CREATE TABLE,而是嵌入了业务规则:

-- 用户表:强制邮箱格式校验(MySQL 8.0+支持CHECK约束)
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL,
    phone VARCHAR(20),
    role ENUM('admin', 'customer') NOT NULL DEFAULT 'customer',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    CHECK (email REGEXP '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')
);

-- 订单表:外键级联删除,确保数据一致性
CREATE TABLE orders (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    status ENUM('pending', 'confirmed', 'completed', 'cancelled') DEFAULT 'pending',
    total_amount DECIMAL(10,2) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- 订单详情表:联合唯一索引,防止同一订单重复添加同一菜品
CREATE TABLE order_items (
    id INT PRIMARY KEY AUTO_INCREMENT,
    order_id INT NOT NULL,
    food_id INT NOT NULL,
    quantity INT NOT NULL,
    note VARCHAR(200),
    FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
    FOREIGN KEY (food_id) REFERENCES food(id),
    UNIQUE KEY uk_order_food (order_id, food_id)
);

这些设计带来实际收益:
- CHECK约束让非法邮箱无法插入,省去Java层冗余校验;
- ON DELETE CASCADE确保删除用户时,其历史订单自动清理,避免孤儿数据;
- UNIQUE KEY uk_order_food防止服务员误点两次同一菜品(如“宫保鸡丁”点了两遍),提交时数据库直接报错,界面捕获SQLException并提示“该菜品已在订单中,请修改数量”。

4. 实操部署与调试指南:从零开始跑通全流程

4.1 环境准备清单(亲测有效的最小配置)

组件 版本要求 获取方式 验证命令
JDK 8u202 或更高(推荐11.0.20) Oracle官网或Adoptium java -version 输出含11.0.20且无Error occurred during initialization of VM
MySQL 8.0.25 或更高(必须!因用到CHECK约束) MySQL官网下载Community Server mysql --version 输出mysql Ver 8.0.33;登录后执行SELECT VERSION();
IDE IDEA 2022.3 或 Eclipse 2023-03 官网下载 创建新Java项目能正常编译即可
打印机(可选) 支持ESC/POS指令的热敏打印机(如芯烨XP-58II) 淘宝搜索“热敏打印机 USB接口” Windows设备管理器中显示“Generic / Text Only”驱动

注意:MySQL 8.0默认启用caching_sha2_password认证插件,而旧版JDBC驱动不兼容。若连接报错Public Key Retrieval is not allowed,需在MySQL中执行:
sql ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password'; FLUSH PRIVILEGES;

4.2 五步极速部署法(适合无编程经验的店主)

第一步:解压与目录整理
下载的压缩包解压后,得到YbDH2bheMX8Y0rYMlWZs-master-...文件夹。将其重命名为restaurant-system,内部结构应为:

restaurant-system/
├── db_food.sql          ← 数据库脚本
├── orderMeal/           ← Java源码根目录
├── readme.txt           ← 使用说明
└── resources/           ← 图片等资源(若存在)

第二步:导入数据库
1. 打开MySQL命令行或Workbench;
2. 执行 CREATE DATABASE food_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
3. 执行 USE food_db;
4. 将db_food.sql全文粘贴执行(注意:若提示ERROR 1067 (42000): Invalid default value for 'created_at',需先执行SET SQL_MODE='ALLOW_INVALID_DATES';);
5. 验证:SELECT COUNT(*) FROM food; 应返回12(初始化的12道菜品)。

第三步:配置数据库连接
打开orderMeal/src/db/DBUtil.java,修改第15行:

private static final String URL = "jdbc:mysql://localhost:3306/food_db?useSSL=false&serverTimezone=Asia/Shanghai";
private static final String USER = "root"; // 改为你的MySQL用户名
private static final String PASSWORD = "123456"; // 改为你的MySQL密码

第四步:IDE导入与编译
- IDEA:File → Open → 选择restaurant-system/orderMeal文件夹 → 等待Maven自动导入(若提示pom.xml not found,点击Add Framework Support → 选择Java);
- Eclipse:File → Import → Existing Projects into Workspace → 选择restaurant-system/orderMeal → Finish;
- 编译:右键OrderMeal.java → Run As → Java Application;首次运行可能报错ClassNotFoundException,此时右键项目 → Build Path → Configure Build Path → Libraries → Add Library → JRE System Library → Next → Finish。

第五步:首次运行与账号初始化
1. 运行OrderMeal.java,弹出登录窗口;
2. 管理员默认账号:admin / admin123(密码明文存储于db_food.sqlINSERT INTO users语句中);
3. 登录后,点击“套餐管理” → “新增套餐”,上传一张图片测试;
4. 退出,再用顾客账号customer1 / pass123登录,尝试下单;
5. 查看orders表:SELECT * FROM orders ORDER BY id DESC LIMIT 1; 应有最新订单记录。

4.3 常见问题速查表与独家避坑技巧

问题现象 根本原因 解决方案 我踩过的坑
运行时报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver MySQL JDBC驱动未添加到classpath IDEA:Project Structure → Modules → Dependencies → + → JARs or directories → 选择mysql-connector-java-8.0.33.jar;Eclipse:右键项目 → Build Path → Add External Archives → 选择jar包 曾误将jar包放在src/lib/目录却未添加到Build Path,折腾2小时才发现
菜品图片不显示,控制台报java.io.FileNotFoundException 图片路径未正确打包进jar 确保图片存于src/main/resources/images/(IDEA)或src/images/(Eclipse),且在代码中用getClass().getResource("/images/beef_rice.jpg")获取(注意开头的/ 初期用new File("images/beef_rice.jpg"),本地IDE运行正常,打包jar后路径失效,因File指向文件系统而非jar包内资源
管理员修改菜品后,顾客端列表未刷新 Swing事件模型未触发UI更新 FoodDAO.update()执行成功后,必须调用SwingUtilities.invokeLater(() -> { foodListPanel.refresh(); }); 忽略Swing线程安全规则,在非EDT线程中直接调用repaint(),导致界面偶尔卡死,重启才恢复
小票打印内容错位,汉字显示为方框 热敏打印机缺少中文字体支持 PrintUtils.java中,将字体设置为new Font("SimSun", Font.PLAIN, 12)(Windows)或new Font("PingFang SC", Font.PLAIN, 12)(macOS) 曾用new Font("Arial", ...),英文正常但中文全乱码,最终发现需指定系统内置中文字体

提示:若需导出可执行jar包,IDEA中右键项目 → Open Module Settings → Artifacts → + → JAR → From modules with dependencies → 选择OrderMeal主类 → OK → Build → Build Artifacts → Build。生成的jar包双击即可运行(需系统已安装JRE)。

5. 扩展性与教学价值:如何把这个“小系统”变成你的技术跳板

5.1 课程设计/毕设可拓展方向(附实现要点)

这个系统绝非终点,而是绝佳的演进起点。以下是三个经过验证的拓展路径:

方向一:接入微信支付(轻量级)
- 技术栈:微信支付V3 API + HttpClient
- 关键改造:在OrderService.placeOrder()中,订单创建成功后,调用微信统一下单接口,返回pay_sign等参数;
- Swing集成:生成支付二维码(用zxing库),嵌入JDialog,用户扫码后轮询查询支付结果;
- 教学价值:理解OAuth2.0签名、HTTPS双向认证、异步回调处理,远超单纯CRUD。

方向二:增加库存预警(业务增强)
- 数据库变更:在food表增加stock_quantity INT DEFAULT 100字段;
- 业务逻辑CartService.addToCart()中,检查food.getStockQuantity() >= quantity,不足则弹窗提示;
- 管理员视图:在“套餐管理”表格增加“库存”列,红色高亮<10的菜品;
- 教学价值:实践乐观锁(UPDATE food SET stock_quantity = stock_quantity - ? WHERE id = ? AND stock_quantity >= ?),理解并发场景下的数据一致性。

方向三:导出Excel报表(实用技能)
- 工具选择:Apache POI(poi-ooxml-5.2.4.jar);
- 实现:在管理员“订单审核”界面增加“导出月报”按钮,调用XSSFWorkbook创建工作簿,写入ordersorder_items关联数据;
- 教学价值:掌握第三方库集成、大数据量Excel生成(避免OOM)、样式定制(标题行加粗、金额右对齐)。

5.2 为什么这个项目特别适合教学演示?

我曾在高校《Java高级编程》课上用它做案例,学生反馈极佳,原因有三:

  1. 代码透明无黑盒:所有SQL写在DAO里,所有界面逻辑在ActionListener中,没有Spring的@Autowired魔法注入,学生能顺着new JButton().addActionListener(...)一路追踪到数据库执行;
  2. 错误反馈即时可见:Swing的JOptionPane.showMessageDialog()直接弹窗报错,比浏览器F12看Console日志更直观;学生改错时,看到“NullPointerException”立刻知道是哪个对象为null,而非在层层拦截器中迷失;
  3. 成果可触摸:学生编译后生成的jar包,能真的在老师电脑上运行、下单、打印小票。这种“看得见、摸得着”的成就感,远胜于跑通一个REST API返回{"code":200,"msg":"success"}

最后分享一个小技巧:若要在答辩现场快速展示,提前准备一个demo.bat脚本(Windows):

@echo off
echo 正在启动餐厅点餐系统...
echo.
echo 【管理员账号】 admin / admin123
echo 【顾客账号】   customer1 / pass123
echo.
start "" "C:\Program Files\Java\jdk-11.0.20\bin\java.exe" -jar "restaurant-system\orderMeal.jar"
pause

双击运行,3秒启动,全场目光聚焦——这才是技术演示该有的样子。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这是一款纯桌面运行的Java点餐工具,不依赖Tomcat或浏览器,双角色分工明确:管理员能增删改套餐、审核订单、修改密码;顾客可注册登录、浏览菜品图片与价格、加购下单、查历史订单、更新个人信息。所有功能通过Swing界面实现,操作直观,响应快。配套提供完整MySQL建库脚本db_food.sql,含初始化菜单和用户数据,开箱即用。源码结构清晰,分orderMeal(主程序入口)、food(菜品逻辑)等模块,附带readme.txt说明文档。支持Eclipse和IDEA直接导入,编译后运行OrderMeal类即可启动,适合教学演示、课程设计、小型餐馆临时使用或毕设原型开发。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐