资讯详情

Java餐饮管理系统实战:权限控制与事务处理核心拆解

📅 2026/9/16 13:44:15 | 华诺云谱 👁 阅读
Java餐饮管理系统实战:权限控制与事务处理核心拆解
简介面向餐饮门店的日常运营场景这份Java餐饮管理系统源码适合Java Web学习者、课程设计或毕业设计参考能够帮助快速理解一套带权限控制的小型管理系统的完整实现。压缩包共24个文件含22个Java源文件、1个可运行Jar包和1份Markdown说明文档整体约955KB体量轻盈便于直接导入开发环境阅读与调试。目前已有527人学习浏览适合作为实战练手或二次开发的基础模板。系统设计了登录管理、前台服务、后台管理、销售统计、系统安全、人员管理六大功能模块登录区分超级管理员与普通管理员权限前台支持开台点菜、查看菜单、查询餐桌状态与结账后台可对菜品、餐桌、订单和结账进行管理其中菜谱支持增删改同时提供日营业额与利润统计、修改密码、员工及管理员增删等功能。阅读源码可以掌握Java Web分层开发、权限校验、业务模块划分等技能也可基于此快速搭建餐饮类管理系统原型。1. 从手动记单到数据闭环这套 Java 餐饮管理系统值得拆一遍餐饮门店的日常痛点集中在三个动作开台、点菜、结账。高峰期服务员来回跑后厨和收银台纸质菜单一多就容易丢单漏单。这套 Java 餐饮管理系统把业务流程拆成六个模块登录管理分配管理员与员工的操作权限前台服务覆盖开台点菜、查看菜单、查询餐桌状态到结账的完整链路后台管理维护菜谱、餐桌、订单与结账数据销售统计按日聚合营业额和利润系统安全负责修改密码人员管理区分超级管理员、普通管理员和员工。源码项目名 RestaurantManagement除了 JSP、Servlet、JDBC 这条经典技术栈最值得看的是角色权限如何落到每次请求、订单明细如何聚合成报表。对 Java 基础还没串起来的新手正好把 session、Filter、PreparedStatement 在真实业务里走通对准备 Java 面试的开发者这份源码也比背八股文更接近真实业务。2. 登录管理与权限控制角色模型、Filter 拦截与密码存储2.1 三类角色与权限模型人员管理模块里操作者被明确分成超级管理员、普通管理员和员工。梳理源码时先看用户表是怎么建的是用一张用户表加 role 字段区分还是管理员和员工分表存储。我见过两种常见设计单表方案登录简单一次查询就能拿到角色分表方案则要先去管理员表查查不到再查员工表。课程设计类项目多用单表字段大致是 id、username、password、role、real_name、status。role 用字符串还是数字会影响后面每一处判断的可读性建议用字符串枚举比如 SUPER_ADMIN、ADMIN、STAFF避免出现 0/1/2 这种魔法数字。权限分配关系可以整理成一张表照着这张表去源码里找每个 Servlet 的入口校验操作超级管理员普通管理员员工登录系统是是是修改密码是是是菜谱增删改是是否仅查看开台/点菜/结账是是是查询销售统计是是否添加/删除员工是是否添加/删除管理员是否否这张表里最容易漏掉的是中间两行普通管理员能维护菜谱、能操作前台但看不到销售额和利润。源码里的前台服务模块和销售统计模块如果做了这样的隔离说明权限控制至少做到了页面层。2.2 登录验证与 Filter 放行规则不管哪种表结构登录后的第一道关卡都是 session。下面这段接近常见源码的 Filter 逻辑核心是放行登录页和静态资源其余请求一律检查 session 里有没有用户对象public class LoginFilter implements Filter { private static final ListString WHITE_LIST Arrays.asList( /login.jsp, /loginServlet, /css/, /js/, /images/); Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String path request.getServletPath(); // 登录页和静态资源不拦截否则会死循环重定向 for (String prefix : WHITE_LIST) { if (path.startsWith(prefix)) { chain.doFilter(request, response); return; } } Object currentUser request.getSession().getAttribute(currentUser); if (currentUser null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }逻辑说明WHITE_LIST 里的前缀在登录前就能访问getServletPath() 返回不含 queryString 的路径比 getRequestURI() 更适合做前缀匹配放行时直接执行 chain.doFilter拦截时重定向到登录页。注意这里只校验了是否登录还没校验是否有权角色判断要在具体 Servlet 里做。2.3 密码存储MD5 的局限与 JDBC 传参系统安全模块里落了修改密码功能登录验证部分通常是 MD5 摘要之后入库。MD5 不带盐的弱点是彩虹表一击即破想强化的话可以在注册登录时把用户名拼进密码再摘要盐值不需要额外字段存储。更规范的做法是换 BCrypt但课程设计项目改造成本偏高保留 MD5 也说得通。数据库操作统一走 PreparedStatement 的 setString 传参既规避单引号拼接导致的 SQL 语法错乱也顺手防住注入。这里也顺带回答一个 Java 面试常问的点session 是服务端会话cookie 只保存会话 idFilter 里判断 session 存在只是最低门槛真正的权限边界要一层层往下收。3. 前台服务实战餐桌状态机与点菜事务3.1 餐桌状态建模与建表餐桌在整个营业周期里只有几个稳定状态空闲 EMPTY、占用 OCCUPIED、待结账 CHECKING。对应表结构常见是这样的CREATE TABLE t_table ( id INT PRIMARY KEY AUTO_INCREMENT, table_no VARCHAR(10) NOT NULL, capacity INT NOT NULL COMMENT 容纳人数, status VARCHAR(16) NOT NULL DEFAULT EMPTY );status 的取值建议固定为 EMPTY、OCCUPIED、CHECKING 三种。开台动作把 EMPTY 改成 OCCUPIED提交结账改成 CHECKING确认收款后再回到 EMPTY。如果没有 CHECKING 状态就会出现客人还在加菜、餐桌却被标记为空闲的误操作。查询餐桌状态的前端页面本质上就是在轮询这张表String sql SELECT id, table_no, capacity, status FROM t_table WHERE status EMPTY;参数说明where 里直接传状态字符串可以看到哪些桌可以直接开台如果要看某一张桌的详情把条件换成 id 并用 setInt 绑定参数避免拼接。3.2 点菜为什么必须在一个事务里点菜至少涉及三件事插入订单主表、插入若干条订单明细、更新餐桌状态。用 JDBC 直连时最常踩的坑是 autocommit 没有关导致订单主表写进去了、明细插入失败餐桌状态却不变。如果你平时用 MyBatis 比较多看到这段 JDBC 事务应该会想起 SqlSession 的 commit/rollback 封装两者是同一套思想Connection conn null; try { conn DBHelper.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开始事务 String insertOrder INSERT INTO t_order(table_id, create_time, status) VALUES(?, NOW(), UNPAID); PreparedStatement ps conn.prepareStatement(insertOrder, Statement.RETURN_GENERATED_KEYS); ps.setInt(1, tableId); ps.executeUpdate(); ResultSet keys ps.getGeneratedKeys(); int orderId -1; if (keys.next()) { orderId keys.getInt(1); } String insertDetail INSERT INTO t_order_detail(order_id, dish_id, quantity, price) VALUES(?, ?, ?, ?); PreparedStatement psDetail conn.prepareStatement(insertDetail); for (CartItem item : cartList) { psDetail.setInt(1, orderId); psDetail.setInt(2, item.getDishId()); psDetail.setInt(3, item.getQuantity()); psDetail.setBigDecimal(4, item.getPrice()); psDetail.addBatch(); } psDetail.executeBatch(); String updateTable UPDATE t_table SET status OCCUPIED WHERE id ? AND status EMPTY; PreparedStatement psTable conn.prepareStatement(updateTable); psTable.setInt(1, tableId); psTable.executeUpdate(); conn.commit(); } catch (SQLException e) { if (conn ! null) { conn.rollback(); } throw new RuntimeException(下单失败订单已回滚, e); } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } }参数说明RETURN_GENERATED_KEYS 拿到订单主键供明细表外键使用addBatch 和 executeBatch 批量插入明细避免在循环里逐条执行造成大量网络往返更新餐桌状态时带上 statusEMPTY 条件防止两个终端同时抢同一张桌。失败时 rollbackfinally 里恢复 autocommit 是固定动作在连接池场景下忘掉这一步会污染下一个请求。3.3 菜单查询与库存过滤服务员查看菜单时的 SQL 至少应该过滤掉下架和库存不足的菜品只返回真正能点的数据SELECT d.id, d.name, d.price, d.stock, c.name AS category_name FROM t_dish d LEFT JOIN t_category c ON d.category_id c.id WHERE d.status 1 AND d.stock 0 ORDER BY d.category_id, d.id;LEFT JOIN 是为了就算菜品没有分类也能查出来status1 表示上架stock0 表示可点。把过滤条件在 SQL 里写清楚页面渲染就少一层判断也能避免前端把已售罄的菜硬递到后端这个习惯在订单量上来之后会省很多排查时间。4. 后台管理与销售统计菜谱维护与日营业额聚合4.1 菜谱删除用下架代替物理删除后台管理的菜谱模块包括添加、修改、删除菜品。删除菜品时最需要注意的是只要订单明细表里存在对该菜品的引用物理删除就会让历史订单对不上。常见做法是加 status 字段把删除变成下架动作UPDATE t_dish SET status 0 WHERE id ?;即使保留物理删除也要注意菜品价格与订单明细价格要双写。菜品表里的 price 是最新挂牌价订单明细里的 price 是成交时价格。统计营业额时千万别去 join 菜品表取当前价格否则改价后历史营业额会被全部重算。4.2 日营业额与利润的聚合查询销售统计模块要的是日营业额和利润核心是一条带时间范围和订单状态的聚合 SQLSELECT DATE(o.create_time) AS biz_date, SUM(d.quantity * d.price) AS revenue, SUM(d.quantity * (d.price - dish.cost)) AS profit FROM t_order o JOIN t_order_detail d ON o.id d.order_id JOIN t_dish dish ON d.dish_id dish.id WHERE o.create_time 2025-01-20 00:00:00 AND o.create_time 2025-01-21 00:00:00 AND o.status PAID GROUP BY DATE(o.create_time);参数说明时间范围用 和 而不是 BETWEEN 或 DATE(create_time)后者会对 create_time 做函数处理导致索引失效status 只统计已支付订单避免未支付数据污染营业额利润用当前菜品成本表去 join 是近似值更严谨的做法是把成本快照也写进订单明细不过课程设计里极少这么做。4.3 订单列表的分页查询订单管理页面基本是分页列表加状态筛选SQL 形式SELECT * FROM t_order WHERE status ? ORDER BY create_time DESC LIMIT ?, ?;第一个问号是起始行号按 (pageNum-1)*pageSize 计算第二个问号是每页条数。统计总条数时执行 SELECT COUNT(*) 单独查一次比把 COUNT 结果放进主查询做窗口函数兼容性更好对老版本 MySQL 也更友好。4.4 高峰时段统计任务不要阻塞主流程营业额统计如果直接在请求线程里做多个终端同时点查看报表时数据库会被聚合查询拖慢。我一般会建议把日报汇总放到线程池异步执行主线程用 CountDownLatch 等待各分区统计任务都完成后再合并结果正好对应面试里常问的主线程怎么等待所有子线程执行完。源码里没实现这个不奇怪但知道在哪里改是加分项改动范围集中在统计模块的 Service 层把同步调用替换成线程池加 Future 即可。5. 人员管理的权限边界超管、普通管理员与三个典型地雷5.1 只有超级管理员能添加管理员规则要落在后端摘要里写明只有超级管理员能添加或删除管理员普通管理员只能管理员工。这个规则的落地不止在页面上。JSP 里用条件判断隐藏按钮只挡住普通用户的操作入口绕过 UI 直接 POST 到管理接口同样能触发c:if test${sessionScope.currentUser.role SUPER_ADMIN} a hrefadminManage.jsp管理员管理/a /c:if按钮隐藏只是体验层人员管理的所有 Servlet 入口都要先读 session 里的角色再决定是否继续执行。更细的做法是对 Service 层方法做注解式鉴权用 Java 动态代理拦截带权限注解的方法这样业务代码里不会散落一堆 if-else 判断。5.2 三个典型地雷地雷一是越权操作。员工管理接口如果只判断已登录就放行普通员工就能修改他人密码甚至给自己提权。修正方式是在进入业务逻辑前统一校验角色而不是等执行到某一行再判断。地雷二是并发超卖。两桌同时点同一道菜库存只剩一份时先查库存再 update 的代码可能同时通过。扣减语句要写成原子操作String sql UPDATE t_dish SET stock stock - ? WHERE id ? AND stock ?; int rows ps.executeUpdate(); if (rows 0) { throw new RuntimeException(库存不足); }受影响行数为 0 时直接失败从源头避免超卖。地雷三是金额精度。菜品价格一旦出现小数double 累加就会在订单总金额上留下误差。金额相关字段统一使用 BigDecimal构造时传字符串而不是 doubleBigDecimal price new BigDecimal(8.9); BigDecimal total price.multiply(BigDecimal.valueOf(quantity));这样 total 的结果是精确的 17.80不会出现 8.899999999 这种经典误差。最后补一条修改密码的细节改完密码后让 session 失效并强制重新登录避免旧会话继续携带原身份操作。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。