尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Spring Boot外卖点餐系统实战:数据库设计、并发扣库存与支付回调

Spring Boot外卖点餐系统实战:数据库设计、并发扣库存与支付回调 简介Spring Boot 作为 Java 后端的主流开发框架被广泛应用于各类企业级系统与课程设计中。一个完整的外卖点餐系统不仅要实现基础增删改查更要妥善处理数据库表结构设计、订单状态流转、高并发场景下的库存扣减、Redis 缓存优化以及支付回调的幂等性等核心问题。这些技术点共同构成了业务闭环也是 Java 后端开发者必须掌握的关键能力。从普通用户浏览菜品、下单支付到商家接单出餐再到管理员后台管理每个环节都考验着开发者对事务、锁、状态机等底层原理的理解。本文结合工程实践系统梳理了基于 Spring Boot 构建外卖点餐系统的完整流程涵盖技术选型、表结构设计、核心代码实现、部署运行及答辩准备帮助读者将理论概念落地到真实项目中。 接手过不少用 Spring Boot 做的外卖点餐系统项目有拿来交课程设计的有想在小餐馆里跑通演示的也有单纯为了面试时能讲清楚一个完整项目的。说句实在话这个题目在网上非常常见但大多数所谓“源码”都是把用户端、商家端、管理后台拼起来就完事真问到底层表结构为什么要这样设计、下单时库存怎么扣、支付回调怎么保证幂等很多人答不上来。这篇文章想做的就是把“Spring Boot 外卖点餐系统”从技术选型、数据库设计、核心代码实现到部署运行和论文答辩准备完整地梳理一遍。不管你是打算拿现成源码二次开发还是想自己从零手写一个里面涉及的业务闭环、状态机设计、并发扣库存、缓存使用都是实际项目中能直接用上的东西。文章尽量少讲废话多给能落地的方案。1. 这套外卖点餐系统的定位搞清楚要做什么再谈技术选型1.1 系统的业务闭环到底长什么样先说清楚外卖点餐系统解决的是什么问题。它不是一个简单的“购物车订单”代码而是一套覆盖三类角色的完整业务流普通用户负责浏览菜品、加购物车、下单、支付、查看订单状态商家负责管理菜品上下架、接收新订单、接单、完成出餐平台管理员负责审核商家、管理用户、处理异常订单、查看整体经营数据。从用户下单到订单完成一条完整的业务链路大概是用户选好菜品放入购物车提交订单后进入待支付状态支付成功后订单推送给商家商家接单开始制作平台安排配送演示类系统通常简化成商家确认发货或骑手取件用户收到餐后确认完成最后可以对订单进行评价。这套业务闭环是这个题目最大的价值所在——它覆盖了典型的增删改查、状态流转、支付对接、并发控制、权限校验和缓存优化几乎所有后端面试里常考的点都能在项目里找到落点。这也是为什么很多人拿它做毕业设计或者面试项目因为它足够完整又不像电商整站那么庞大。1.2 Spring Boot 为主力周边技术栈怎么搭配技术选型上这类系统最稳妥的组合是后端框架Spring Boot 2.7.xJDK 1.8 或 11。如果你用的是 Spring Boot 3.x需要 JDK 17并且要注意javax.*包全部变成了jakarta.*老教程里的代码直接复制大概率编译不过第一次做这个项目的人容易卡在这里。持久层MyBatis-Plus。它比原生 MyBatis 少写大量 XML单表 CRUD 直接继承BaseMapper就能用分页插件、条件构造器都能显著提升开发效率。如果你打算在论文里写“数据持久层设计”MyBatis-Plus 的 LambdaQueryWrapper 也是很好讲的点。数据库MySQL 5.7 或 8.0字符集统一 utf8mb4排序规则用 utf8mb4_general_ci 或 utf8mb4_unicode_ci。缓存Redis用来存验证码、热点菜品缓存、也可以承载购物车数据。前端Vue 2 或 Vue 3 Element UI/Plus配合 Axios 调用后端接口。如果要做小程序端常见做法是 uni-app 复用 Vue 语法。接口管理Knife4j 或 springfox 集成 Swagger方便前后端联调也方便演示时快速查看接口。很多人会问这个规模要不要拆微服务我的建议很明确不要。外卖点餐系统作为单体 Spring Boot 应用完全够用模块之间通过包结构划分边界比如controller、service、mapper、entity分层远比拆成订单服务、用户服务、商品服务要简单可靠。微服务带来的分布式事务、服务发现、配置中心对一个演示级项目来说完全是负担答辩时反而容易被追问到说不清。2. 数据库设计建表之前先想清楚订单和库存是怎么流转的2.1 功能模块拆解与数据表规划数据库是这类系统的地基。我一般会先画功能模块图再倒推表结构。把系统拆成以下几个模块用户模块、商家模块、菜品模块、购物车模块、订单模块、支付模块、评价模块和后台管理模块。对应到数据库表核心的表大致如下业务域数据表关键字段用户userid, username, password, phone, avatar, status地址addressid, user_id, consignee, phone, detail, is_default商家shopid, name, status, notice, delivery_fee, start_time, end_time分类categoryid, shop_id, name, sort菜品dishid, shop_id, category_id, name, image, price, stock, status, description购物车cartid, user_id, dish_id, quantity, checked订单ordersid, order_no, user_id, shop_id, total_amount, pay_amount, status, address_snapshot订单明细order_detailid, order_id, dish_id, dish_name, dish_image, price, quantity评价commentid, order_id, user_id, content, rating管理员adminid, username, password, role优惠券可选couponid, name, threshold, amount, stock2.2 订单表为什么要做“冗余快照”在设计订单表和订单明细表时最容易出现的低级错误是把菜品名称、价格、图片直接关联查询 dish 表。看起来没问题但实际一旦商家修改了菜品价格、改名或下架历史订单显示就会错乱——用户明明下单时是 15 元后来商家调成 20 元用户查历史订单发现价格变成了 20 元这就是不合格的订单设计。正确做法是下单时把当前菜品信息“快照”到订单明细中也就是order_detail表同时存dish_id、dish_name、dish_image、price。订单表本身也存address_snapshot收货地址快照不要只存地址表的 id否则用户之后修改默认地址历史订单的配送信息也会被改掉。这个设计无论是论文里的“数据库设计”章节还是面试中“为什么订单表要冗余字段”都是非常加分的点。2.3 核心表结构与 DDL 示例这里给出菜品表、订单表、订单明细表的 DDL 示例照着建就能跑CREATE TABLE dish ( id bigint(20) NOT NULL AUTO_INCREMENT, shop_id bigint(20) NOT NULL COMMENT 所属商家ID, category_id bigint(20) NOT NULL COMMENT 分类ID, name varchar(64) NOT NULL COMMENT 菜品名称, image varchar(255) DEFAULT NULL COMMENT 菜品图片URL, price decimal(10,2) NOT NULL COMMENT 售价, stock int(11) NOT NULL DEFAULT 0 COMMENT 库存, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 1上架 0下架, description varchar(255) DEFAULT NULL, create_time datetime NOT NULL, update_time datetime NOT NULL, PRIMARY KEY (id), KEY idx_shop_category (shop_id, category_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT菜品表;CREATE TABLE orders ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 业务订单号, user_id bigint(20) NOT NULL, shop_id bigint(20) NOT NULL, total_amount decimal(10,2) NOT NULL COMMENT 商品总额, pay_amount decimal(10,2) NOT NULL COMMENT 实付金额, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2商家已接单 3配送中 4已完成 5已取消, address_snapshot varchar(255) NOT NULL COMMENT 收货地址快照, pay_time datetime DEFAULT NULL, finish_time datetime DEFAULT NULL, create_time datetime NOT NULL, update_time datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_status_time (user_id, status, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;CREATE TABLE order_detail ( id bigint(20) NOT NULL AUTO_INCREMENT, order_id bigint(20) NOT NULL, dish_id bigint(20) NOT NULL, dish_name varchar(64) NOT NULL COMMENT 菜品名称快照, dish_image varchar(255) DEFAULT NULL COMMENT 菜品图片快照, price decimal(10,2) NOT NULL COMMENT 下单时单价快照, quantity int(11) NOT NULL, PRIMARY KEY (id), KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;这里有几个细节值得注意金额字段必须用decimal(10,2)不能用 float 或 double否则金额累加会出现精度问题订单表要单独建一个业务订单号order_no并且加唯一索引对外展示和支付回调都用它内部自增主键只作为数据库主键使用orders表建了(user_id, status, create_time)联合索引这是按用户查订单列表最常见的查询条件不建索引的话数据量上来之后 SQL 会很慢。3. 核心业务链路的实现逻辑和方法3.1 登录鉴权JWT 拦截器的常规组合一般场景下用户端和管理后台不需要做复杂 OAuthJWT 是最常见的方案。流程是用户提交用户名密码后端校验通过后用io.jsonwebtoken.JJWT生成 token返回给前端前端把 token 存在 localStorage并在 Axios 请求拦截器里通过Authorization: Bearer token传给后端后端写一个拦截器校验 token 是否存在且合法然后放行。拦截器配置时有一类路径必须放行登录接口、注册接口、菜品列表接口、支付回调接口。支付回调尤其不能放在拦截器里要求登录因为支付平台的服务器根本不会带你的 token回调被拦截会导致支付成功但订单状态一直不更新这是比较容易踩的坑。更安全的做法是把回调写在一个独立 Controller 中路径单独匹配放行。3.2 菜品数据用 Redis 做缓存读多写少的典型场景菜品列表和分类是典型读多写少的数据。用户打开首页要加载分类和菜品商家改菜品的频率远低于用户浏览的频率。最朴素的缓存方案是用 Redis String 缓存整个分类列表key 设计成category:list每个分类下的菜品列表按分类 id 缓存key 设计成dish:category:{categoryId}。读取逻辑是先查 Redis命中直接返回没命中则查数据库再把结果写回 Redis并设置过期时间比如 30 分钟。商家端新增或修改菜品后主动删除对应的缓存 key下次读取时重新加载。缓存虽然简单但有一个问题会被问到缓存穿透、缓存击穿、缓存雪崩怎么办。实际项目中可以不把三个都做全但至少要有基础防护——缓存空值能防穿透热点 key 不加固定过期时间改为逻辑过期能防击穿不同分类设置不同的随机过期时间能降低雪崩概率。能讲清楚这几点答辩时“Redis 在系统中的作用”这一问基本就稳了。3.3 下单核心逻辑事务、库存校验与订单状态机下单接口是整个系统最不能出错的环节。正确流程是前端把购物车勾选的菜品清单提交给后端后端重新计算价格绝不能信任前端传的金额校验库存生成订单号和订单明细扣减库存然后返回订单号给前端去发起支付。一次后端重新计算价格的动作天然避免了“用户篡改请求价格”的安全漏洞。这也是论文里“安全性设计”部分可以写的内容。核心实现代码如下。先定义下单的 Service 方法Override Transactional(rollbackFor Exception.class) public PayOrderVO submitOrder(SubmitOrderDTO dto) { // 1. 查询用户收货地址生成地址快照 Address address addressMapper.selectById(dto.getAddressId()); if (address null) { throw new BusinessException(收货地址不存在); } // 2. 遍历购物车计算总金额并生成订单明细 BigDecimal totalAmount BigDecimal.ZERO; ListOrderDetail detailList new ArrayList(); for (CartItemDTO item : dto.getItemList()) { Dish dish dishMapper.selectById(item.getDishId()); if (dish null || dish.getStatus() ! 1) { throw new BusinessException(菜品已下架 item.getDishId()); } // 3. 核心带库存条件的更新返回影响行数 int rows dishMapper.reduceStock(dish.getId(), item.getQuantity()); if (rows 0) { throw new BusinessException(库存不足 dish.getName()); } totalAmount totalAmount.add( dish.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())) ); OrderDetail detail new OrderDetail(); detail.setDishId(dish.getId()); detail.setDishName(dish.getName()); detail.setDishImage(dish.getImage()); detail.setPrice(dish.getPrice()); detail.setQuantity(item.getQuantity()); detailList.add(detail); } // 4. 生成订单号和订单主表记录 Orders order new Orders(); order.setOrderNo(generateOrderNo()); order.setUserId(dto.getUserId()); order.setShopId(dto.getShopId()); order.setTotalAmount(totalAmount); order.setPayAmount(totalAmount); order.setStatus(0); order.setAddressSnapshot(address.getFullAddress()); orderMapper.insert(order); // 5. 插入订单明细 for (OrderDetail detail : detailList) { detail.setOrderId(order.getId()); orderDetailMapper.insert(detail); } return new PayOrderVO(order.getId(), order.getOrderNo(), order.getPayAmount()); }对应的reduceStockSQL 在 DishMapper 中定义重点看 where 条件update idreduceStock UPDATE dish SET stock stock - #{quantity} WHERE id #{dishId} AND stock #{quantity} /update这里不能先SELECT stock判断再执行UPDATE因为并发同时下单时两个请求都查到库存充足最后都会执行扣减就超卖了。把判断条件放进 update 的 where 里利用数据库行锁保证同一时刻只有一个事务能成功扣减这是最简单也最有效的防超卖手段。3.4 订单状态机与支付回调幂等订单状态我一般设计成待支付0→ 已支付/待接单1→ 商家已接单2→ 配送中3→ 已完成4另外还有已取消5。状态迁移必须是有向的比如已完成的订单不允许再次取消已取消的订单不允许变成已支付。代码上可以在 Service 层写一个状态变更方法每次都校验当前状态和期望状态public void changeOrderStatus(Long orderId, Integer expectStatus, Integer targetStatus) { Orders order orderMapper.selectById(orderId); if (order null) { throw new BusinessException(订单不存在); } if (!expectStatus.equals(order.getStatus())) { throw new BusinessException(订单状态已变更请刷新后重试); } order.setStatus(targetStatus); orderMapper.updateById(order); }支付回调是另一个重点。以微信支付/支付宝沙箱为例支付平台会异步 POST 一个回调到你的接口通知支付结果。回调接口要做三件事验签、查订单、改状态。验签确保请求确实来自支付平台查订单是拿着回调里的order_no去数据库查一笔订单改状态时必须判断当前订单是否已经是“已支付”如果是就直接返回成功不再重复更新。这就是幂等。如果不做幂等支付平台重试回调时可能导致同一笔订单被反复更新商家端收到重复消息。if (order.getStatus() ! 0) { return SUCCESS; // 已经处理过了直接返回成功 } order.setStatus(1); order.setPayTime(new Date()); orderMapper.updateById(order);对于超时未支付订单常用方案是定时任务每分钟扫描一次超过 15 分钟未支付的订单主动置为已取消并回补库存。单机部署用Scheduled就够了多实例部署时要注意加分布式锁否则多个实例同时扫到同一笔订单会重复回补库存。4. 我踩过的坑事务失效、并发超卖、跨域、时区与静态资源4.1 下单事务不生效查了一天才发现是同类调用第一次实现下单时我把submitOrder和内部的扣库存逻辑都写在同一个 Service 类里然后在类内部直接调用“另一个私有方法”方法上加了Transactional结果下单后数据库中订单和明细都没了但库存却扣了。看起来是很诡异的问题其实原因是在同一个类内部通过this调用带事务的方法并不会经过 Spring 的代理对象事务注解直接失效。这个坑的解法很简单把扣库存或其他需要事务保护的操作拆到另一个 Service 里比如DishStockService然后在原 Service 里注入它通过代理对象调用。这也是为什么我上面示例代码里把dishMapper.reduceStock直接写在事务方法里因为它和整体下单逻辑处于同一个事务边界内。排查事务问题时最快的方式是在方法入口打印日志观察数据库里发生异常后数据是否回滚。也可以给application.yml加一行logging.level.org.springframework.transactionDEBUG看事务的提交和回滚日志。4.2 并发超卖先查库存再扣减就是错的这个问题在上面下单代码里已经给出了正确做法但值得单独提一次因为我见过太多版本只在代码里写了“库存足够才扣”的判断没有把条件放进 SQL 里。用两个线程同时请求数量为 1 的库存两个请求都查到库存为 1然后同时执行扣减最后数据库库存可能变成 -1。用UPDATE ... WHERE stock #{quantity}之后第二次 update 影响行数为 0直接抛业务异常回滚库存永远不可能为负数。如果商家端使用 Redis 缓存菜品库存还需要额外考虑缓存和数据库的一致性这个在演示项目中可以不展开但答辩被问到时至少要知道扣减的核心逻辑要放在数据库层面保证原子性。4.3 跨域拦截器顺序问题前端报跨域接口却没进拦截器前后端分离后Vue 起的开发服务器在localhost:5173后端在localhost:8080浏览器会发跨域请求。常规方案是后端配置 CorsFilter 或者实现WebMvcConfigurer#addCorsMappings但如果你同时注册了登录拦截器很可能会发现前置 OPTIONS 预检请求直接被拦截器拦住了根本走不到跨域处理。解决办法有两个一是在拦截器中直接放行 OPTIONS 请求二是跨域配置改用CorsFilter它会先于拦截器处理请求添加跨域响应头后再进入业务逻辑。我倾向于第二种配置简单也更稳妥。Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); }4.4 本地好好的服务器部署就出问题时区和静态资源这类问题在新手项目里出镜率极高。本地开发时数据库连接串里写了serverTimezoneAsia/Shanghai开发机系统时区也是东八区一切正常部署到 Linux 服务器后容器默认时区是 UTC订单创建时间就比实际时间慢 8 小时。排查方式先date看服务器时间再查 MySQL 的time_zone最后看应用日志里的时间和数据库里的时间是否一致。修正方案是数据库连接串显式指定时区同时启动容器时把时区映射进去比如 Docker Compose 里加上TZAsia/Shanghai环境变量。静态资源的问题则是上传的菜品图片保存在本地磁盘/data/images/但页面访问http://ip:8080/images/xxx.jpg返回 404。原因是没有把本地目录映射成静态资源路径。需要继承WebMvcConfigurer添加资源映射Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/images/**) .addResourceHandler(file: uploadDir); }如果是演示项目用相对路径存图片部署之后路径错乱的概率很大。建议统一用一个绝对路径配置项比如app.upload-dir/data/images启动前手动创建目录。5. 从源码到能跑本地启动、Docker 部署和演示环境准备5.1 拿到源码包后怎么在本地快速跑起来不管源码是网上拿的还是自己写的第一次运行都要按顺序做这几件事先看 README再初始化数据库接着改配置然后启动后端最后启动前端。初始化数据库时必须确认数据库脚本的字符集。如果你用的是 MySQL 8.0而脚本是从 5.7 导出的执行时大概率遇到Unknown collation: utf8mb4_0900_ai_ci之类的报错解决方式是全局替换排序规则为utf8mb4_general_ci。后端配置一般只改application.yml里的数据源和 Redis 地址spring: datasource: url: jdbc:mysql://localhost:3306/food_delivery?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你自己的密码 redis: host: localhost port: 6379启动命令用 Maven 的话是mvn spring-boot:run或者打 jar 包再跑。启动日志里看到Started Application in x.xx seconds说明后端起来了然后用 Swagger 或直接浏览器访问登录接口验证连通性。前端部分如果是 Vue 项目通常改的是.env.development里VUE_APP_BASE_URLhttp://localhost:8080然后执行npm install再npm run dev。Vue 项目 node_modules 安装很慢是常见情况建议切换 npm 镜像源。5.2 Docker 部署数据库、Redis 和后端一键编排如果要把项目部署到服务器演示Docker Compose 是最省心的方式。一个标准的docker-compose.yml包含三块MySQL、Redis、后端应用。后端打包成镜像前需要把application.yml里的数据库地址从localhost改成 MySQL 容器服务名比如mysql因为容器之间是通过服务名解析的。一个可用的 Dockerfile 示例FROM openjdk:8-jdk-alpine VOLUME /tmp COPY target/food-delivery.jar app.jar ENV TZAsia/Shanghai ENTRYPOINT [java,-jar,/app.jar]再配合 docker-composeversion: 3 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: food_delivery ports: - 3306:3306 volumes: - ./sql:/docker-entrypoint-initdb.d - mysql-data:/var/lib/mysql redis: image: redis:7 ports: - 6379:6379 app: build: . depends_on: - mysql - redis ports: - 8080:8080 volumes: - /data/images:/data/images volumes: mysql-data:这样部署完后前端静态资源可以用 Nginx 托管/api路径反向代理到http://localhost:8080。前端打包产物是dist目录直接放到 Nginx 的 html 目录下即可。5.3 演示环境准备不要让演示变成事故现场很多人在答辩或给客户演示时栽在环境准备上。提前一天检查这几项数据库是否导入了测试数据至少保证有 5 个以上分类、30 个以上菜品菜品图片能够正常访问商家账号的状态是否是“营业中”否则用户端看不到这家店Redis 是否能连接缓存 key 是否过期导致第一次访问特别慢支付环节如果没有真实商户号一定提前在代码里预留“模拟支付”入口点击按钮直接改订单状态为已支付避免演示时卡在扫码支付环节。演示时最尴尬的情况不是功能少而是接口加载超时、图片 404、下单后订单列表刷不出来。花十分钟把这些静态数据和本地环境调好演示流畅度会高很多。6. 论文与答辩准备这套系统怎么“讲”出价值6.1 论文结构的整体安排毕业设计论文的结构虽然各校有差异但大体是固定的。放在外卖点餐系统上比较顺的章节安排是绪论背景与意义、国内外研究现状、论文主要工作。这一章不用写太多重点是引出“外卖点餐系统为什么值得研究”。相关技术介绍Spring Boot、MyBatis-Plus、Redis、Vue、JWT 等。写技术简介时不要只抄概念要写“本项目用它解决什么问题”比如 Redis 用来做缓存和验证码存储。需求分析功能性需求用户、商家、管理员各角色用例图、非功能性需求性能、安全、易用性。系统设计总体架构图、功能模块图、数据库 E-R 图、数据库表结构设计。系统详细设计与实现这一章是核心按业务模块逐个介绍实现思路配合核心类图、时序图、关键代码片段。下单模块、支付回调、库存扣减是最值得重点写的。系统测试功能测试用例表、测试结果、性能测试简单分析。总结与展望总结做的工作指出不足和后续扩展方向。写论文时最容易犯的错是一大段一大段贴代码。论文不是源码分析文档代码只要截取关键片段比如下单事务方法、库存扣减 SQL每段代码后面跟一段文字解释设计思路这比堆代码有价值得多。6.2 答辩 PPT 与演示顺序答辩 PPT 不要超过 15 页时间控制在 8 到 12 分钟。推荐顺序是封面选题背景与意义技术路线与开发环境需求分析痛点系统总体设计架构图 流程图核心模块设计与实现用户下单、商家接单、支付回调界面演示截图系统测试总结与展望。演示环节比 PPT 更重要。建议先演示用户端从注册/登录、浏览菜品、加入购物车、下单、支付到订单状态变成已支付再切换商家账号演示接单、完成出餐的操作最后切换到管理后台展示订单报表数据。整套流程控制在 5 分钟左右节奏要稳每切一个页面说一两个关键点就行不用把所有功能都点一遍。如果现场网速不稳提前录一份演示视频作为备选方案关键时刻能救场。6.3 高频答辩问题与答题思路以下问题在我见过的答辩中被问次数最多每个都值得提前准备为什么选 Spring Boot 而不是 SSH/SSM回答核心是自动配置、内置容器、生态丰富开发效率高。MyBatis-Plus 相比 MyBatis 解决了什么问题单表 CRUD 无需写 SQL、条件构造器、分页插件。订单状态是怎么流转的把状态机图表讲清楚重点强调状态变更必须校验前置状态。怎么防止商品超卖讲清楚UPDATE ... WHERE stock quantity的原子性再提到事务回滚。Redis 在项目中用在哪些地方验证码、菜品缓存、购物车。问得深一点会问缓存和数据库一致性可以回答“更新数据库后主动删除缓存”的策略。支付回调怎么保证安全验签、回调接口放行、幂等处理。下单后一直不支付怎么办定时任务扫描超时订单取消订单并回补库存单机用Scheduled多实例考虑分布式锁。一个人做系统时代码可以自己研究着写但到了答辩环节必须知道自己写的每个模块解决的核心问题是什么。源码是别人整理的也好、自己手写的也好业务逻辑、表结构、状态流转这些内容一定要自己拆一遍因为老师不会按着源码问他只会按着“你做的系统”来问。最后再分享一点体会如果时间允许把这个项目拆开重写一遍尤其是下单、库存、回调这三个核心点比单纯把源码跑起来有用得多。很多人在答辩时被问倒恰恰是因为只记住了项目“能跑”却说不清楚为什么这样设计。把表结构为什么冗余、库存为什么用条件更新、回调为什么要求幂等这些底层逻辑想明白这套外卖点餐系统才真正算你的东西。本文还有配套的精品资源点击获取
返回列表