ARTICLE DETAIL

资讯详情

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

Spring Boot点餐系统开发全解析:从数据库设计到并发扣库存

Spring Boot点餐系统开发全解析:从数据库设计到并发扣库存 做点餐系统这种项目前前后后踩了不少坑。从最初一版JSP页面堆Servlet到后面重构为Spring Boot前后端分离基本把快餐店日常点餐、出餐、库存扣减那套完整流程都走了一遍。这里把整个m080快餐店点餐服务系统从需求拆解、数据库设计、核心代码到线上问题排查做个完整复盘包括那些课程设计和毕业论文里不会写清楚的技术细节。如果你正在做类似的毕设或者想快速搭一个能用的点餐系统这篇文章应该能帮你省下不少时间。这个项目要解决的核心问题表面上看是顾客能在网上点餐下单但实际操作中你会发现真正麻烦的是订单状态流转、菜单上下架、库存并发扣减、不同身份角色的权限控制这一连串纠缠在一起的事情。系统面向三类用户顾客负责浏览菜单、加购下单后厨员工负责查看订单并更新出餐状态管理员负责菜品管理、订单审核和经营数据统计。角色清晰之后后面的表结构、接口设计才不至于乱掉。1. 项目整体设计与思路拆解1.1 核心需求解析这份系统到底要做什么先看需求侧。快餐店点餐系统与外卖平台的区别在于它既要支持顾客到店扫码点餐也要支持管理员在后厨端、收银端操作。所以功能上天然分成两大块——面向顾客的前台点餐功能和面向门店运营的后台管理功能。前台点餐功能包含菜单分类浏览、菜品详情展示、购物车管理、订单提交、模拟支付、历史订单查看。后台管理功能包含菜品增删改查、菜品上下架、分类管理、订单状态处理、公告管理、销售统计。这些功能听上去简单但每一条拆出来都有细节。比如菜品上下架不是简单改个状态位还要考虑已经加入购物车的商品在下架后怎么提示用户订单状态处理要考虑从待支付、已支付、制作中、已完成到已取消这五态之间的合法流转路径。从技术实现角度看这类系统最合适的模式就是经典的三层架构控制层负责接收请求和参数校验业务层负责核心逻辑和事务控制数据访问层负责与数据库交互。这样做的好处是每一层的职责单一后续做单元测试、功能扩展都会轻松很多。即便你的项目规模不大也不建议把SQL直接写在Servlet或者Controller里否则一旦需求变了就是牵一发动全身。1.2 技术选型为什么这么定这套系统我最终选择的技术方案是层级技术选型选型理由前端页面HTML CSS JavaScript原生或Vue项目核心是业务逻辑前端不需要过度复杂化后端框架Spring Boot 2.x自动配置减少大量样板代码内置Tomcat部署方便持久层MyBatis MySQL 8.0SQL可控性强复杂表关联查询写起来直观权限控制拦截器 Session快餐店场景角色少基于Session的权限模型足够可靠构建工具Maven依赖管理和多模块构建都顺手可能有朋友会问为什么不上前后端完全分离、再配个Redis做缓存、用JWT做无状态认证原因很简单——这个项目的核心是订单和菜品数据的一致性不是高并发。快餐店的日均单量通常在几百到一千单这个量级一台普通服务器完全扛得住。技术栈越复杂出问题的环节就越多尤其是对于课设、毕设这类有时间限制的项目稳定跑通比炫技重要得多。1.3 整体架构与运行流程系统运行流程用一句话概括就是顾客选菜下单系统校验库存和菜品状态生成订单后厨接单做菜完成出餐管理员在后台维护所有基础数据。模块之间的依赖关系要理清楚。菜品模块是基础它不依赖订单模块购物车模块依赖菜品模块的库存和价格信息订单模块依赖购物车模块和菜品模块统计模块依赖订单模块。这种单向依赖关系在编码时要严格遵守尽量避免循环依赖。实际开发中我还会把金额计算单独抽一个工具类统一处理小数位和精度问题防止不同页面算出来的价格不一致。2. 数据库设计每一张表怎么规划2.1 六大核心表的结构与字段定义数据库是整个系统的地基。我设计的是六大核心表用户表、菜品分类表、菜品表、购物车表、订单表、订单明细表。部分系统还会加公告表、评价表但核心业务六张表就能跑通。用户表和一般的用户表差别不大核心字段是用户名、密码、手机号、角色标识。密码一定要加密存储这里用的是BCrypt加密而不是MD5因为MD5加不加盐都容易被彩虹表秒破。菜品分类表和菜品表是典型的父子关系。分类表字段简单分类ID、分类名称、排序值。菜品表就要复杂一些关键字段包括菜品名称、所属分类、价格、原价、主图URL、描述、月售数量、库存数量、状态标识。这里有两个隐藏细节一个是价格字段用DECIMAL(10,2)而不是FLOAT或DOUBLE因为浮点数在金额计算上存在精度丢失问题另一个是状态字段用tinyint类型0表示下架1表示上架后续通过状态位决定前端菜单是否展示。CREATE TABLE dish ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, name VARCHAR(50) NOT NULL, price DECIMAL(10,2) NOT NULL, original_price DECIMAL(10,2) DEFAULT NULL, image VARCHAR(255) DEFAULT NULL, description VARCHAR(500) DEFAULT NULL, stock INT NOT NULL DEFAULT 0, monthly_sales INT NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_category (category_id), INDEX idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;订单表是整个系统中最重要的表。字段包括订单编号、用户ID、订单总金额、支付金额、支付方式、订单状态、收货或者取餐信息、下单时间、支付时间。订单编号不要用自增ID直接暴露给用户我采用yyyyMMddHHmmss 四位随机数的生成方式既便于人眼识别也避免订单号被遍历抓取。订单明细表记录的是订单快照。这里要说一个很多新手容易忽略的点订单明细里不但要存菜品ID还必须冗余存一份菜品名称和下单时的单价。为什么因为菜品表和分类表的价格、名称是可能被管理员修改的如果不做快照用户半年后查看历史订单时看到的是改价之后的菜品名称和金额这就会引发售后纠纷。2.2 表关系与数据流向设计六张表之间的关系并不复杂用户和订单是一对多订单和订单明细是一对多菜品分类和菜品是一对多用户和购物车是一对多购物车和菜品是多对一。购物车表的设计比较有意思。购物车本质上是一个临时存储区域它的字段为购物车ID、用户ID、菜品ID、数量、加入时间。设计上要加一个唯一约束(user_id, dish_id)防止同一用户把同一道菜重复加进购物车。实际编码时如果用户再次点击加入购物车系统先去查这个唯一组合的记录如果存在就把数量加一不存在才插入一条新记录。数据流向要特别注意转账逻辑购物车表的记录在订单创建成功后必须删除不能存留。否则用户下次访问购物车时会看到已经下单的商品还在里面这是一类非常典型的线上Bug。我最初实现时忘了做这一步测试阶段就收到了我明明已经下单了购物车商品还在的反馈。2.3 快餐场景下的几个表设计细节快餐店的业务与正餐店有一个明显区别高峰期时间短、订单密度大。这意味着数据库的写入操作是突发性的。所以在设计时要注意两点。第一订单状态更新不要频繁使用UPDATE。比如后厨接单、出餐这两个操作频繁更新订单主表会造成行锁竞争。我的做法是把订单主表的状态作为主要状态同时增加一张订单状态流转日志表只做INSERT不做UPDATE。管理员看到的就是一条完整的状态时间线这比单纯改字段值可靠得多。第二菜品月售数量不要实时更新。如果每下一单都去UPDATE菜品表的monthly_sales字段高峰期会拖慢性能。更合理的方式是下单时不更新而是通过查询订单明细表在当天24点前做一次聚合统计再批量更新菜品表。这个方案我实测下来对数据库的压力会小很多。3. 功能模块设计从点餐到出餐的完整链路3.1 菜单浏览与菜品检索模块顾客进入系统后看到的第一个界面就是菜单页。菜单页按照分类展示菜品左侧是分类导航右侧是菜品卡片列表。这里面的关键性能点是菜品图片不能直接放原图一定要在服务端或前端做缩略图处理。不然后厨端加载菜单时会非常卡。实际上很多快餐店的网络环境并不好一张原图两三百KB一个分类十道菜就是两三MB网页加载速度就会明显变慢。菜品检索做的是关键词模糊搜索和分类筛选的组合查询。MyBatis中对应的SQL用动态标签拼接条件select idlistDishes resultTypeMap SELECT d.id, d.name, d.price, d.image, d.monthly_sales, d.description, c.name AS category_name FROM dish d LEFT JOIN category c ON d.category_id c.id where if testkeyword ! null and keyword ! AND d.name LIKE CONCAT(%, #{keyword}, %) /if if testcategoryId ! null AND d.category_id #{categoryId} /if AND d.status 1 /where ORDER BY d.monthly_sales DESC, d.id ASC /select这里要注意的是菜品状态筛选条件我直接写在查询里确保下架菜品不会出现在任何菜单页面。有朋友可能觉得在前端隐藏就够了但后端查询不拦截的话用户完全可以构造请求拿到下架菜品数据这是安全隐患。3.2 购物车模块会话存储 vs 数据库存储购物车有两种实现方式一种把购物车数据存Session另一种存数据库。两种方案我都试过说说各自的适用场景。Session方式的优点是访问速度快、实现简单缺点是用户换设备、清缓存后购物车就没了。数据库方式的优点是用户在不同设备上能共享购物车但每次增删改查都要访问数据库高峰期的请求量会成倍增加。这套系统针对的是到店点餐场景大多数顾客用手机扫码访问Session天然适合这种场景。但为了演示效果和课设答辩时能展示数据持久化这个能力我最终采用了数据库存储。实现时在购物车添加接口里用一个事务先查唯一记录存在则更新数量不存在则插入。数量校验放在Service层完成必须判断当前库存是否充足。3.3 订单提交与扣库存的并发处理订单提交是整个系统最核心的一环也是并发问题最集中的地方。常规流程是前端提交购物车中的菜品ID和数量列表后端接收后先校验菜品状态和库存计算订单总额生成订单主表和明细表扣减库存最后清空购物车。这里的并发坑很经典两个用户同时购买最后一份菜品服务端同时查到库存为1同时通过校验都去扣减库存结果就把库存扣成负数了。要解决这个问题最简单的办法是在扣库存SQL中加上库存条件判断UPDATE dish SET stock stock - #{quantity}, monthly_sales monthly_sales #{quantity} WHERE id #{dishId} AND stock #{quantity}这条UPDATE语句利用数据库的行锁机制保证原子性库存不足时影响行数为0代码根据这个结果回滚事务并提示手慢了菜品已售罄。这一行SQL加与不加就是系统在大促高峰稳不稳定之间的差别。对于订单量在千级别的小型系统来说这种方式完全够用不需要引入分布式锁或者消息队列。下单接口的完整时序大致为接收请求、校验用户登录、查询菜品列表、校验库存和上下架状态、计算总金额、生成订单号和订单记录、插入明细、逐条扣减库存、删除对应购物车记录、返回订单ID。这整块逻辑必须加在同一事务中任何一步失败都要回滚保证数据一致性。3.4 后台管理模块菜品、订单与统计后台管理模块按照角色权限分为两个层级普通员工可管理订单状态管理员可以操作菜品和查看统计报表。菜品管理页面菜单项包括菜品列表、新增菜品、编辑菜品、上下架操作。上下架操作不需要真正删除数据库记录只是将status字段在0和1之间切换。真正删除菜品时要注意处理订单明细表中的外键关联。有些系统用了物理删除导致历史订单明细里的菜品名称无法显示这就是快照字段没有设计好造成的。而我在订单明细表中冗余存储菜品名称所以外键缺失也不会影响历史数据展示。在后台我实现了完整的CRUD案例。新增菜品时会限制必填项菜品名、分类、价格、库存其中库存为0时不允许上架。这个限制逻辑要放在Service层校验不能只靠前端表单。接口层也要加参数校验防止非法请求绕过页面直接调用接口。订单管理页面核心是状态流转操作。我在订单状态设计上定义了五种状态待支付、已支付、制作中、已完成、已取消。合法的状态流转路径是待支付可以取消待支付支付后变为已支付已支付后厨接单变为制作中制作中出餐后变为已完成。为了防止非法跳转后台在每次状态更新时都校验当前状态是否合法这比前端按钮的disabled属性靠谱得多。统计报表模块做了一个简单的数据面板展示今日订单数、今日营业额、热门菜品TopN。营业额统计的SQL是对已完成订单的金额求和注意只统计已支付或已完成状态不能把待支付和已取消的订单也算进去否则数据就会虚高。3.5 支付模块怎么设计才能既演示又安全真实快餐店的支付会对接微信、支付宝的扫码支付API但这类接口通常需要商户资质个人开发和课程演示根本申请不下来。这里我做的是模拟支付前端点击去支付后系统直接调起一个模拟收银台页面展示应付金额和支付按钮点击确认后后端把订单状态从待支付更新为已支付。模拟支付流程中隐藏了一个容易忽视的细节前端在支付成功后需要反复确认订单状态是否被更新。因为实际网络中请求可能超时或重试前端如果只要点击按钮就跳转页面可能出现后端状态其实没改过来的情况。正确的做法是点击支付按钮后页面轮询查询订单状态的接口直至状态变成已支付再跳转。这个方法在不引入WebSocket的前提下是一种最简单也是最稳的方案。4. 核心代码实现与关键配置4.1 项目目录结构与分层实践代码组织上我是按Maven标准目录来建的。后端Java代码放在com.example.fastfood包下再按照controller、service、mapper、entity、common五个子包区分职责。这里的common包放的是统一返回结果封装、全局异常处理、日期工具类等公共组件。统一返回结果的封装是很多课程项目里容易忽略但实际开发中特别重要的一块。我封装了一个Result类包含code、message、data三个字段。所有Controller接口都返回Result对象而不是直接返回一个裸数据。这样前端无论拿到成功还是失败的结果解析逻辑都是统一的。全局异常处理用RestControllerAdvice注解实现。业务异常抛出BusinessException统一在这里拦截并返回错误消息给前端。这样Service层不需要每个方法都搞try-catch处理异常代码会干净很多。4.2 下单核心接口的代码拆解下单接口是理解整套系统的钥匙。我在这里给出核心的Service实现逻辑代码不算长但每一行都有存在的原因Transactional(rollbackFor Exception.class) public Long createOrder(OrderCreateDTO dto, Long userId) { // 1. 参数校验 if (dto.getItems() null || dto.getItems().isEmpty()) { throw new BusinessException(下单商品不能为空); } // 2. 查询购物车或直接传菜品列表这里以明细列表为准 ListOrderItemDTO items dto.getItems(); // 3. 收集菜品ID一次性查出所有菜品避免循环查询数据库 ListLong dishIds items.stream().map(OrderItemDTO::getDishId).toList(); ListDish dishes dishMapper.selectBatchByIds(dishIds); MapLong, Dish dishMap dishes.stream() .collect(Collectors.toMap(Dish::getId, Function.identity())); // 4. 组合校验及金额计算 BigDecimal totalAmount BigDecimal.ZERO; for (OrderItemDTO item : items) { Dish dish dishMap.get(item.getDishId()); if (dish null || dish.getStatus() ! 1) { throw new BusinessException(部分菜品已下架请刷新菜单); } if (dish.getStock() item.getQuantity()) { throw new BusinessException(菜品[ dish.getName() ]库存不足); } totalAmount totalAmount.add( dish.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())) ); } // 5. 生成订单号 String orderNo generateOrderNo(); // 6. 插入订单主表 Order order new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setTotalAmount(totalAmount); order.setStatus(OrderStatus.UNPAID.getCode()); orderMapper.insert(order); // 7. 插入订单明细 for (OrderItemDTO item : items) { Dish dish dishMap.get(item.getDishId()); OrderDetail detail new OrderDetail(); detail.setOrderId(order.getId()); detail.setDishId(dish.getId()); detail.setDishName(dish.getName()); detail.setDishPrice(dish.getPrice()); detail.setQuantity(item.getQuantity()); orderDetailMapper.insert(detail); } // 8. 扣减库存逐条执行带条件更新的UPDATE for (OrderItemDTO item : items) { int rows dishMapper.stockDeduct(item.getDishId(), item.getQuantity()); if (rows 0) { throw new BusinessException(菜品库存不足订单已回滚); } } // 9. 清空购物车如果是从购物车结算 cartMapper.deleteByUserIdAndDishIds(userId, dishIds); return order.getId(); }这段代码里有几个值得说的点。校验库存和计算金额必须在事务内完成否则校验和扣减之间如果出现其他事务穿插就会出现超卖。用selectBatchByIds一次性查出所有菜品避免了N次查询数据库的性能浪费。订单状态初始为待支付这样用户在支付前取消订单时只需要一条UPDATE语句把状态改成已取消不会产生脏数据。4.3 菜品上下架与库存同步的细颗粒度处理菜品上下架功能看似简单但对用户体验的影响很大。比如某个菜品已经下架了但是用户已经加购成功并且正在结算页面此时提交订单一定要做好后端校验。这就是为什么我在下单接口里特别检查了dish.getStatus() ! 1这个字段就是用来拦截加购时还在、下单时已下架这种情况的。前端在菜品列表展示时我会判断库存是否小于等于零或者状态是否为下架在这些情况下商品卡片上显示已售罄或已下架标签并且加入购物车按钮置灰。要注意的是这个交互不能替代后端校验因为前端页面数据是静态渲染的用户停留页面的过程中后端状态可能已经变化。另外菜品库存的批量同步在生产环境中也要做。比如后厨完成了10份汉堡的制作管理员在后台录入增加10库存系统执行的是UPDATE操作。为了方便追踪我在菜品变更记录表里记录了操作人、操作时间、变更前数量、变更后数量和变更原因。虽然这个表看似多余但一旦账目对不上它就是复盘的关键依据。4.4 登录拦截与权限控制的落地细节系统的登录状态通过Session保存后端用拦截器对请求做登录校验。拦截器注册时需要注意放行哪些路径用户登录接口、菜单浏览接口、菜品详情接口都要放行购物车、订单提交、后台管理接口都需要登录。角色权限也要区分普通用户访问/admin开头的路径时要返回无权限提示。拦截器判断逻辑不复杂但有一个细节容易出问题跨重定向的Session丢失。如果前端页面和后端服务不在同一个域名下前端默认不携带CookieSession就无法识别。实际处理中都要给前端发请求时设置withCredentials: true同时后端配置CORS允许携带凭证。为了防止测试接口时反复输入账号密码我在Dev环境加了一个自动登录的开关配置。用Spring的Profile注解限制只有dev环境生效。这样联调阶段省了不少事部署到生产环境时这个配置自动失效不必担心安全问题。5. 常见问题与排查技巧实录5.1 典型报错购物车数量加了但库存没扣这个问题我在初期经常遇到直接表现是下单成功订单数据也生成了但菜品库存显示还是原来那个数。排查路径非常固定——先看日志里有没有异常再看扣库存的SQL影响行数最后检查事务是否真的提交了。最终定位到的原因通常是方法上没有加Transactional注解或者事务方法被同类内部调用导致事务失效。Spring的事务是基于代理实现的如果同类里一个方法直接调另一个方法事务不会生效。我之前在原有代码中调用新增代码时漏加了事务注解就会出现这种问题。排查事务失效问题就多看日志如果日志里出现Rolling back且不是业务异常那就是事务配置有问题。5.2 MySQL数据库连接数被打满的排查在模拟压力测试阶段系统在高并发下执行了点餐操作然后出现数据库连接超时的报错。排查时先看了MySQL的max_connections配置再到后端日志里查找连接池相关的异常。最终发现是因为每次数据库操作我都手动获取连接但没有在finally块中释放导致连接泄漏。解决方法是统一使用MyBatis或Spring管理的事务模板获取和释放连接不要自己写JDBC连接逻辑。同时要给连接池配置合理的最大连接数和等待超时时间。HikariCP是Spring Boot 2.x默认的数据源连接池配置相对完善。我调整了maximum-pool-size为20minimum-idle为5connection-timeout为5000ms系统恢复正常。对于小项目来说连接池参数不是越大越好一旦超过数据库实际负载能力反而会拖慢响应。5.3 订单金额出现精度差的问题金额精度问题在初版用double存储时就遇到了连续下了几单后后台统计的营业额总和总是差那么几分钱。原因就是浮点类型的二进制存储方式无法精确表达所有十进制小数累加时会累积误差。解决方案有两层数据库层面金额字段全部改为DECIMAL(10,2)Java层面所有金额计算的变量类型统一用BigDecimal禁止用double和float参与金额计算。同时BigDecimal的构造方法用字符串构造比如new BigDecimal(19.90)不要用new BigDecimal(19.90)后者仍然有精度问题。5.4 图片上传后页面显示不出来的处理菜品图片上传功能本来很简单但第一次部署到服务器后前端怎么都加载不出图片。排查后发现原因上传成功后的图片存放在服务器的本机磁盘路径下而前端通过URL访问时走的是Tomcat的静态资源映射默认只能访问classpath下的静态资源。常规做法是配置一个虚拟路径映射把磁盘上的上传目录映射为URL可访问路径。Spring Boot里的写法是在配置类中实现WebMvcConfigurer重写addResourceHandlers方法Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String uploadPath file: System.getProperty(user.dir) /upload/; registry.addResourceHandler(/upload/**).addResourceLocations(uploadPath); }注意这个配置在生产环境里要换成线上服务器的绝对路径不要用相对路径否则jar包启动或者服务目录变化时图片路径会失效。5.5 订单状态不一致问题的定位思路订单状态不一致的典型案例是用户支付成功数据库订单还是待支付或者后厨已完成出餐但用户端一直显示制作中。遇到这种问题先别急着改代码第一件事是去订单状态流转表查看实际的状态变更记录确认是哪一步没有触发。如果是支付成功但状态未更新优先检查回调接口的幂等性处理防止重复回调导致状态被覆盖。如果是出餐状态未同步到用户端要么是前端轮询接口挂了要么是状态更新接口没有返回最新状态。这套系统的状态更新接口我设计成幂等的如果请求更新的目标状态与当前状态一致直接返回成功不会做重复更新。这样即使前端因为网络问题重发请求也不会把状态搞乱。6. 部署上线与后续扩展建议6.1 云服务器部署的实践步骤系统的部署我用的是经典的单机部署方案一台Linux服务器安装MySQL 8.0和JDK 11后端打成jar包运行前端打包成静态文件放在Nginx下。部署时要注意三个细节。第一个是MySQL的时区配置如果默认时区是UTC数据库存的时间会和本地时间相差8小时查询会有很大的偏差。第二个是jar包启动脚本要和项目目录分离日志输出到固定目录。我习惯用nohup命令启动把控制台日志重定向到logs目录下方便之后通过日志排查问题。第三个是防火墙和安全组要同时放通对应端口很多人本地测试一切正常部署到云服务器后前台页面打不开或前后端接口请求失败大概率就是端口没有放通。Nginx配置里把前端静态资源和后端接口的反向代理放在同一个server块中接口路径为server_name/api/转发到本机8080端口的后端服务。配置跨域时主要让前端请求API能带上Cookie保证Session机制的登录状态可用。6.2 项目可以怎么进一步扩展这个基础版本做完了后续扩展方向可以从两个维度考虑。面向用户的维度可以做订单评价、积分商城、会员优惠券、不同门店的选择。面向运营的维度可以增加销售趋势图、食材损耗分析、员工绩效统计等。如果想把系统的并发能力再提升一个台阶可以引入Redis缓存菜品列表和购物车数据减少数据库压力引入消息队列做订单创建的流量削峰让用户先看到下单中状态后端异步处理后再异步通知结果。但要注意这些技术改造必须建立在当前架构真的遇到性能瓶颈的前提下否则就是过度设计。高并发方案会显著提升复杂度对课程设计而言必须有足够清晰的架构理由才值得做。6.3 开发和部署环境相关配置参考这里给一份我测试通过的参考配置如果你的环境和我的类似可以直接参考。配置项值说明JDK版本1.8 或 11两个版本都跑通过Spring Boot2.7.x不建议直接用3.x部分组件差异较大MySQL8.0.x注意字符集配置建表时明确utf8mb4MyBatismybatis-spring-boot-starter 2.2.x稳定版本前端原生JS Bootstrap 5描述页面布局效果直观方便答辩展示构建Maven 3.8注意maven仓库镜像的配置数据库连接配置放在application.yml中密码等信息建议用环境变量注入不要明文写在配置文件里部署到公开环境。提示这套系统的完整开发过程本质上是在模拟一个真实的业务系统从需求分析到上线运行的闭环。项目不需要做到大厂一样的分布式架构但流程规范、职责清晰、异常处理完整才是在课程设计和面试中真正能拿得出手的部分。做这类项目有一个很深的体会代码写完能跑只是最基础的一步把数据一致性、异常边界、权限控制这些看不见的部分处理好才是系统真正可用的关键。踩过的坑多了自然会形成一套自己的排查方法。后续如果你想在这个系统上加点什么功能建议从一个真实存在的业务痛点出发比如高峰期出餐排队提示、优惠活动价格覆盖规则之类的别为了炫技去加一堆没用的技术栈。系统业务跑得顺代码结构看得懂这份设计才算真正有价值。
返回列表