写了这么多年Java后端,每年五六月份都会被问一次“基于Spring Boot的快餐订餐系统怎么做”。这个题目在计算机毕业设计里属于典型的“常青树”:业务上不难理解——点餐、下单、支付、出餐,是一条非常标准的交易链路;技术上又不简单——涉及登录鉴权、数据库设计、缓存、事务、统计报表,正好能把Spring Boot的主要能力完整过一遍。更关键的是,这个题材的扩展空间很大,有的同学在此基础上加了多商户入驻,有的做了扫码点餐,有的接了地图配送,这些都说明它的底子搭得好。
这篇文章就把一个完整的快餐订餐系统,从需求拆解到Spring Boot实现,再到论文答辩可能遇到的问题,按我实际操作时的顺序讲一遍。适合正在做毕业设计、准备Java后端面试,或者想用Spring Boot做一个拿得出手的完整项目的同学参考。我下面写的代码结构和配置,都是可以直接抄作业的版本,但也建议你抄完以后动手改一改,因为答辩老师问得最多的,往往就是你自己的那一部分改动。
1. 项目定位与需求拆解:快餐订餐系统到底做什么
1.1 业务场景分析
快餐订餐系统的核心场景是:顾客在手机上浏览菜单、加购、下单,商家在后台接单、出餐、完成订单。这个场景在真实世界里对应着学校食堂、写字楼周边快餐店、社区餐饮店等。业务过程不复杂,但每天要处理大量类似的请求,所以系统的重点是稳定和清晰。
从业务视角看,它本质上是把传统的“到店点单-排队付款-等待出餐”流程,改成了线上化的“选菜-下单-支付-到店自取/外卖配送”。这个业务模型的难点不在于单个功能,而在于订单状态的完整流转。从用户提交订单到商家确认,再到制作完成、取餐或送达,每一个节点都要在后台可见,否则系统就是不可信的。我见过不少同学的代码,用户把订单提交后,管理端却查不到任何待处理的订单记录,这种“前后端断链”的问题,在毕设演示时基本等于直接暴露项目完成度。
1.2 功能模块拆分
功能拆成两个角色来看,会清晰很多。
用户端:注册登录、菜品分类浏览、菜品详情、加入购物车、提交订单、模拟支付、订单列表与状态查看、个人信息修改。
管理端:菜品分类管理、菜品上架下架与价格修改、订单处理(接单、出餐、完成)、当日销售统计、用户管理。
这两个角色通过一张用户表里的role字段区分。管理员走同一个登录入口,登录后根据角色跳转到不同页面。如果想让项目更有亮点,可以在普通用户和管理员之间再加一个“商家”角色,让商家只能看到自己的菜品和订单,这就把单店模型扩展成了多商户模型。工作量增加得不多,但答辩时讲出来会高级不少。
1.3 需求到Spring Boot特性的映射
需求确定以后,很重要的一件事是把它翻译成技术选型。我列一张自己的映射表:
- 注册登录 → Spring MVC实现接口,密码用BCrypt加密,登录后通过JWT生成令牌,再用拦截器做统一鉴权;
- 菜品查询 → MyBatis实现SQL查询,菜品列表加Redis缓存,减少每次请求查询数据库的压力;
- 购物车与订单 → 购物车是典型的关联表操作,下单过程涉及订单主表、订单明细表、购物车状态变更,必须用@Transactional保证事务;
- 后台统计 → 用MySQL聚合函数和简单的日期分组统计,不涉及大数据量,不需要额外引入框架;
- 前后端交互 → 可以单独写一个Vue页面,也可以用Thymeleaf服务端渲染,两种方案都能拿分,我后面会专门讲它们的差别。
这一步想清楚,后面写代码就不是“想到哪写到哪”,而是每一段代码都有它在整个架构中的位置。这也是论文里“需求分析”和“系统设计”两章真正的素材来源。
2. 技术选型与脚手架搭建:为什么这么选
2.1 Spring Boot版本与JDK版本的选择
老生常谈的问题:Spring Boot版本到底选几?
如果你的电脑里装的是JDK 1.8,那Spring Boot建议锁定在2.7.x,这是Spring Boot 2系列最后一个维护版本,兼容性最好,网上资料也最多。如果用的是JDK 17,就可以选Spring Boot 3.x,但要注意一个细节:Spring Boot 3把javax.servlet体系换成了jakarta.servlet,很多老教程里的import javax.*在3.x里面直接编译报错。
对于毕设这种“求稳”的项目,我个人的建议是:JDK 8 + Spring Boot 2.7.x,除非导师明确要求用新版。不要盲目追新版本,版本太高反而容易在环境上浪费大量时间。这就是搜索热词里“springboot版本太高”对应的真实痛点——很多同学一上来装了最新的Spring Boot 3.3或3.4,结果发现MyBatis、Thymeleaf、Redis的starter版本要联动调整,光处理依赖冲突就耗掉一两天。
2.2 项目骨架与持久层方案
写毕设时我习惯的目录结构是:
com.example.fastfood ├─ FastfoodApplication.java ├─ config/(跨域配置、拦截器配置) ├─ controller/(用户端接口、管理端接口) ├─ service/(业务层接口与实现) ├─ mapper/(MyBatis接口) ├─ entity/(数据库实体类) ├─ dto/(前端交互对象) ├─ utils/(JWT工具类、统一返回结果类) └─ resources/ ├─ mapper/(MyBatis XML) └─ application.yml持久层我用MyBatis而不是JPA。理由是:实际工作中MyBatis在互联网公司用得更多,SQL可控性强。订单明细这种一对多查询、多表联查,用MyBatis写XML比JPA直观很多。现在Spring Boot整合MyBatis已经很成熟,只需要在pom.xml里加一个mybatis-spring-boot-starter,再在application.yml里配好mapper-locations路径即可。
2.3 配置文件与数据库设计细节
application.yml的核心配置如下:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/fastfood?useUnicode=true&characterEncoding=utf8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true jwt: secret: fastfood-secret-key expire: 604800这里面有几个细节容易踩坑。MySQL 8.x必须把driver-class-name写成com.mysql.cj.jdbc.Driver,老文章里的com.mysql.jdbc.Driver是MySQL 5时代的写法,直接导致启动时报Cannot load driver class。数据库名、密码是本地环境的,提交代码前记得换成占位符或者单独注释说明,避免答辩换机器时连接失败。打开map-underscore-to-camel-case以后,数据库里的user_id才能自动映射成实体的userId,这个开关能省掉大量字段映射代码。
自定义jwt配置项放在application.yml里,启动参数可以随时改,不用重新编译代码,这种设计在“配置文件”相关提问里也算一个不错的回答角度。
数据库设计方面,用户表、菜品表、订单表、订单明细表我给出核心建表SQL,便于你直接初始化:
CREATE TABLE `user` ( `id` int NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL UNIQUE COMMENT '用户名', `password` varchar(100) NOT NULL COMMENT 'BCrypt加密后的密码', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `avatar` varchar(255) DEFAULT NULL COMMENT '头像地址', `role` tinyint DEFAULT 0 COMMENT '0普通用户 1管理员', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `dish` ( `id` int NOT NULL AUTO_INCREMENT, `category_id` int NOT NULL COMMENT '分类id', `name` varchar(100) NOT NULL COMMENT '菜品名', `price` decimal(10,2) NOT NULL COMMENT '价格', `image` varchar(255) DEFAULT NULL COMMENT '图片', `description` varchar(500) DEFAULT NULL COMMENT '描述', `status` tinyint DEFAULT 1 COMMENT '0下架 1上架', `sort` int DEFAULT 0 COMMENT '排序权重', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `orders` ( `id` int NOT NULL AUTO_INCREMENT, `order_no` varchar(32) NOT NULL COMMENT '订单号', `user_id` int NOT NULL COMMENT '下单用户', `total_amount` decimal(10,2) NOT NULL COMMENT '总金额', `status` tinyint NOT NULL DEFAULT 0 COMMENT '0待支付 1已支付 2制作中 3待取餐 4已完成 5已取消', `pay_time` datetime DEFAULT NULL COMMENT '支付时间', `remark` varchar(200) DEFAULT NULL COMMENT '备注', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `order_item` ( `id` int NOT NULL AUTO_INCREMENT, `order_id` int NOT NULL COMMENT '订单id', `dish_id` int NOT NULL, `dish_name` varchar(100) NOT NULL COMMENT '下单时的菜品名快照', `price` decimal(10,2) NOT NULL COMMENT '下单时的价格快照', `quantity` int NOT NULL COMMENT '数量', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;订单明细表里保存dish_name和price这两个冗余字段,是我强烈建议的做法。它们是从菜品表“复制”过来的快照,好处是订单一旦生成,即使商家后面修改了菜品名称或价格,历史订单的结算金额也不会受影响。这个细节未必在答辩时被直接问,但能说明你理解“订单是历史凭证”这一业务本质。
2.4 一个自己动手的Banner
虽然不是核心功能,但“Spring Boot启动时的Banner”是搜索热词里的高频词,也是很多同学做完项目后喜欢截图分享的细节。你可以在在线Banner生成器里做一段ASCII艺术字,把它粘贴到resources/banner.txt里。启动项目时,原来的Spring Boot标志会被替换成你自己的文字。这个操作五分钟就能完成,不影响功能,但能给你带来一点项目归属感,也适合出现在论文的开发环境截图里。
3. 核心模块实现:从注册到下单的完整链路
3.1 用户注册与JWT登录鉴权
用户表user的设计前面已经给出,username做唯一索引,password字段用BCrypt加密后存储。不要用MD5存密码,MD5是摘要算法,无法抵抗彩虹表攻击。我见过不少毕设还在用MD5,答辩时如果能说一句“密码经过BCrypt哈希存储”,反而是体现安全意识的加分点。
JWT登录的主要流程是:用户登录成功后,后端生成一个token返回给前端。前端之后每次请求在请求头Authorization带上这个token。后端用一个拦截器拦截需要登录的路径,解析token,拿到用户id和role,再判断放行还是拒绝。
核心拦截器逻辑:
public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token == null || !JwtUtil.verify(token)) { response.setStatus(401); return false; } Integer userId = JwtUtil.getUserId(token); request.setAttribute("userId", userId); return true; } }注意拦截器里只需要校验token合法性,不要在拦截器里查询用户身份。拦截器对每个请求都会执行,如果每次都查一次数据库,接口响应会明显变慢。正确做法是把用户id从token里解出来,放进request的attribute里,业务方法需要时再取。
JWT工具类的核心逻辑:
public static String generateToken(Integer userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim("role", role) .setExpiration(new Date(System.currentTimeMillis() + 604800000L)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); }过期时间建议设置7天左右。太短的话用户要频繁登录,太长了安全性会差。签名密钥写在application.yml里而不是硬编码在代码中,这样不同环境部署时可以切换密钥。
登录接口还有一个容易被忽略的问题:同一个用户名重复登录。毕设可以不实现严格意义上的单点登录,但最好在Redis里存一份“用户当前有效token”,修改密码或退出登录时删除token,这样安全性和逻辑完整度都比纯JWT更好。这个点属于加分项,有兴趣的同学可以自己加。
3.2 菜品管理、分类查询与Redis缓存
菜品表dish的字段前面已经列出,价格用DECIMAL(10,2)而不是float,float在涉及金额时会出现诡异的小数误差。status表示上下架状态,用户端只查status=1的菜品,管理端要能查到全部。
菜品列表加Redis是本系统最容易被答辩老师追问的点,也是最基础的缓存实践。基本思路是:
- 查询菜品列表时先查Redis,key按分类设计,比如dish:list:cat:1;
- 如果Redis里没有,就去查MySQL,把结果写入Redis并设置过期时间,比如30分钟;
- 管理端修改菜品或上下架时,主动删除Redis中相关的key,保证下一次查询读到最新数据。
这套逻辑在真实项目里叫Cache Aside Pattern。在毕设里实现它并不难,但能证明你理解缓存不是只会用RedisTemplate。答辩时可以顺势讲清楚:过期时间设成30分钟而不是永久,是为了避免商家改完价格后用户端还显示旧价格的问题。
接口伪代码如下:
@GetMapping("/list") public Result list(Integer categoryId) { String cacheKey = "dish:list:cat:" + (categoryId == null ? 0 : categoryId); String cached = redisUtil.get(cacheKey); if (cached != null) { return Result.ok(cached); } List<Dish> dishList = dishMapper.selectDishList(categoryId); redisUtil.set(cacheKey, JSON.toJSONString(dishList), 1800); return Result.ok(dishList); }这里有一个容易踩的坑:Redis的key不要用中文分类名。如果你拼成dish:list:汉堡,URL编码和缓存读取都会出乱码问题。所以key尽量用英文拼接+id。
3.3 购物车与下单事务
购物车我用独立的cart表:id、user_id、dish_id、quantity、create_time。用户对同一道菜重复添加时,应该执行“数量更新”而不是“插入新记录”。这个业务细节很多同学会漏掉,导致购物车列表里同时出现两行“汉堡5元”,非常不专业。
下单流程是本系统的核心,我拆成五个动作:
- 校验购物车非空;
- 生成订单号order_no并计算总金额;
- 在orders表插入订单主记录;
- 遍历购物车,在order_item表插入订单明细;
- 清空购物车。
这五个动作必须在同一个事务里执行,任何一个步骤失败都要整体回滚。实现方式就是在service方法上加@Transactional注解。我用一个例子说明为什么重要:假设第4步插入订单明细失败,但第3步订单主记录已经插入成功,那用户付款后会看到一个“没有菜品的订单”,这种数据不一致在答辩演示时直接暴露。
下单Service的核心代码:
@Transactional(rollbackFor = Exception.class) public Long submitOrder(Integer userId, String remark) { List<Cart> cartList = cartMapper.selectByUserId(userId); if (cartList == null || cartList.isEmpty()) { throw new BusinessException("购物车为空"); } BigDecimal total = BigDecimal.ZERO; String orderNo = generateOrderNo(); for (Cart cart : cartList) { Dish dish = dishMapper.selectById(cart.getDishId()); total = total.add(dish.getPrice().multiply(BigDecimal.valueOf(cart.getQuantity()))); } Order order = new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setTotalAmount(total); order.setStatus(0); orderMapper.insert(order); for (Cart cart : cartList) { Dish dish = dishMapper.selectById(cart.getDishId()); OrderItem item = new OrderItem(); item.setOrderId(order.getId()); item.setDishId(dish.getId()); item.setDishName(dish.getName()); item.setPrice(dish.getPrice()); item.setQuantity(cart.getQuantity()); orderItemMapper.insert(item); } cartMapper.deleteByUserId(userId); return order.getId(); }这里注意@Transactional注解里我写了rollbackFor = Exception.class。Spring默认只对RuntimeException回滚,如果你的业务中抛出的是自定义的受检异常,不写rollbackFor就不会回滚。这是事务使用的高频考点。
下单时还要处理“重复提交”问题。用户连续点了两次“提交订单”,如果没有控制,可能会出现两个相同内容的订单,用户还得手动取消一个。我建议做两层防护:前端按钮提交后置灰;后端用Redis的setNx对同一用户短时间内做幂等拦截,比如10秒内只允许提交一次。这个点写进论文,明显能提升项目完成度。
3.4 模拟支付与订单状态机
真正接入微信支付、支付宝在毕设里没有意义,因为需要真实的商户号、证书和回调域名,流程繁琐且容易卡住。我建议做“模拟支付”:用户点击“去支付”,后端生成一条模拟成功的支付记录,把订单状态从待支付改为已支付,同时记录支付时间。
如果确实想展示支付流程,网上公开的沙箱文档可以研究,但不要为了毕设去申请企业资质,这一步容易拖住整个项目进度。模拟支付已经足够支撑答辩中“支付流程怎么走”“资金怎么流转”“订单状态怎么变更”这类问题,完全可以自圆其说。
订单状态我定义成一个整数状态机:0待支付、1已支付、2制作中、3待取餐/待送达、4已完成、5已取消。每次状态变更都可以记录到订单日志表order_log,这种设计在答辩时可以解释成“可追踪、可审计”。状态机的好处是,所有状态迁移是明确的,不会出现从“已完成”跳回“待支付”这种不合理流转。
状态流转的接口定义如下:
- 用户端:提交订单(→待支付)、取消订单(待支付→已取消)、支付订单(待支付→已支付)、确认收货(待取餐→已完成);
- 管理端:接单(已支付→制作中)、出餐(制作中→待取餐/待送达)、完成(待取餐→已完成)。
所有状态更新SQL必须带条件,例如:
update orders set status = #{nextStatus} where id = #{orderId} and status = #{currentStatus}这种写法能防止并发场景下订单状态被覆盖成错误值。比如管理员和用户同时点操作时,因为条件里带上了原状态,后一个请求就无法生效。
3.5 管理后台与统计报表
管理后台的页面量不小,但最有亮点的通常是统计报表。统计两个指标就够了:
- 今日订单数、今日营收:select count(id), sum(total_amount) from orders where create_time >= today and status != 5;
- 菜品销量TOP10:select dish_name, sum(quantity) from order_item group by dish_name order by total_quantity desc limit 10。
统计接口返回给前端的数据格式要处理好。我建议后端直接返回已经格式化好的字符串,或者统一的时间戳,不要在SQL里用DATE_FORMAT再转换,避免前后端时区不同导致对不上。
这里也有一个实际坑:数据库字段create_time是DATETIME,Java实体里用LocalDateTime接收,直接返回JSON时会序列化成2025-01-05T12:30:00这种ISO格式,前端想显示成2025-01-05 12:30:00就得额外处理。解决方案是给实体字段加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss"),再配合全局Jackson配置,保证所有接口输出格式统一。
4. 联调、部署与常见坑位实录
4.1 前后端分离下的跨域与Token传递
如果你的毕设是Spring Boot后端 + Vue前端,一定会遇到跨域问题。前端页面运行在localhost:8888,后端接口在localhost:8080,浏览器认为这是两个域,会拦截请求。
处理方案有三种:
- 后端添加CORS配置,允许指定路径跨域;
- 前端开发环境用Vue CLI的proxy代理转发请求;
- 用Nginx在部署时统一反向代理。
我建议同时写第1种和第2种。后端CORS配置是一段WebMvcConfigurer配置,注意allowedOriginPatterns不要写成allowedOrigins("localhost:8888"),后者在Spring Boot 2.4以后对带端口的本地地址支持不太好。
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }Token传递相对简单但容易被忽略:前端每次请求要把token放进header里,后端拦截器从header里取。常见错误是前端把token放在请求参数里,导致后端拦截器读不到。联调时统一约定为Authorization: Bearer ,后端解析时去掉Bearer前缀即可。
4.2 打包与Docker部署
项目在本地能跑只是一个阶段,能打包部署到服务器上是另一个阶段。推荐用Docker部署Spring Boot项目,步骤固定,也适合写成论文的部署章节。
先在pom.xml的build里配置好finalName,比如fastfood.jar,然后执行:
mvn clean package -DskipTests验证jar包在target目录下之后,写一份Dockerfile:
FROM openjdk:8-jdk-alpine COPY target/fastfood.jar /app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app.jar"]构建镜像和启动容器:
docker build -t fastfood . docker run -d -p 8080:8080 --name fastfood-app fastfood这里有一个毕设党经常踩的坑:本地打包时如果测试类里写了需要连接数据库的用例,mvn package时test跑不过去,就会一直报错。解决办法是打包时加-DskipTests跳过测试,或者在写论文阶段补一个简单的@SpringBootTest启动测试,既能证明项目可以启动,又不影响打包过程。
如果导师要求提供“系统部署说明书”,上述三步加一个初始化数据库脚本就完全够写了。数据库初始化脚本里建议显式指定编码:
CREATE DATABASE IF NOT EXISTS fastfood DEFAULT CHARACTER SET utf8mb4;UTF-8在MySQL里其实是历史遗留问题,中文乱码基本都可以归因于没用utf8mb4。这个字符集是MySQL 5.5以后才完整的,MySQL 5.0时代的utf8并不支持全部emoji和生僻字。毕设里一句话写清楚字符集选择,能避免后面大量的乱码排查时间。
4.3 我遇到的四个典型启动/运行报错
第一个是端口占用。启动时报Web server failed to start. Port 8080 was already in use,这通常是你本地之前有一个没关掉的进程占了8080。排查命令在Windows可以用netstat -ano | findstr 8080,找到PID之后去任务管理器结束进程;Linux/macOS用lsof -i:8080。更省心的做法是配置文件里把端口改成可动态覆盖的,开发时用8001,部署时再改成8080。
第二个是MyBatis的Mapper绑定异常。启动时报Invalid bound statement (not found),绝大部分原因是XML文件没有打包进classes目录。检查target/classes/mapper下面有没有对应的XML,没有就清理重新编译,同时确认pom.xml里有资源过滤配置:
<resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources>第三个是Redis连接失败。启动时如果Redis没启动,报Unable to connect to Redis。根源很简单:缓存依赖Redis服务,但开发环境忘了启动。这时把本地Redis启动,或者临时把Redis相关配置注释掉即可。开发时不要把缓存过期时间设成0,0代表永不过期,改完代码后调试会痛苦很久。
第四个是MyBatis的XML里大于小于号转义问题。查询时间范围时如果SQL里直接写>=,XML解析会直接报错。正确写法是大于号转义为>,小于号转义为<,或者用 包住整段SQL。这个坑非常典型,我指导同学改代码时几乎每个月都会遇到一次。
5. 论文写作与答辩准备
5.1 论文框架与写法建议
毕设论文的常规骨架是:绪论、相关技术介绍、需求分析、系统设计、数据库设计、系统实现、系统测试、总结。快餐订餐系统的论文想写好,重点在需求分析和数据库设计这两章。
需求分析不要写成“本系统包括用户管理、菜品管理”这种流水账。要用文字描述业务场景,再用UML用例图把用户和管理员各自的用例说清楚。数据库设计要给出ER图和核心表结构说明,并把“为什么订单和订单明细要拆成两张表”解释清楚:因为一个订单里可能有多道菜,如果只保存在一行,菜品数量不固定,数据库表结构就无法稳定。
系统实现章节按模块写,每个模块先贴一个核心代码片段,再配一两个效果截图。贴代码时不要贴整个文件,只贴关键方法。答辩老师没有耐心读长代码,他们更在意代码逻辑的完整性与注释质量。论文中把核心接口调用关系画成时序图,用Word自带的画图工具就能完成,但能直观展示前后端交互流程。
5.2 答辩高概率问题与答题思路
围绕Spring Boot,答辩高频问题我整理几个给你:
问题一:为什么用Spring Boot? 答:Spring Boot是Spring生态的快速开发脚手架,它的自动装配机制让我们只用少量配置就能搭建独立运行的Web应用。本项目用它做整体基础框架,整合MyBatis与Redis,同时内置Tomcat,所以部署时只打一个jar包就行。
问题二:Spring Boot自动装配的原理是什么? 答:核心是@SpringBootApplication里的@EnableAutoConfiguration。Spring Boot通过spring.factories机制加载配置类,再通过@ConditionalOnClass等条件注解判断是否满足启用条件,从而自动创建对应的Bean。
问题三:为什么订单插入要加@Transactional? 答:因为下单涉及订单主表、订单明细、购物车三个数据变更,如果不加事务,任何一个步骤失败都会造成脏数据。加上事务之后,只要有一个环节抛出异常,整个流程就会回滚。
问题四:项目有什么不足? 答:目前还是单体单机部署,没有做分布式与集群,真正高并发场景需要引入消息队列和更完善的缓存策略。回答时要说清楚这些不是不知道,而是本次项目的定位不在这里。
问题五:如果老板让你把系统上线,你第一步做什么? 答:先确认吞吐量和并发量指标,再判断数据库和缓存是否需要扩容,同时确认日志与监控方案。这种开放题没有标准答案,但能体现你有没有真实落地的思维。
答辩前建议把项目完整跑一遍,手机端点单、后台出餐,整个主流程顺畅,比任何说辞都有效。多数被挂的毕设不是代码量少,而是演示时流程断了:购物车加不了菜、支付后状态不变、管理端列表空白,这些低级问题一旦出现,之前积累的好印象全归零。
写到这里,这个快餐订餐系统最核心的链路已经讲得差不多了。说一点我个人带毕设的真实体会:快餐订餐这个题目本身不算惊艳,但你可以靠完成度做出真正的亮点。把JWT鉴权、事务边界、Redis缓存、订单状态机这四个关键细节讲清楚,已经能超过一批只停留在“能跑”层面的毕业设计。
最后再分享一个我的习惯:项目从写第一行代码开始,就建一个自己的README文档,把启动步骤、数据库脚本、默认账号密码、常用命令都记在里面。DML和DDL文件用中文注释,方便自己和答辩老师阅读;答辩前按README五分钟内把环境拉起来。这个小习惯能让你在演示当天从容很多,也更像一个真正“设计与实现”过系统的人。