springboot餐饮管理系统这个题目,在毕业设计选题库里已经连续多年霸占热度前排。作为这几年陆陆续续帮不少学弟学妹审过代码、模拟过答辩的人,我每年三四月都会收到差不多的私信:这题到底好不好做?技术栈怎么选?代码跑通了但老师问起来不知道怎么讲怎么办?
这篇东西我不想写成那种“照着敲就能交差”的教程,而是想用带项目的视角,把这个题目从需求边界、数据库设计、后端实现、前端联调,一直讲到打包部署和答辩演示,完整拆一遍。你如果正准备拿这个题目当毕设,或者已经把代码跑通但总觉得心里没底,那这篇应该能帮你少走很多弯路。
1. 动工前先圈需求边界:餐饮管理系统到底要管什么
先说一个很多人踩过的坑:拿到“餐饮管理系统”这个题目后,第一反应是去CSDN、GitHub搜源码,看到别人的截图里有菜品管理、桌台管理、订单管理、会员管理、库存管理、报表统计,觉得“我也全做”,结果做了两个月,每个模块都只停留在增删改查,最后论文写得像一本系统操作手册。
1.1 “管什么”是先决问题:五条核心业务线
餐饮管理系统本质上是在解决一家餐厅日常经营里的信息流转问题。我习惯把需求拆成下面五条业务线:
| 业务线 | 典型场景 | 常见角色 | 毕设优先度 |
|---|---|---|---|
| 菜品与分类管理 | 餐厅老板上架新菜、调整价格、停售估清菜品 | 管理员 | 高 |
| 桌台与开台管理 | 顾客到店、安排空桌、更换桌台 | 前台/服务员 | 高 |
| 点餐与出餐 | 服务员记录顾客所点菜品,后厨按单制作并更新状态 | 服务员、后厨 | 高 |
| 收银与订单结算 | 订单金额汇总、优惠计算、结账完成 | 收银员/管理员 | 高 |
| 会员与营业统计 | 会员折扣、历史订单查询、日营收/菜品销量统计 | 管理员 | 中 |
至于库存管理,能做但要看你的时间和数据库功底,没有库存模块也不影响系统主流程成立,可以让“停售”状态来承担部分估清功能。会员营销这类需求,如果论文里没有对应的数据分析和商业模式推导,很容易做成“办了张会员卡,存了个手机号”的摆设。
1.2 主链路优先:先把“点餐到结算再到统计”的闭环跑通
我给自己带的人定过一条规矩:第一版系统不求功能多,只求一条主链路上没有断点。这条链路在我的项目里是这样的:
服务员或收银员登录进入工作台,看到桌台状态列表;选择一张空桌后进入点餐页面,从菜品列表中勾选菜品加入本次订单;确认下单后,后厨角色登录能看到新订单并按菜品状态开始制作;菜品出餐后更新订单状态;顾客用餐完毕,收银台发起结账,计算总金额;订单完成后,老板角色能够在统计页面看到今日营收、订单量、菜品销量排行。
这一套全部打通,系统作为“餐饮管理系统”的题就已经立住了。后续加会员、加库存、加Redis缓存,全都是锦上添花。
1.3 别用中间件堆工作量
我见过一些卷到离谱的毕设选题,什么springboot + redis + kafka + flowable + xxl-job全往一个点餐系统里塞。不是说这些技术不好,而是你要先回答一个问题:这个餐厅场景里真的有跨系统异步消息吗?真的需要一个工作流引擎来审批订单吗?
Flowable这种工作流引擎解决的是“审批流、任务流”这类强流程问题,餐饮点单更自然的模型是订单状态机——从下单到支付到制作到出餐,状态有限且流转方向清晰,用一张订单表和几个状态字段就能管理得很好。Kafka同理,如果只是为了让点餐后给后厨推一条消息,那更像是引入一个分布式问题再用中间件去解决。老师问一句“你的Kafka在其中起什么不可替代的作用”,很多人当场就卡住了。
毕设的技术选型有一个很现实的原则:复杂度必须配得上业务场景,同时配得上你能讲清楚的程度。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈到底怎么选:SpringBoot和它周围的配套们
既然题目里已经写明了springboot,那么后端主框架基本就是定死的。真正需要你动脑子的,是剩下那一圈的配套选型。
2.1 为什么这套组合最省心
我推荐给大多数人的组合是这样的:
- 后端:Spring Boot 2.7.18 + MyBatis-Plus 3.5.x + MySQL 8.0
- 缓存:Redis(主要存桌台状态和菜品分类,不做复杂缓存也算加分项)
- 鉴权:JWT + 拦截器(理由后面细说)
- 后端接口文档:Knife4j 或 Springdoc,按版本匹配选择
- 前端:Vue 2 + Element UI(如果你对Vue 3和Element Plus已经很熟,用3也行,但别在临近答辩时临时换)
- 前后端联调:前端开发服务器代理到本机8080
这套组合最大的优点不是“新”,而是稳。MyBatis-Plus把单表CRUD和分页查询简化了很多,你可以把精力集中在订单状态流转这种核心逻辑上;Element UI的表格、表单、弹窗组件开箱即用,很短时间内能把后台管理界面搭得像个正经系统。
2.2 版本选择:我用的是SpringBoot 2.7.18,不是3.x
这个点我必须单独拿出来说,因为热词里总有人在问“springboot版本太高”怎么办。
Spring Boot 3.x发布已经有段时间了,但它有几个实际的坎:强制JDK 17以上,javax命名空间迁移到jakarta,相关第三方组件的兼容版本要重新对齐。对于毕设来说,你查到的绝大多数博客、视频、踩坑文章都基于Spring Boot 2.x,直接上3.x意味着你要在环境配置上多花很多时间,而这些东西老师不会给你加分。
我自己在实际项目里选的是2.7.18。这个版本是2.x里的长期维护版本,既能用传统javax.servlet那一套,也兼容绝大多数MyBatis-Plus、JWT依赖,相关问题的解决方案在社区里一搜一大把。更重要的是,JDK 8 + Spring Boot 2.7 + MySQL 8这条组合链,在Docker部署时镜像资源多,出现问题也容易排查。
2.3 配置文件的组织方式
Spring Boot的application.yml是整个项目最常被拷问的配置文件。我的习惯是不把所有环境塞进一个文件里:
yaml复制# application.yml 主配置
spring:
profiles:
active: dev
然后拆成application-dev.yml(本地开发)和application-prod.yml(服务器部署或答辩演示)两个文件:
yaml复制# application-dev.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/restaurant?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
这里需要注意三个地方:MySQL 8要用com.mysql.cj.jdbc.Driver而不是老的com.mysql.jdbc.Driver;serverTimezone建议显式指定Asia/Shanghai,避免容器环境时区不一样导致时间查询差8小时;map-underscore-to-camel-case要开,否则数据库字段create_time映射不到实体属性createTime。
用spring.profiles.active切换环境的习惯,好处在部署阶段会体现出来:你不需要为了线上改一处数据库密码而手改代码,只需要启动时指定参数--spring.profiles.active=prod。这个细节很多同学不知道,但答辩老师会觉得你是有工程经验的。
3. 数据库设计:先想清楚表和状态,代码只是翻译一遍
我一直对学弟学妹说一句话:代码可以边写边改,但数据库表结构如果到了中期才大改,那基本等于推翻重来。餐饮管理系统这种业务模型相对固定的项目,表结构设计就是整个系统的地基。
3.1 核心实体关系先理清
餐饮系统里的核心实体,说到底逃不出这几类:
- 人和角色:用户表
sys_user,角色表sys_role,用户角色关联表 - 菜本身:菜品分类表
category,菜品表dish - 空间:桌台表
dining_table(注意别叫table,那是SQL保留字) - 交易:订单表
orders,订单明细表order_detail - 扩展:会员表
member,库存表stock,根据实际需求取舍
如果画实体关系图,最核心的主线是:一个菜品分类下挂多个菜品,一个桌台可以产生多笔订单,一笔订单包含多个订单明细,而订单明细和菜品之间是“引用关系但保留历史快照”。
3.2 菜品和桌台这两张基础表
菜品分类表有几个容易被忽略的字段,比如type用来区分是菜品分类还是套餐分类,sort用来控制前台展示顺序,status用来控制分类启停。菜品表的核心字段包括name、category_id(关联分类)、price、image、description、status(起售/停售)以及时间字段。
价格字段必须用decimal,不能用float或double,这个点后面专门讲。桌台表里除了桌号、座位数,核心是那个status字段:0空闲、1用餐中、2已结账待清理。很多同学点餐时没有校验桌台状态,结果同一张空桌被两个订单同时占用,这是很典型的低级逻辑漏洞。
3.3 订单表里的“快照”思想
订单主表orders字段比较多,但核心并不复杂。我在项目里通常这样建:
sql复制create table orders
(
id bigint primary key auto_increment,
order_number varchar(32) not null comment '订单号,业务上唯一',
table_id bigint not null comment '桌台id',
table_name varchar(32) null comment '桌台名称,冗余存储',
state tinyint not null default 0 comment '订单状态:0待支付,1制作中,2待上菜,3已完成,4已取消',
member_id bigint null comment '会员id,可为空',
member_name varchar(32) null comment '会员名,冗余快照',
total_amount decimal(10,2) not null comment '订单原价总额',
discount_amount decimal(10,2) not null default 0.00 comment '优惠金额',
payable_amount decimal(10,2) not null comment '应付金额',
payment_method tinyint null comment '支付方式:1现金,2扫码,3会员卡余额',
remark varchar(255) null comment '订单备注',
create_time datetime not null,
update_time datetime not null,
key idx_table_state (table_id, state),
key idx_create_time (create_time),
unique key uk_order_number (order_number)
) comment '订单表';
很多第一次做的人会问:为什么订单明细里已经有dish_id,还要把dish_name和price原样存一份?这就是“快照”思想。
菜品今天卖20块,明天调价到25块。如果订单明细只存一个dish_id,结账时再去菜品表查价格,那历史订单的金额就全乱了——顾客昨天实际付的是20,系统今天查出来却是25。所以订单明细表里的dish_name、price存的是下单那一刻的数据,菜品表后续怎么改,都不应该影响已经生成的订单。答辩时能被老师问到“你的历史订单数据会因为菜品改价而出错吗”,把快照思路讲出来,是很大的加分项。
3.4 订单状态流转:用状态机而不是脑内流程
订单状态不要用字符串'待支付'、'已支付'散着存,最好用tinyint加上代码里的枚举常量统一管理。我见过不少项目把状态字段写成state=0待支付,state=1已支付,state=2商家已接单,到后面代码里到处出现魔法数字,改一个状态要全局搜索替换。
订单状态我一般定义成:
0 待支付 → 1 制作中(支付完成或线下收银确认)→ 2 待上菜 / 已出餐 → 3 已完成(顾客用餐结束并结账)→ 4 已取消
如果订单在待支付阶段取消,直接从0走到4。后厨出餐更新为2后,服务员端确认上菜,再走到3。这里要注意,不要把“订单完成”和“支付完成”混为一谈,餐饮场景里顾客完全可能先吃后付,收银结账才是订单真正终结的节点。
对毕设来说,有这样一个清晰的枚举流转,不光代码好写,答辩画流程图时也很有条理。如果你希望更严谨一点,可以加一张订单状态变更日志表,记录每一次状态从什么值变成什么值、操作人是谁。这是一个成本很低但很容易让老师眼前一亮的扩展点。
3.5 金额计算:为什么不能存double
接着说那个所有餐饮系统都必须踩到的问题:金额到底用什么类型。
如果你用double存价格,0.1 + 0.2会得到0.30000000000000004,这在菜品单价和数量做乘法、再叠加折扣的时候很容易出现分位误差。哪怕最后显示时四舍五入了,日积月累的对账差异也会让你头疼。
Java后端里正确的做法是全程使用BigDecimal,构造函数直接传字符串,或者用BigDecimal.valueOf(...),尽量避免用new BigDecimal(double)。举个例子:
java复制// 计算订单金额时
BigDecimal itemTotal = dish.getPrice()
.multiply(BigDecimal.valueOf(quantity));
// 合计
BigDecimal total = total.add(itemTotal);
// 保留两位小数,四舍五入
BigDecimal payable = total.subtract(discount)
.setScale(2, RoundingMode.HALF_UP);
数据库字段就用decimal(10,2),Java实体用BigDecimal,MyBatis-Plus默认也能正确处理这种映射。这一套在答辩时是标准的“专业素养”考点,几乎必问。
4. 后端实现:按“能用、能扩展、能答辩”的标准分层
表结构定好以后,后端代码的组织方式决定了后面几周你改功能时是游刃有余还是拆东墙补西墙。我不建议把业务逻辑全堆在Controller里,哪怕你只有十几个接口,也最好按这个分层的习惯来:
Controller负责接收参数和返回结果,Service负责业务规则和事务,Mapper负责数据库操作,Entity对应数据库表,DTO/VO负责接口入参和出参,Common放统一返回体和异常处理,Config放配置类。这套结构不是什么高深架构,但它能让你在后端“加一张表、加一组接口”时像流水线一样顺畅。
4.1 统一返回体和全局异常
前后端联调最忌讳的就是每个接口返回格式都不一样:有的返回{code:200,data:...},有的直接返回一个数组,报错时又变成一段普通字符串。前端为了兼容这些格式不得不写一堆判断,时间都耗在这种地方了。
我的做法是所有接口统一返回Result<T>,结构很简单:
java复制@Data
public class Result<T> {
private Integer code;
private String msg;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMsg("success");
result.setData(data);
return result;
}
public static <T> Result<T> error(String msg) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMsg(msg);
return result;
}
}
配合全局异常处理,Service层抛业务异常时,Controller不需要每个方法都写try-catch。用@RestControllerAdvice加@ExceptionHandler统一捕获,前端拿到的永远是结构一致的错误响应。代码里出现Result.success(...)的次数很多,但接口却非常清爽。
4.2 权限:用JWT加拦截器,反而比Security更适合本科毕设
餐饮管理系统必然要区分角色:管理员能管理菜品、桌台、查看统计;服务员能操作开台、点餐、结账;后厨能看到订单但不能改价格;收银员能处理收银。
权限这块,常被推荐的是Spring Security,但我个人觉得对于本科毕设,Spring Security + JWT的学习曲线偏陡,过滤器链、认证管理器、UserDetailsService这些概念消化起来需要不少时间。如果你不是对Security很熟,一个更可控的方案是JWT加自定义拦截器:
登录接口校验用户名密码成功后,生成一个JWT字符串(里面存用户id和角色标识)返回给前端。前端后续每个请求都在Header里带上Authorization: token。后端写一个拦截器,排除登录、接口文档、静态资源等白名单后,对每个请求做两件事:
- 解析并校验Token是否合法、是否过期;
- 把当前用户id放到
ThreadLocal里,方便后续业务逻辑获取操作人。
如果需要角色控制,可以在点餐、结账等接口上加一个自定义注解,比如@RequireRole("admin"),拦截器解析出用户角色后做比对。这个方案的好处是每个环节的Filter和Interceptor都是你自己写的,老师问起来你能清清楚楚讲出认证流程,比贴一个配置类然后说不清Security过滤器链要实在得多。
使用JWT拦截器时,接口文档的放行是个高频坑。你配了拦截器后发现http://localhost:8080/doc.html打不开,或者Knife4j页面能看到但发请求时返回401,多半就是没有放行文档相关路径。需要加入白名单的常见前缀包括/doc.html、/webjars/**、/swagger-resources/**、/v3/api-docs/**或/v2/api-docs/**,具体看你的文档组件版本,但思路是一致的。
4.3 点餐核心接口这样设计
一个点餐接口的请求路径可以设计成POST /api/order,入参大概是桌台id、菜品列表(每个菜品包含dishId和quantity)、备注。Service层的处理逻辑大致是这样的:
- 校验桌台状态必须为“空闲”;
- 遍历菜品列表,查菜品当前是否在售,如果某个菜品已下架,直接返回“土豆烧鸡已停售,请重新选择”;
- 用BigDecimal逐项计算金额,汇总订单总额;
- 生成唯一的订单号;
- 插入订单主表,批量插入订单明细表;
- 更新桌台状态为“用餐中”;
- 返回订单id和应付金额。
这一步要特别注意事务。如果插入订单主表成功了,但批量插入明细时出了问题,整个点餐操作必须回滚,否则会出现一笔没有菜品的“幽灵订单”。方法上加@Transactional(rollbackFor = Exception.class),并且在写代码时不要在事务方法里捕获异常后吞掉,这样Spring才能在抛出RuntimeException时触发回滚。
另一个常被问到的是并发问题:两个服务员几乎同时给同一张空桌点餐怎么办?简单的项目里可以通过数据库状态更新条件来实现:
java复制int updated = diningTableMapper.updateState
("空闲", "用餐中", tableId);
if (updated == 0) {
throw new BusinessException("当前桌台已被占用,请刷新后重试");
}
核心思路是只有一个线程能成功把“空闲”改成“用餐中”,另一个线程更新受影响行数为0,证明桌台被抢占了。这个方案不需要分布式锁,只靠一条数据库原子更新就能挡住大部分并发问题,是性价比很高的实现。答辩时如果老师顺着问“高并发下会不会超卖”,你就把这段逻辑讲出来,比他预期中的标准答案好很多。
4.4 避免循环依赖与代码组织纪律
SpringBoot项目启动失败的经典原因之一是循环依赖。比如OrderService要调用TableService更新桌台状态,而TableService里的某个逻辑又反过来调OrderService,两个Bean互相注入,Spring容器启动时可能直接报错。
这个问题在Spring Boot 2.6以后默认禁止循环依赖,表现是启动日志里出现一段“Requested bean is currently in creation”之类的提示。解决方法不是开spring.main.allow-circular-references=true去绕过,而是老老实实重构:把公共逻辑抽到第三个Service里,或者调整调用方向,让依赖变直链。毕设项目代码量不大,出现循环依赖一定是设计上分了不该分的层,提前留个心眼能省很多启动排查时间。
4.5 简单报表统计怎么写
最后一个后端模块是统计。你不需要引入复杂报表引擎,只要会用SQL的聚合函数就够了。今日营收可以按create_time和订单状态过滤,把已完成的订单金额相加;近一周走势可以用GROUP BY DATE(create_time)按天分组;菜品销量排行则是从订单明细表按dish_id分组,SUM(quantity)后按总份数倒序。
需要提醒的是,统计查询的字段一定要建好索引,否则订单数据量稍微大一点,直接在页面上查某一天的数据都会让MySQL全表扫描。订单量到几万条时不至于卡到不能用,但能建索引的地方建上索引,要么用order_number,要么用create_time,也算是一种良好的表设计习惯。
5. 前端和接口联调:把后台从“能用”变成“看得过去”
后端的核心链路写好后,系统其实已经能跑通了,但毕业设计这种场景,界面观感直接决定了老师和评分同学的第一印象。你可以不追求多炫酷,但整体布局、颜色、操作流至少要像个正经后台。
5.1 前端项目结构别整太复杂
我说的是Vue 2 + Element UI的组合,项目的标准结构差不多是:views目录存放页面组件,router目录配置路由,api目录按业务模块封装axios请求,utils放请求工具类,store可以放用户登录状态。
页面不需要很多,能把主流程覆盖住就够了:
- 登录页
- 系统主框架(左侧菜单+顶栏+内容区)
- 菜品分类页面
- 菜品管理页面
- 桌台管理页面
- 订单管理页面(服务员点餐/收银入口)
- 后厨工作台
- 经营统计页面
- 系统用户管理
Element UI最常用的几个组件是el-table、el-dialog、el-form、el-select、el-upload,你能把这几个组件用熟,后台90%的界面都能搭出来。菜品图片上传如果不想搞复杂的对象存储,直接上传到后端项目的本地静态目录,再配置一个资源映射路径就能访问,毕设完全够用。
5.2 接口联调顺序
前后端联调最大的痛苦是“页面写了一堆,接口一个个试,不知道错误到底在哪”。我的建议是按照下面顺序来,每一环通了再进入下一环:
- 登录接口:能成功拿到token,并且把用户信息保存到前端store;
- 获取当前登录用户信息接口:验证Token拦截器是否放行;
- 菜品分类列表和菜品列表:确认后端数据能正常渲染到表格;
- 桌台列表:确认按钮点击能切换抽屉或弹出点餐面板;
- 提交点餐订单:打开浏览器Network看请求体里的菜品列表格式是否和后端DTO一致;
- 后厨订单列表:确认同一份订单数据在两个角色下表现是否正确;
- 收银结账:从点击结账到后端更新订单状态返回,整个链表上的功能全部验证一遍。
这个顺序最大的好处是:每一步的失败都可以缩小到某一个模块,不会出现“页面白屏但不知道是路由问题、接口问题还是权限问题”这种无法定位的情况。
5.3 跨域、Token这些联调痛点
如果前端开发服务器跑在8081,后端跑在8080,前端直接请求http://localhost:8080/api/...就会遇到跨域问题。最简单的处理方式是走开发代理,在vue.config.js里加一段:
js复制const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
devServer: {
port: 8081,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
// 如果后端Controller的RequestMapping前缀没有/api,需要加pathRewrite
}
}
}
})
这样前端请求/api/login会由开发服务器转发到后端8080,绕过跨域。等部署上线时,前端打包后的静态文件可以交给Nginx托管,Nginx再配置一条location /api反向代理到后端服务。这套方案的关键点在于接口前缀的约定要统一,我习惯后端所有的Controller类上都加一个@RequestMapping("/api/xxx"),前端请求就全部以/api开头,代理配置会清爽很多。
Token这块,前端用axios请求拦截器统一添加Header:
js复制service.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = token
}
return config
})
响应拦截器里判断后端返回的code,如果是401就跳转登录页。注意这里Token不要放在响应体里的data.token而Header里没带,这种前后端约定不一致是联调时最常见的低级错误。
5.4 UI上加分的小细节
一个餐饮后台要看起来像样,其实有几个很便宜的技巧:
桌台管理页面不要只做一个普通表格,把每张桌显示成卡片或色块,空闲用绿色,用餐中用红色,待清理用橙色,视觉上直观很多。菜品状态可以在表格里用el-tag展示“起售/停售”,停售的菜品在点餐页要置灰不可选。订单详情显示为抽屉而不是弹窗,信息层级比弹窗清晰。统计页面用ECharts画近七天的营收折线图和菜品销量条形图,这些图引入成本很低,但答辩时投影在大屏上的效果比一堆数字表格好太多。
6. 打包部署与答辩演示:这一步决定了你的一学期值多少分
很多项目写到最后是“本地能跑,演示靠运气”。代码写得再完整,如果答辩当天连不上数据库、前端白屏、或者忘记演示账号,前面半年功夫可能直接被印象分拖累。
6.1 Maven打包和本地启动检查
后端打包前要确认几件事:
第一,application-prod.yml里的数据库地址、账号、密码正确,且启动时指定的profile是prod。很多人本地开发用的是dev配置,打包后直接java -jar运行,结果连的还是本机数据库,到了另一台电脑自然启动失败。
第二,如果打包时不想跑单元测试,用mvn clean package -DskipTests,避免测试类里连不上测试库导致打包中断。
第三,确认Jar包能通过java -jar独立启动,而不是只能在IDEA里右键运行。启动后访问http://localhost:8080/doc.html,看到接口文档页面能正常刷新,才算后端部署就绪。
有条件的话,在资源目录下放一个banner.txt,可以用在线banner生成器把自己的姓名缩写或者项目名做成ASCII字符画。SpringBoot启动时会打印出来,这个小彩蛋很能让答辩氛围稍微轻松一点,也能在细枝末节上体现项目的个性化。
6.2 Docker Compose部署
如果你租了一台服务器,或者想在演示机上一键拉起环境,用Docker Compose会省很多事情。一个最基本的三件套编排大概长这样:MySQL容器、Redis容器、应用容器。
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: restaurant-mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: restaurant
