尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SSM框架社区食堂系统拆解:订单状态机与权限设计实战

SSM框架社区食堂系统拆解:订单状态机与权限设计实战 简介面向Java方向毕业设计及课程设计人群这套社区共享食堂信息系统以SSMSpringSpringMVCMyBatis为后端框架JSP搭建前端页面搭配MySQL数据库适合在Eclipse、MyEclipse、STS或IDEA中基于JDK1.8环境直接运行。系统覆盖主页、个人中心、用户管理、美食分类、食堂管理、菜品信息、订单管理等完整功能模块既能用于毕设演示也可作为SSM整合开发的学习案例。压缩包整体约87.22MB包含完整源代码、数据库脚本、毕业论文、答辩PPT、环境工具包以及同框架项目的安装教程见说明文档可支撑从建库、配置到部署逐步复现项目。目前已有48人学习下载适合需要快速搭建SSMJSP项目、撰写毕设文档或准备答辩的同学系统性参考与二次开发。1. 社区共享食堂系统拆解先搞清 SSM 加 JSP 这个组合在管什么社区共享食堂这个课题听起来像外卖点餐真正把源码拆开后会看到它其实是一个典型的管理后台加订单流转系统普通用户按食堂浏览菜品、下单、查订单食堂管理员负责维护自家菜品和接单系统管理员管用户、美食分类、全局参数。它砍掉了支付、库存这些重业务保留权限分类、订单状态、分页检索等 SSM 面试里最常考的点反而比商城类项目更容易讲清楚。技术栈是 Spring SpringMVC MyBatis页面用 JSP数据库 MySQLJDK 1.8。适合准备毕业答辩的人也适合想从旧式 JSP 开发往 SSM 框架过渡的读者。下面按表结构、配置、核心链路、部署排错一条线讲。2. 数据库表设计和 Spring/MyBatis 配置把地基打牢2.1 六张核心表支撑三类角色后端权限要落地第一步不是写接口而是把表关系理清楚。这套系统的关键表是用户表、美食分类表、食堂表、菜品信息表、订单主表、订单明细表。用户表里用一个 role_type 字段区分管理员、食堂管理人员和普通用户系统管理模块里的“用户管理”也是对着这张表做增删改查。CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, nickname VARCHAR(32), phone VARCHAR(16), role_type TINYINT NOT NULL DEFAULT 3 COMMENT 1管理员 2食堂管理员 3普通用户, status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE canteen ( id INT PRIMARY KEY AUTO_INCREMENT, canteen_name VARCHAR(64) NOT NULL, location VARCHAR(128), manager_id INT COMMENT 关联sys_user中role_type2的用户, status TINYINT DEFAULT 1, remark VARCHAR(255) ); CREATE TABLE dish_info ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT, canteen_id INT, dish_name VARCHAR(64) NOT NULL, price DECIMAL(10,2) NOT NULL, cover_image VARCHAR(255), monthly_sales INT DEFAULT 0, sort_order INT DEFAULT 0, status TINYINT DEFAULT 1 COMMENT 1上架 0下架 );这三建好之后接口层的查询就基本有方向了菜品表同时挂 canteen_id 和 category_id所以首页既能按食堂过滤也能按美食分类过滤。这里有个容易踩的坑如果把食堂和分类信息直接冗余到菜品表字段是够用但后续改食堂名称或分类名称时要做关联更新正确做法是只存 ID查询时用 JOIN 把名称带出来。订单部分用主从表而不是单表否则无法支持一个订单多个菜品。订单主表记录哪个用户下的、归属哪个食堂、总金额、状态明细表记录每一行的菜品快照。快照的意思是下单那一刻的菜品名称和单价要写死不能之后菜品改名了导致历史订单变成另一道菜。CREATE TABLE order_info ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id INT NOT NULL, canteen_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0 COMMENT 0待确认 1已接单 2已完成 3已取消, remark VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE order_detail ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, dish_id INT, dish_name VARCHAR(64), dish_price DECIMAL(10,2), quantity INT, subtotal DECIMAL(10,2) );设计成这样之后“食堂管理”模块里显示某个食堂的营业额就只需要按 order_id 关联明细再把订单状态限定在“已完成”即可。用户管理模块则可以直接复用 sys_user 表通过 role_type 过滤出不同身份不需要额外建角色表能省掉一层 Spring Security 复杂配置。2.2 web.xml 与 Spring 配置请求从哪里进容器SSM 项目最常见的部署形态是打 WAR 包放到 Tomcat入口在 web.xml。读源码时要先看两段配置ContextLoaderListener 和 DispatcherServlet。前者创建 Spring 根容器管 Service、Mapper、数据源后者创建 SpringMVC 容器管 Controller、视图解析器、拦截器。context-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener servlet servlet-namespringmvc/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namespringmvc/servlet-name url-pattern//url-pattern /servlet-mapping两个容器如果都在做组件扫描边界要清楚spring-mvc.xml 只扫 controllerapplicationContext.xml 只扫 service 和 dao。常见问题是两个配置文件用了相同context:component-scan base-packagecom.xxx/导致 Controller 被扫描两次事务通知匹配不到 Service后台日志能看到 Bean 存在但事务不回滚。拿到源码后先 grep 一下 component-scan 的 base-package再决定要不要拆开。2.3 MyBatis 映射SQL 和 Java 之间的适配层MyBatis 的核心不依赖 SqlSessionTemplate 这些框架类而在 mapper XML 里的动态 SQL。查看源码时重点看if、foreach、where这些标签它们决定了多条件查菜能不能复用。select idselectDishByCanteen resultTypecom.community.entity.DishVO SELECT d.id, d.dish_name, d.price, d.cover_image, c.category_name, canteen.canteen_name FROM dish_info d LEFT JOIN food_category c ON d.category_id c.id LEFT JOIN canteen ON d.canteen_id canteen.id where if testcanteenId ! null and canteenId ! 0 AND d.canteen_id #{canteenId} /if if testcategoryId ! null and categoryId ! 0 AND d.category_id #{categoryId} /if AND d.status 1 /where ORDER BY d.sort_order DESC, d.id DESC /select这段 SQL 里的 if 是重点canteenId 为空时不拼条件categoryId 为空时不拼条件。后面加AND d.status 1是为了保证 JSP 首页不显示已下架菜品。注意不要把 status 的条件也写在 if 里否则用户一旦传了异常值可能把下架菜也查出来。3. 从 JSP 下单到订单状态更新核心链路是怎么跑通的3.1 Controller-Service-DAO 三层链路一次“按食堂点菜”请求怎么走读者拿到源码最容易迷路的地方是请求入口。先看 controller 类上的 RequestMapping再看动态参数从哪里来。以菜品列表为例前端 JSP 里的分页组件传 canteenId、categoryId、pageNumController 只负责接收参数、调用 Service、把结果放到 Model具体计算都在 Service 层完成。RequestMapping(/dishes) public String listByCanteen(Integer canteenId, Integer categoryId, Integer pageNum, Model model) { if (pageNum null) { pageNum 1; } PageHelper.startPage(pageNum, 8); ListDishVO dishList dishService.findByCanteenAndCategory(canteenId, categoryId); PageInfoDishVO pageInfo new PageInfo(dishList); model.addAttribute(pageInfo, pageInfo); return front/dish_list; }用 PageHelper 时有个使用顺序问题必须先在调用 Mapper 之前执行PageHelper.startPage(pageNum, 8)而且紧跟着的那条查询必须是物理查询 SQL中间不能夹带其他查询。Service 里如果先执行订单查询或分类查询分页对象会被绑定到错误的 SQL 上最后发现 JSP 翻页无效先排查这里。3.2 订单状态机下单、接单、完成的边界条件订单模块最值得讲的是状态设计。源码里通常用 int 而不是字符串因为数据库排序和索引更友好改状态时用一条 UPDATE 加状态条件即可能避免并发操作把已取消的订单变成已完成。状态值含义允许的前置状态页面入口0待确认用户提交订单后默认值无食堂后台订单列表1已接单食堂管理员点击确认0食堂后台接单按钮2已完成订单流程结束1用户端确认收货3已取消用户或管理员取消0, 1取消操作Service 层创建订单时主表、明细表要在同一个事务里写代码需要使用 Transactional。这部分是答辩时最容易被追问的所以要清楚事务注解应该写在 Service 实现类上不是 Controller 上。Transactional(rollbackFor Exception.class) public Long createOrder(OrderDTO dto, Long userId) { BigDecimal total BigDecimal.ZERO; for (OrderItemDTO item : dto.getItems()) { Dish dish dishMapper.selectByPrimaryKey(item.getDishId()); if (dish null || dish.getStatus() ! 1) { throw new BizException(菜品已下架 item.getDishId()); } total total.add(dish.getPrice().multiply( new BigDecimal(item.getQuantity()))); } OrderInfo order new OrderInfo(); order.setOrderNo(UUID.randomUUID().toString().replace(-, )); order.setUserId(userId); order.setCanteenId(dto.getCanteenId()); order.setTotalAmount(total); order.setStatus(0); orderMapper.insert(order); dto.getItems().forEach(item - { OrderDetail detail new OrderDetail(); detail.setOrderId(order.getId()); detail.setDishId(item.getDishId()); detail.setDishName(item.getDishName()); detail.setDishPrice(item.getDishPrice()); detail.setQuantity(item.getQuantity()); orderDetailMapper.insert(detail); }); return order.getId(); }这里把金额重新用数据库价格计算而不是直接读取前端传进来的 total是防篡改的基本策略。用户提交订单后食堂端把状态从 0 改成 1 时最好也用UPDATE order_info SET status 1 WHERE id ? AND status 0这样的条件更新返回影响行数为 0 就说明订单已经被别人处理过。3.3 JSP 页面用 JSTL 和 AJAX 兜底两种提交方式JSP 页面的价值在于可以和后端共享数据模型列表渲染通常用 JSTL 完成核心是 c:forEach 和 fmt:formatNumber。不要使用嵌 Java 代码片段那会导致清空 work 目录后编译报错。c:forEach items${pageInfo.list} vardish div classdish-card img src${ctx}${dish.coverImage} alt${dish.dishName} / h4${dish.dishName}/h4 p classprice fmt:formatNumber value${dish.price} pattern0.00 / /p button onclickaddToCart(${dish.id}, ${dish.price})加入购物车/button /div /c:forEachJSTL 标签本质是 Java 代码但容错更好值为 null 时显示空字符串不会直接抛异常。页面里的购物车用一个全局 JavaScript 数组保存最后把数组序列化成 JSON 提交给后端下单接口。提交方式可以用 AJAX也可以用一个隐藏表单做同步提交。function submitOrder() { var items cart.map(function (i) { return { dishId: i.dishId, quantity: i.quantity }; }); $.post(ctx /order/create, { canteenId: currentCanteenId, items: JSON.stringify(items) }, function (res) { if (res.code 200) { window.location.href ctx /order/list; } else { alert(res.message); } }, json); }这段 JS 里的 ctx 是 JSP 页面里通过${pageContext.request.contextPath}拼出来的全局变量目的是防止项目部署在非根路径时 AJAX 请求 404。后台接收 items 时要用RequestBody或手动把 JSON 转成 List不同版本的项目实现方式不一样先看 Controller 方法签名再对齐前端。4. JDK 8、MySQL、Tomcat 环境部署与 JSP 编译排错4.1 从源码到能运行安装步骤有顺序这个项目最常见的问题是开发工具版本和运行环境不匹配。JDK 必须用 8Tomcat 常用 8.5MySQL 建议 5.7 或 8.0 的小版本。若用 JDK 17 跑传统 SSM 项目经常会遇到反射访问受限、cglib 报错、jsp servlet 依赖缺失所以安装文档里给的 JDK 1.8 不是随便写的。基础环境变量配置如下。JAVA_HOMEC:\Java\jdk1.8.0_202 Path%JAVA_HOME%\bin;C:\apache-tomcat-8.5.84\bin;数据库导入脚本时先看 sql 文件开头有没有 CREATE DATABASE。如果有直接用命令行导入不要新建空库再导入否则表被建到另一个库web 项目连接报 “Unknown database”。推荐做法是登录 MySQL 后执行mysql -u root -p source C:/community_canteen/canteen.sql;注意 source 路径里的分隔符最好写正斜杠容易踩坑。导入完成后打开 src/db.properties 或 jdbc.properties确认 url、username、password 三项和当前数据库一致。这里最常见的错误是数据库密码里有特殊字符例如或#如果没转义连接串会被截断启动日志报 Access denied 或 Communications link failure。4.2 “环境工具包”里到底放了什么购买或下载的压缩包里的“环境工具包”通常不是同一个版本打开后先看文件类型再决定是否安装。一般包含以下几种JDK 8 安装包、Tomcat 8.5 压缩版、MySQL 安装程序、Navicat 或 SQLyog 客户端、开发工具 Eclipse EE 或 MyEclipse以及一个“说明文档”。表里列出了使用优先级。工具来源用途建议JDK 1.8oracle .exe编译和运行安装后命令行验证java -versionTomcat 8.5apache 压缩包部署 WAR优先选解压版路径不能有中文MySQL 5.7mysql installer保存业务数据记得服务启动类型改成手动Navicat第三方导入 SQL、改数据也可以用命令行代替Eclipse IDEA任意修改源码导入时勾选 Existing Projects into Workspace另外要确认包内是否带 Maven。如果不是 Maven 工程项目根目录不会有 pom.xml所有 jar 都放在 WebContent/WEB-INF/lib 下。在 IDEA 里导入这类项目时不要选择 Maven 类型而应该用普通 Web 项目方式导入然后把 lib 目录 Add as Library否则启动时 Spring 容器会报找不到 SqlSessionFactoryBean。4.3 启动报错和 JSP 编译文件位置的排查方向很多人在页面出现 404 或数据库报错时反复清浏览器缓存实际上 Tomcat 里 JSP 的编译产物不是源码目录而是 work 目录。第一次访问 JSP 时Tomcat 会把它翻译成 Java再编译成 class缓存位置是Tomcat/work/Catalina/localhost/项目名/org/apache/jsp。修改 JSP 后如果页面仍然显示旧版用 Project Clean 清一遍项目再删除 work 目录里对应项目文件夹然后重启。现象典型原因处理启动端口占用8080 被其他服务占用netstat -ano数据库中文乱码url 缺 characterEncoding 参数在 jdbc.properties 中改成characterEncodingutf8页面 404项目没有发布到 Tomcat webapps检查 Deployment 的 Application context 是否为/canteenIDEA 运行后没有 artifact没有把 Web 模块构建到 lib打开 Project Structure检查 Web Facet 的打包JSP 修改后不生效Tomcat 缓存编译产物删除work/Catalina/localhost/项目名后重启后台 Java 文件修改后未生效则要区分是 IDEA 的 Build 缓存还是 Tomcat 的 classloader 缓存。Eclipse/MyEclipse 点 Publish 之后Tomcat 的 runtime 目录会生成一个原来项目的副本只重新编译 Original Project 是不够的必须看发布目录里 classes 文件的时间戳。5. 拿到论文和答辩 PPT 之后上线前先改这几处5.1 用 SQL 替换演示数据让页面和论文截图一致答辩演示最怕的是页面里还是“张三”“李四”这类初始化数据。拿到源码后直接用 UPDATE 把食堂、菜品、分类改成你自己单位或社区的真实命名再重新导出 sql 文件。这样论文里的截图和实际演示环境一致省去现场输入数据的时间。UPDATE canteen SET canteen_name 社区北区食堂, location 3号楼一层 WHERE id 1; UPDATE dish_info SET dish_name 土豆牛腩饭, price 16.00, monthly_sales 128 WHERE id 1;修改完以后不要把 sql 脚本回导到原库因为订单表的 user_id 和 canteen_id 有外键关系改完食堂 ID 后订单数据可能出现孤儿记录。5.2 在下单入口加一个菜品状态校验原版项目可能在 JSP 页面已经判断了菜品上架状态但后端 Service 没有做同样校验。这是答辩时一个低风险高收益的加强点。在订单详情循环里直接把下架菜品拦截下来。if (dish null || dish.getStatus() ! 1) { throw new BizException(菜品已下架 item.getDishId()); }这段代码的位置可以在订单总金额计算之前。加了这层判断后即使 JSP 页面被绕过直接构造 POST 请求也无法对下架菜下单。答辩时被问到“前端和后端校验哪个更重要”可以直接说前端提升用户体验后端才是安全边界这句话能接住大部分追问。5.3 改权限列表时注意缓存依赖项目里最常见的二次开发是“用户管理”模块新增角色。如果你把 sys_user 的 role_type 从 3 扩成 5记得同步改登录用户的 Session 和菜单拦截器。很多 SSM 项目的 shiro 或 interceptor 是直接写死roleType 1来判断管理员光加字段不更新拦截逻辑改完依然访问不了管理页面。可以写一个公共方法判断当前登录用户是否有权限再在需要权限的 Controller 方法上调用。从社区点餐这个业务出发最值得关注的不是页面外观而是订单状态、菜品上下架和权限这三个枢纽。把这三个卡点跑通这个项目转成其他行业的后台系统也不会断骨。本文还有配套的精品资源点击获取
返回列表