ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java毕设实战:SpringBoot+SSM网上订餐系统从零搭建全解析

Java毕设实战:SpringBoot+SSM网上订餐系统从零搭建全解析 摩尔街这套“基于JavaSpringBootSSM的网上订餐系统”我在帮学生改项目、带课程设计的时候见过很多次也亲手从零搭过类似结构的外卖平台。说实话这题目在Java方向的毕设和课设里属于高频典型SpringBoot负责后端快速开发SSMSpringSpringMVCMyBatis作为底层整合框架解决业务逻辑与数据访问问题前端配合Bootstrap这类轻量框架做页面展示整体难度对中等基础的同学来说刚刚好卡在“能做完”和“有东西可写”之间。这篇博客我不打算只贴目录和截图而是把这套系统从需求梳理、数据库设计、核心流程实现到本地复现、调试排查、论文整理的完整链路讲透。不管你是拿它当毕业设计还是想自己练手做一个小型电商点餐平台照着这套思路都能少踩很多坑。1. 项目整体设计与技术选型1.1 为什么是SpringBootSSM而不是纯SSM很多人一看到标题“SpringBootSSM”会觉得奇怪SpringBoot本身是简化Spring配置的框架SSM是SpringSpringMVCMyBatis的组合这两者听起来有重叠但实际上搭配非常合理。纯SSM项目最痛的点是配置地狱。web.xml、spring-mvc.xml、spring-mybatis.xml、数据库连接池配置、事务管理器配置一套下来少说几十行XML动不动就报Bean创建异常。SpringBoot用自动配置和约定大于配置的思想把大部分重复工作收进了starter依赖里application.yml一行配置就能替代原来五六行的XML。但SpringBoot并没有替代SpringMVC和MyBatis它的底层仍然是Spring IoC容器和SpringMVC的DispatcherServletMyBatis也照样通过Mapper接口操作数据库。所以在这个项目里SSM是骨SpringBoot是皮皮让骨架更好看、更好用。1.2 三层架构与模块划分网上订餐系统的业务并不复杂但是涉及的角色多顾客、商家、管理员。每类角色操作的后台逻辑差异很大所以在设计初期一定要分模块否则代码写到后面就是一团乱麻。我建议这样划分顾客端前台用户注册登录、浏览菜品、加入购物车、下单、支付、查看订单状态商家端后台菜品管理增删改查、分类管理、订单接单/出餐/完成操作、销量统计管理端平台后台商家审核、用户管理、订单管理、数据报表对应的Controller层也需要分开比如CustomerController、SellerController、AdminController不要全塞在一个Controller里。Service层负责业务逻辑Mapper层只做数据存取严格的单一职责能让后期调试轻松很多。1.3 技术栈清单与版本建议这里列一个我自己实测过的版本组合稳定性比较好不容易出现依赖冲突组件版本/说明JDK1.88SpringBoot2.3.x 或 2.5.xMyBatis3.5.xMyBatis-Spring-Boot-Starter2.2.x 或 2.3.xMySQL5.7 或 8.0Maven3.6以上前端Thymeleaf 或 JSP Bootstrap jQuery开发工具IDEA 2020 或 Eclipse注意SpringBoot 2.x的官方维护虽然已经进入尾声但作为毕设/课设2.5.x版本足够稳定网上资料也多出了问题容易搜到解决方案。不建议一上来就用SpringBoot 3.x因为3.x基于JDK17很多老教程里的配置会失效排查成本高。2. 数据库设计网上订餐系统的表结构拆解2.1 核心数据表梳理数据库设计是这类项目的灵魂。外卖订餐和普通电商不一样它天然带有“位置、时效、接单”的业务语义所以表的设计要重点考虑订单状态流转。我一般会建这几张表user表用户表用户id、昵称、手机号、密码、头像、角色标识0顾客/1商家/2管理员、注册时间seller表商家表商家id、店铺名称、店铺地址、联系电话、营业状态、评分food表菜品表菜品id、所属商家id、分类id、菜品名称、图片、单价、库存、月销量category表菜品分类表分类id、分类名称、所属商家idcart表购物车表购物车id、用户id、菜品id、数量、单价快照orders表订单表订单id、订单编号、用户id、商家id、订单金额、订单状态、收货地址、备注、创建时间order_detail表订单明细表明细id、订单id、菜品id、菜品名称快照、数量、单价值得一提的是一定要加上address表收货地址表。很多毕设懒得做多地址功能只留一个字段结果答辩时老师问你“用户换地址怎么处理”就很难圆回来。哪怕做一个简易的多个地址切换也是一个亮点。2.2 订单状态流转与快照机制订单状态是订餐系统里最容易乱的地方。我在设计时用的是int值0到5对应不同阶段0-待支付1-待接单2-商家已接单制作中3-配送中4-已完成5-已取消状态流转不能乱跳。比如待支付状态下只能取消或支付待接单状态商家可以接单顾客也能取消一旦进入制作中就不能随便取消。为什么要用状态值而不是直接改字符串一是数据库存储效率更高二是Java枚举配合switch更容易维护三是后期做统计报表时可以直接按数字分组。另外订单明细一定要做快照。也就是说下单那一刻把菜品的名称和单价冗余到order_detail里。因为商家可能会改价、改菜名甚至下架菜品如果订单明细里关联的是实时菜品表那用户查历史订单时看到的信息可能会变这对系统来说是不可接受的。2.3 购物车与库存的并发问题购物车表cart的核心字段是user_id food_id的组合前端每次点击加购物车后端先去查这个组合是否已存在存在则数量加1不存在则新增记录。这块逻辑不复杂但很多人会忽略库存校验。我的经验是加入购物车时不校验库存展示商品时实时显示库存情况真正扣减库存放在下单接口里。为什么因为购物车是一个临时状态用户可能加了菜最后不下单你频繁锁库存会导致菜品卖不出不划算。下单成功后一次性扣减同时用悲观锁或乐观锁避免超卖问题。班级项目里并发量不高用SELECT ... FOR UPDATE就足够了。实际上在JVM层面单机部署时我会在Service方法上加上synchronized或者使用数据库的乐观锁版本号字段来防止并发扣减。虽然有点重但答辩时你能讲出“我用了乐观锁确保库存不超卖”这是一个非常加分的点。3. 核心业务流程与关键代码实现3.1 登录鉴权从Session到简易Token网上订餐系统涉及三类用户最朴素的实现是Session存用户id配合拦截器做放行控制。但如果你想让项目显得比同学高一个档次可以用拦截器自定义注解做权限校验或者直接在Controller参数里加一个当前用户的对象。我的习惯是写一个LoginInterceptor实现HandlerInterceptor接口在preHandle里面从请求头里读取token登录时生成UUID存到数据库或者内存Map然后解析出用户对象放到ThreadLocal里。这样Controller里到处能拿当前用户不用在每个方法里传HttpSession。代码大致是这样public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || .equals(token)) { response.setStatus(401); return false; } // 根据token查询用户 User user userService.getUserByToken(token); if (user null) { response.setStatus(401); return false; } UserThreadLocal.set(user); return true; } }提示这种简易Token方案不依赖Redis适合没有引入中间件的课程设计项目。如果你们要求必须用Redis也可以把Token存到Redis里核心逻辑一样只是存取位置变了。3.2 下单功能的流程拆解下单是订餐系统的核心业务最少涉及四步操作校验库存、生成订单主表、生成订单明细表、扣减库存。所以我强烈建议在Service层加上Transactional注解保证多张表的写入要么全部成功要么全部回滚。下单流程的伪代码Transactional public Long createOrder(CreateOrderRequest request) { // 1. 查询购物车数据根据用户id ListCartItem cartItems cartMapper.findByUserId(request.getUserId()); // 2. 遍历校验库存 for (CartItem item : cartItems) { Food food foodMapper.selectByPrimaryKey(item.getFoodId()); if (food.getStock() item.getQuantity()) { throw new BizException(菜品【 food.getName() 】库存不足); } } // 3. 计算总金额 BigDecimal total calculateTotal(cartItems); // 4. 创建订单主记录 Orders order new Orders(); order.setOrderNo(generateOrderNo()); // 时间戳随机数 order.setUserId(request.getUserId()); order.setTotalAmount(total); order.setStatus(0); // 待支付 ordersMapper.insert(order); // 5. 批量插入订单明细快照 for (CartItem item : cartItems) { OrderDetail detail new OrderDetail(); detail.setOrderId(order.getId()); detail.setFoodId(item.getFoodId()); detail.setFoodName(item.getFoodName()); // 快照 detail.setPrice(item.getPrice()); // 快照 detail.setQuantity(item.getQuantity()); orderDetailMapper.insert(detail); } // 6. 扣减库存 foodMapper.decreaseStock(item.getFoodId(), item.getQuantity()); // 7. 清空购物车 cartMapper.deleteByUserId(request.getUserId()); return order.getId(); }这段逻辑每个步骤都有对应的数据库操作却都在同一个事务里。如果不加事务用户下单过程中数据库突然断电很可能出现“订单主表有了、明细没有、库存已经扣了”的脏数据答辩时这种bug被抓住就很难受。3.3 订单编号生成策略订单编号看起来不起眼但最容易出问题。很多新手直接用SimpleDateFormat生成“yyyyMMddHHmmss”格式结果同一秒内下单就会重复。我推荐用时间戳随机数的组合String orderNo System.currentTimeMillis() String.format(%04d, new Random().nextInt(10000));这样每秒可以容纳一万个不重复订单对于课设来说完全足够。如果要求更严格一点可以用UUID的前8位拼接时间戳但UUID太长订单明细表里做索引不太友好而且展示给用户看也不美观。3.4 后端分页与模糊搜索的通用写法菜品搜索是网上订餐系统标配功能。MyBatis分页如果你不想手写LIMIT可以用PageHelper插件但是PageHelper有个坑就是它基于ThreadLocal实现如果SQL执行时上下文不是同一个线程分页会失效。我个人倾向于手写一个简单的分页查询参数对象包含pageNum和pageSizeMapper里写select idsearchFoods resultTypecom.moore.pojo.Food SELECT * FROM food where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testcategoryId ! null AND category_id #{categoryId} /if /where LIMIT #{offset}, #{pageSize} /select注意这里的offset是(pageNum-1)*pageSize最好在Service层算好再传进去而不是在Mapper里写乘法。原因很简单业务逻辑尽量留在Service层Mapper只做参数生效。前端商品列表部分我建议用简单的卡片式布局每个菜品卡片显示图片、名称、价格、月销量、库存状态。加入购物车用Ajax提交避免整个页面刷新用户体验会好很多同时也方便你对接后端的JSON接口。3.5 支付模块的模拟实现真正的支付接口需要对接微信支付或支付宝需要商户号、证书、回调地址毕设环境一般不具备条件。最省事也最安全的做法是做一个模拟支付用户点击“去支付”弹出一个模拟支付页面确认后直接修改订单状态为“1-待接单”。模拟支付页面可以做一个倒计时确认后订单状态置为待接单同时给商家端推送一个订单提醒这里用简易的轮询即可不需要WebSocket否则复杂度爆炸。如果你想让项目看起来更有亮点再在支付页面加一个支付金额展示和“余额支付”为了这个再去建一张user_account表也未尝不可。4. 项目复现与调试实录从0到能跑通的完整过程4.1 环境准备与基础配置拿到源码包后的第一件事不是点运行而是检查自己的环境。我见过太多人卡在启动阶段其实多半是环境变量有问题。第一步装好JDK 1.8确保命令行能输出java -version第二步装好Maven并确认settings.xml里配置了阿里云镜像第三步装MySQL建库建用户赋予权限第四步用IDEA打开项目等待Maven依赖下载完成。项目里的核心配置在src/main/resources/application.ymlserver: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/moore?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.moore.pojo注意MySQL 8.0以上必须指定serverTimezone否则启动报时区错误。driver-class-name也要用com.mysql.cj.jdbc.Driver老版本的com.mysql.jdbc.Driver已经不能用了。4.2 数据库脚本导入的坑与技巧源码包里一般会有sql文件通常是moore.sql或者db_moore.sql。先用Navicat或命令行建好空数据库命名和配置文件保持一致然后右键导入SQL脚本。导入后一定要做的事检查所有表是否创建成功检查测试账号的密码有没有被加密存储检查菜品图片路径是相对路径还是URL如果账号密码是MD5加密的你需要知道对应的明文。我常用的是“admin/admin123”或者“123456”直接MD5‘e10adc3949ba59abbe56e057f20f883e’。如果不知道明文可以直接在数据库里执行UPDATE user SET passworde10adc3949ba59abbe56e057f20f883e WHERE usernameadmin把密码重置成123456。4.3 常见启动报错与排查方案我整理了一份自己调试这个项目时反复踩过的坑按出现频率排个序报错信息/现象根本原因解决方法Access denied for user rootlocalhost数据库密码错误检查application.yml的密码注意特殊字符ClassNotFoundException: com.mysql.cj.jdbc.DriverMaven依赖没下载reimport项目检查pom.xml是否有mysql依赖Port 8080 was already in use端口被占杀掉占用进程或改server.portInvalid bound statement (not found)Mapper接口和XML没绑定检查MyBatis配置的mapper-locations路径是否匹配Table moore.user doesnt exist数据库连错或脚本没导入核对数据库名和表名页面中文乱码URL编码或编辑器编码不一致检查IDEA文件编码为UTF-8数据库连接加characterEncoding4.4 源码阅读顺序建议很多学生拿到源码第一反应是从Controller开始读结果云里雾里。我建议按这样的顺序阅读先看数据库表结构理解实体之间的关系再看pojo/entity包对照表字段看Mapper接口和XML理解每个SQL的作用看Service接口和实现类先看业务逻辑流转最后看Controller理解请求入口和参数绑定这样一层层从下往上读逻辑是顺的。如果直接看Controller很容易被各种方法签名绕晕也不知道某个service方法背后到底执行了什么SQL。5. 文档撰写与答辩实战经验5.1 LW论文的写作框架与字数分配这类项目的配套LW也就是毕业设计论文通常要求1.2万字左右。我的经验是不要平均分配重点放在需求分析和系统设计上。建议章节结构绪论背景意义、国内外研究现状、论文结构约2000字相关技术介绍SpringBoot、SSM、MySQL、Bootstrap介绍不要写太多源码重点讲为什么选它约1500字系统分析可行性分析技术、经济、操作、需求分析功能性需求、非功能性需求约2000字系统设计总体架构设计、功能模块设计、数据库设计E-R图、表结构约3000字系统实现分模块贴核心代码和截图每段代码配文字说明约2500字系统测试测试用例表、测试结果、性能测试简单负载约1500字总结与展望完成工作、不足、改进方向约500字论文中最容易扣分的是数据库设计部分光贴SQL建表语句是不够的必须画E-R图。E-R图不用太专业用ProcessOn或者Draw.io画清楚就行实体、属性、联系缺一不可。5.2 调试文档的写法调试文档本质上是运维笔记。源码包里带一份调试文档的话你应该按这样的结构写环境要求JDK、Maven、MySQL版本项目导入步骤IDEA打开项目、配置Maven、修改application.yml数据库初始化导入脚本、创建数据库、修改账号密码启动顺序先MySQL再启动SpringBoot最后浏览器访问默认账号列表管理员、商家、顾客的测试账号常见错误处理把上面那个表格搬进去这份文档写完不只是应付交差对你自己的答辩也有用。老师问“项目怎么部署”你可以一气呵成地讲出来印象分会好很多。5.3 答辩高频问题与加分回答答辩时老师最喜欢问的几个问题我提前帮你们预演一遍问你的项目为什么用SpringBoot还要写SSM答SpringBoot解决的是配置复杂的问题但它的核心IoC容器仍然是SpringMVC层仍然是SpringMVCORM层用MyBatis所以本质上就是SSM框架的SpringBoot版本。SpringBoot通过自动配置和starter依赖把以前需要手写的大量XML配置封装起来开发效率更高但底层的请求处理流程和数据访问方式没有变。问订单超时未支付怎么处理如果你用了定时任务或延迟队列就能加分如果没实现也不能慌可以回答当前版本采用用户手动取消和商家拒绝订单的机制规避超时问题。后续可以引入延迟队列或者Spring的Scheduled定时扫描订单表把超过30分钟未支付的订单自动置为已取消并释放库存。问你项目里如何保证数据库和安全密码采用MD5加密存储SQL全部用PreparedStatement参数化过滤XSS和SQL注入风险。订单和库存操作使用事务Transactional保证数据一致性。5.4 二次开发方向建议如果学有余力我建议在这个项目上做三个小改造范围小但能显著提升技术含量引入Redis做Token管理和菜品缓存让登录态和热门菜品不频繁查数据库使用WebSocket实现商家实时接单提醒这样顾客下单后商家页面不需要手动刷新就能看到新订单引入RabbitMQ或者简单的阻塞队列处理下单后的短信通知逻辑模拟把异步解耦体现出来这三个方向改完你的项目在班里基本就是第一梯队了。但要注意一次别全做完每个改动都够写2000字论文内容结合你自己时间决定。6. 关于这套系统的几点个人体会从带过的学生和对接过的真实需求来看网上订餐系统的技术难点从来不在“功能多”而在“状态准、数据稳、流程顺”。SpringBootSSM的组合刚好用最小的配置成本把这三点兜住了——SpringBoot管理依赖和自动装配Spring管业务对象SpringMVC管请求分发MyBatis管SQL和数据映射各司其职。我试过把这个系统部署到云服务器上演示除了把数据库连接地址改成云数据库的IP之外几乎不用改别的代码。这说明这套架构的迁移成本低对环境依赖小也是它能常年在毕设和课设里“霸榜”的原因。最后再分享一个小技巧如果你在本地调试时总是觉得启动慢可以在IDEA里把Maven测试跳过然后在SpringBoot的VM options加上-Dspring.devtools.restart.enabledfalse开发阶段有热部署工具的话可以开着。真等答辩演示时可以提前启动好项目别在台上盯着控制台等加载这个小细节很多人不注意结果现场翻车。
返回列表