在实际高校食堂运营中高峰时段排队拥挤、人工点餐效率低下、支付方式单一以及后厨备餐信息滞后是普遍存在的痛点。一个基于SpringBoot的自助点餐系统能够让学生通过手机或食堂终端自主完成浏览菜单、下单、支付并将订单实时推送至后厨有效分流窗口压力提升整体运营效率。对于开发者而言这不仅是一个典型的Web应用项目更是一个融合了用户管理、商品展示、购物车、订单处理、支付集成和后厨调度的综合性实战案例。本文将以一个可运行的高校食堂自助点餐系统原型为例带你从零开始完成系统设计、技术选型、核心功能实现到部署上线的全过程。你将了解到如何用SpringBoot快速搭建后端服务如何设计符合食堂业务的数据模型如何处理高并发下的订单状态流转以及如何集成常见的支付功能。文章面向具备Java和SpringBoot基础希望进行完整项目实践的开发者。通过本文你将获得一个可直接用于毕业设计或作为企业级项目雏形的完整方案。1. 系统核心设计与技术栈选型在动手编码之前明确系统的业务边界和技术架构是避免后期返工的关键。一个食堂点餐系统其核心业务流程是用户登录 - 浏览菜品 - 加入购物车 - 提交订单 - 支付 - 后厨接单制作 - 用户取餐。围绕这个流程我们需要设计相应的功能模块和技术组件。1.1 业务模块划分系统主要分为前台用户端、后台管理端和后厨显示端。前台用户端面向学生和教职工。核心功能包括用户注册登录、菜品浏览与搜索、购物车管理、订单创建与支付、订单状态查询、个人中心历史订单、收藏。后台管理端面向食堂管理员。核心功能包括菜品分类与信息管理增删改查、上下架、订单管理查看、接单、出餐完成、数据统计销量、营收、用户管理。后厨显示端面向厨师。核心功能是实时、清晰地展示待处理的订单列表通常按时间或菜品类型排序并能在制作完成后标记订单状态。1.2 技术栈选型与理由基于SpringBoot生态我们选择以下技术栈并解释其选型理由后端框架SpringBoot 2.7.x简化配置内嵌Tomcat快速构建独立运行的、生产级的应用。选择2.7.x这个长期支持版本稳定性高社区资源丰富。持久层MyBatis-Plus在MyBatis基础上增强提供通用的CRUD操作减少大量简单SQL的编写提高开发效率。其分页插件、条件构造器非常适合动态查询场景。数据库MySQL 8.0关系型数据库适合存储结构化的用户、菜品、订单数据。8.0版本在性能和JSON支持上有较好表现。缓存Redis用于存储用户会话替代Session、热门菜品缓存、购物车临时数据、以及秒杀场景下的库存预减能极大提升系统响应速度和并发能力。消息队列RabbitMQ用于解耦核心业务。例如用户支付成功后通过消息队列异步通知后厨系统生成制作单避免同步调用导致的阻塞和耦合。前端技术Vue 3 Element Plus前后端分离架构下Vue框架易于上手组件化开发效率高。Element Plus提供了丰富的UI组件能快速构建美观的管理后台。用户端也可采用Vue或Uni-app用于生成小程序。构建与依赖管理Maven管理项目依赖统一项目结构。API文档Swagger/knife4j自动生成和测试API接口文档便于前后端协作。注意技术选型需考虑团队熟悉度和项目实际规模。对于小型项目可先省略消息队列和缓存随着业务复杂再引入。2. 项目环境准备与基础搭建在开始编写业务代码前需要确保开发环境就绪并搭建好项目的基础骨架包括统一的依赖管理、配置规范和项目结构。2.1 开发环境清单请确保你的本地开发环境包含以下组件并建议使用指定或更高版本。环境/工具推荐版本用途说明JDK1.8 或 11SpringBoot 2.7.x 兼容的Java运行环境。IDEIntelliJ IDEA强大的Java IDE对SpringBoot支持极佳。MySQL5.7 或 8.0主数据库。Redis6.x缓存与会话存储。Maven3.6项目构建与依赖管理。Node.js16运行前端Vue项目如管理端采用前后分离。Postman最新版API接口测试。2.2 使用Spring Initializr初始化项目使用IDEA的Spring Initializr或访问 start.spring.io 创建项目。Project: Maven ProjectLanguage: JavaSpring Boot: 2.7.18 (一个稳定的2.7.x版本)Project Metadata:Group:com.campusArtifact:canteen-order-systemPackaging: JarJava: 8 或 11Dependencies: 添加Spring Web,Lombok,MyBatis Framework,MySQL Driver。点击生成并导入到IDEA中。后续其他依赖我们在pom.xml中手动添加。2.3 完善POM依赖与项目结构在生成的pom.xml中我们需要补充MyBatis-Plus、Redis、RabbitMQ等依赖。同时规划一个清晰的项目包结构。?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent groupIdcom.campus/groupId artifactIdcanteen-order-system/artifactId version0.0.1-SNAPSHOT/version namecanteen-order-system/name description高校食堂自助点餐系统/description properties java.version1.8/java.version mybatis-plus.version3.5.3.1/mybatis-plus.version knife4j.version3.0.3/knife4j.version /properties dependencies !-- Spring Boot 基础依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency !-- 数据库相关 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version${mybatis-plus.version}/version /dependency !-- 缓存 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- 消息队列 (可选按需引入) -- !-- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-amqp/artifactId /dependency -- !-- 工具 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdcom.alibaba/groupId artifactIdfastjson/artifactId version1.2.83/version /dependency !-- API文档 -- dependency groupIdcom.github.xiaoymin/groupId artifactIdknife4j-spring-boot-starter/artifactId version${knife4j.version}/version /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration excludes exclude groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /exclude /excludes /configuration /plugin /plugins /build /project项目包结构建议如下src/main/java/com/campus/canteen ├── CanteenOrderSystemApplication.java // 启动类 ├── config // 配置类 (RedisConfig, MybatisPlusConfig, WebMvcConfig等) ├── controller // 控制器层 ├── service // 服务层 │ └── impl // 服务实现层 ├── mapper // MyBatis Mapper接口层 ├── entity // 实体类 (对应数据库表) ├── dto // 数据传输对象 (用于前后端交互) ├── vo // 视图对象 (用于接口返回) ├── enums // 枚举类 (如订单状态) ├── utils // 工具类 └── aspect // 切面 (如日志、权限)3. 数据库设计与核心实体建模数据库设计是系统的基石需要准确反映业务实体及其关系。我们围绕用户、菜品、订单三个核心实体展开。3.1 核心表结构设计以下是简化版的核心表结构实际项目可能需要更多字段如创建时间、更新时间、逻辑删除标记等。-- 用户表 CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(64) NOT NULL COMMENT 用户名/学工号, password varchar(255) NOT NULL COMMENT 密码加密后, nickname varchar(64) DEFAULT NULL COMMENT 昵称, phone varchar(20) DEFAULT NULL COMMENT 手机号, avatar varchar(500) DEFAULT NULL COMMENT 头像, status tinyint(1) DEFAULT 1 COMMENT 状态 0:禁用1:正常, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 菜品分类表 CREATE TABLE category ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(64) NOT NULL COMMENT 分类名称如主食、饮料、小吃, sort int(11) DEFAULT 0 COMMENT 排序, status tinyint(1) DEFAULT 1 COMMENT 状态 0:停用1:启用, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT菜品分类表; -- 菜品表 CREATE TABLE dish ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(128) NOT NULL COMMENT 菜品名称, category_id bigint(20) NOT NULL COMMENT 分类ID, price decimal(10,2) NOT NULL COMMENT 价格, image varchar(500) DEFAULT NULL COMMENT 图片, description varchar(500) DEFAULT NULL COMMENT 描述, status tinyint(1) DEFAULT 1 COMMENT 状态 0:停售1:启售, stock int(11) DEFAULT -1 COMMENT 库存(-1表示无限), create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category_id (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT菜品表; -- 订单表 (核心) CREATE TABLE orders ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, order_no varchar(64) NOT NULL COMMENT 订单号唯一, user_id bigint(20) NOT NULL COMMENT 用户ID, amount decimal(10,2) NOT NULL COMMENT 订单总金额, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 订单状态 1:待支付2:已支付/待接单3:制作中4:待取餐5:已完成6:已取消, pay_method tinyint(4) DEFAULT NULL COMMENT 支付方式 1:微信2:支付宝, pay_time datetime DEFAULT NULL COMMENT 支付时间, estimated_time datetime DEFAULT NULL COMMENT 预计取餐时间, remark varchar(500) DEFAULT NULL COMMENT 用户备注, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表; -- 订单明细表 CREATE TABLE order_detail ( id bigint(20) NOT NULL AUTO_INCREMENT, order_id bigint(20) NOT NULL COMMENT 订单ID, dish_id bigint(20) NOT NULL COMMENT 菜品ID, dish_name varchar(128) NOT NULL COMMENT 菜品名称快照, dish_image varchar(500) DEFAULT NULL COMMENT 菜品图片快照, amount int(11) NOT NULL COMMENT 数量, price decimal(10,2) NOT NULL COMMENT 单价快照, PRIMARY KEY (id), KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;设计要点说明订单号使用唯一字符串如时间戳随机数而非自增ID避免暴露业务量也更安全。状态枚举订单状态是业务流转的核心需要明确定义每个状态的含义和流转规则。数据快照order_detail表中存储了菜品下单时的名称、图片和价格即使原菜品信息后续修改订单历史数据也不会变保证了数据的准确性。索引在user_id,status,order_no等查询频繁的字段上建立索引提升查询性能。3.2 创建对应的Java实体类使用Lombok简化实体类代码。以Dish菜品实体为例package com.campus.canteen.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; Data TableName(dish) public class Dish { TableId(type IdType.AUTO) private Long id; private String name; private Long categoryId; private BigDecimal price; private String image; private String description; private Integer status; // 0 停售 1 启售 private Integer stock; // 库存-1表示无限 TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; }使用MyBatis-Plus的TableField注解配合元数据对象处理器MetaObjectHandler可以自动填充createTime和updateTime。package com.campus.canteen.config; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.time.LocalDateTime; Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }4. 核心业务功能实现我们将聚焦于用户登录、菜品浏览、购物车、下单支付这几个最核心的流程进行实现。4.1 用户登录与JWT令牌认证在前后端分离架构中通常使用JWTJSON Web Token进行无状态认证。1. 添加JWT依赖dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency2. 编写JWT工具类package com.campus.canteen.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Date; import java.util.HashMap; import java.util.Map; Component public class JwtUtil { Value(${jwt.secret}) private String secret; Value(${jwt.expiration}) private Long expiration; // 生成Token public String generateToken(String username) { MapString, Object claims new HashMap(); claims.put(sub, username); claims.put(created, new Date()); return doGenerateToken(claims, username); } private String doGenerateToken(MapString, Object claims, String subject) { return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() expiration * 1000)) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } // 验证并解析Token public Claims getClaimsFromToken(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } // 检查Token是否过期 public Boolean isTokenExpired(String token) { final Date expiration getExpirationDateFromToken(token); return expiration.before(new Date()); } public Date getExpirationDateFromToken(String token) { return getClaimsFromToken(token).getExpiration(); } }3. 用户登录Service实现package com.campus.canteen.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.campus.canteen.entity.User; import com.campus.canteen.mapper.UserMapper; import com.campus.canteen.service.UserService; import com.campus.canteen.utils.JwtUtil; import com.campus.canteen.vo.LoginVO; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; Service Slf4j public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; Autowired private JwtUtil jwtUtil; Autowired private BCryptPasswordEncoder passwordEncoder; Override public MapString, Object login(LoginVO loginVO) { // 1. 根据用户名查询用户 LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getUsername, loginVO.getUsername()); User user userMapper.selectOne(wrapper); if (user null) { throw new RuntimeException(用户名或密码错误); } // 2. 校验密码 (存储时应为加密后的密码) if (!passwordEncoder.matches(loginVO.getPassword(), user.getPassword())) { throw new RuntimeException(用户名或密码错误); } // 3. 检查用户状态 if (user.getStatus() 0) { throw new RuntimeException(账号已被禁用); } // 4. 生成JWT Token String token jwtUtil.generateToken(user.getUsername()); // 5. 返回用户基本信息和Token (密码不返回) MapString, Object result new HashMap(); result.put(token, token); result.put(userInfo, user); return result; } }4. 配置拦截器验证Token编写一个拦截器在除登录接口外的其他请求头中获取Authorization字段即Token并进行验证。4.2 菜品浏览与购物车管理购物车数据具有临时性且读写频繁非常适合用Redis存储。我们以用户ID为Key存储一个Hash结构field为菜品IDvalue为菜品数量。1. 购物车Service核心方法package com.campus.canteen.service.impl; import com.alibaba.fastjson.JSON; import com.campus.canteen.entity.Dish; import com.campus.canteen.service.CartService; import com.campus.canteen.vo.CartItemVO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Map; Service public class CartServiceImpl implements CartService { private static final String CART_PREFIX cart:user:; Autowired private RedisTemplateString, Object redisTemplate; Autowired private DishService dishService; Override public void addToCart(Long userId, Long dishId, Integer number) { // 构建Redis Key String key CART_PREFIX userId; HashOperationsString, String, String hashOps redisTemplate.opsForHash(); // 判断菜品是否存在及状态 Dish dish dishService.getById(dishId); if (dish null || dish.getStatus() 0) { throw new RuntimeException(菜品不存在或已停售); } // 获取当前购物车中该菜品的数量 String current (String) hashOps.get(key, dishId.toString()); Integer currentNumber current null ? 0 : Integer.parseInt(current); int newNumber currentNumber number; // 更新到Redis hashOps.put(key, dishId.toString(), String.valueOf(newNumber)); } Override public ListCartItemVO getCart(Long userId) { String key CART_PREFIX userId; HashOperationsString, String, String hashOps redisTemplate.opsForHash(); MapString, String entries hashOps.entries(key); ListCartItemVO cartItemList new ArrayList(); for (Map.EntryString, String entry : entries) { Long dishId Long.parseLong(entry.getKey()); Integer number Integer.parseInt(entry.getValue()); Dish dish dishService.getById(dishId); if (dish ! null dish.getStatus() 1) { CartItemVO item new CartItemVO(); item.setDishId(dishId); item.setName(dish.getName()); item.setImage(dish.getImage()); item.setPrice(dish.getPrice()); item.setNumber(number); item.setAmount(dish.getPrice().multiply(BigDecimal.valueOf(number))); cartItemList.add(item); } else { // 菜品已失效从购物车移除 hashOps.delete(key, dishId.toString()); } } return cartItemList; } Override public void clearCart(Long userId) { String key CART_PREFIX userId; redisTemplate.delete(key); } }4.3 下单与支付流程下单是核心事务操作需要保证数据一致性如库存扣减。这里展示一个简化的下单Service方法。1. 下单Service实现伪代码重点在流程Override Transactional(rollbackFor Exception.class) // 开启事务 public String submitOrder(OrderSubmitDTO orderSubmitDTO, Long userId) { // 1. 校验购物车数据计算总金额 ListCartItemVO cartItems cartService.getCart(userId); if (cartItems.isEmpty()) { throw new RuntimeException(购物车为空); } BigDecimal amount BigDecimal.ZERO; for (CartItemVO item : cartItems) { amount amount.add(item.getAmount()); } // 2. 生成唯一订单号 (示例时间戳随机数) String orderNo System.currentTimeMillis() new Random().nextInt(999); // 3. 组装订单主数据 Orders order new Orders(); order.setOrderNo(orderNo); order.setUserId(userId); order.setAmount(amount); order.setStatus(1); // 待支付 order.setRemark(orderSubmitDTO.getRemark()); orderMapper.insert(order); // 插入订单主表 // 4. 组装订单明细并扣减库存注意并发问题 ListOrderDetail orderDetailList new ArrayList(); for (CartItemVO item : cartItems) { OrderDetail detail new OrderDetail(); detail.setOrderId(order.getId()); detail.setDishId(item.getDishId()); detail.setDishName(item.getName()); detail.setDishImage(item.getImage()); detail.setAmount(item.getNumber()); detail.setPrice(item.getPrice()); orderDetailList.add(detail); // 扣减库存这里需要处理并发可使用数据库乐观锁或Redis分布式锁 Dish dish dishService.getById(item.getDishId()); if (dish.getStock() ! -1 dish.getStock() item.getNumber()) { throw new RuntimeException(菜品【 dish.getName() 】库存不足); } // 使用MyBatis-Plus的update方法配合版本号或条件更新 LambdaUpdateWrapperDish updateWrapper new LambdaUpdateWrapper(); updateWrapper.eq(Dish::getId, item.getDishId()) .ge(Dish::getStock, item.getNumber()) // 乐观锁条件 .set(Dish::getStock, dish.getStock() - item.getNumber()); int updateCount dishMapper.update(null, updateWrapper); if (updateCount 0) { throw new RuntimeException(菜品【 dish.getName() 】库存不足请刷新重试); } } // 批量插入订单明细 orderDetailService.saveBatch(orderDetailList); // 5. 清空购物车 cartService.clearCart(userId); // 6. 返回订单号用于后续支付 return orderNo; }2. 支付回调处理支付成功后第三方支付平台如微信支付、支付宝会异步通知我们的服务器。我们需要一个公网可访问的接口来接收回调并更新订单状态。PostMapping(/pay/notify) public String payNotify(HttpServletRequest request) { // 1. 解析回调参数验证签名非常重要防止伪造请求 MapString, String params parseRequestParams(request); boolean signValid verifySign(params); // 验证签名方法 if (!signValid) { return FAIL; } // 2. 获取回调中的订单号和支付状态 String orderNo params.get(out_trade_no); String tradeStatus params.get(trade_status); // 3. 根据订单号查询本地订单 LambdaQueryWrapperOrders wrapper new LambdaQueryWrapper(); wrapper.eq(Orders::getOrderNo, orderNo); Orders order orderService.getOne(wrapper); if (order null) { return FAIL; } // 4. 判断订单状态避免重复处理 if (order.getStatus() ! 1) { // 如果不是待支付状态 return SUCCESS; // 已处理过直接返回成功 } // 5. 根据支付平台状态更新本地订单 if (TRADE_SUCCESS.equals(tradeStatus)) { order.setStatus(2); // 更新为已支付/待接单 order.setPayTime(LocalDateTime.now()); order.setPayMethod(1); // 假设是微信支付 orderService.updateById(order); // 6. 可选发送消息到MQ通知后厨接单 // rabbitTemplate.convertAndSend(order.exchange, order.pay, orderNo); } // 7. 返回成功标识给支付平台 return SUCCESS; }5. 系统配置、运行与验证完成核心代码后需要进行正确的配置并验证整个流程是否跑通。5.1 关键配置文件详解application.yml是SpringBoot的核心配置文件。server: port: 8080 servlet: context-path: /api spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/campus_canteen?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai username: root password: your_password redis: host: localhost port: 6379 database: 0 # password: 如果Redis有密码则配置 lettuce: pool: max-active: 8 max-wait: -1ms max-idle: 8 min-idle: 0 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 # MyBatis-Plus 配置 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印SQL生产环境关闭 global-config: db-config: logic-delete-field: deleted # 全局逻辑删除字段名 logic-delete-value: 1 # 逻辑已删除值 logic-not-delete-value: 0 # 逻辑未删除值 # JWT 配置 jwt: secret: campusCanteenSecretKey2024 # 密钥生产环境需复杂且保密 expiration: 7200 # token过期时间(秒)2小时 # Knife4j 文档配置 knife4j: enable: true setting: language: zh_cn5.2 启动与基础功能验证启动服务运行CanteenOrderSystemApplication的main方法。访问API文档浏览器打开http://localhost:8080/api/doc.html即可看到Knife4j生成的漂亮文档界面并直接测试接口。验证流程用户注册/登录调用/user/login接口获取Token。浏览菜品调用/dish/list接口可公开访问。添加购物车在请求头中加入Authorization: Bearer {你的Token}调用/cart/add。查看购物车调用/cart/list。提交订单调用/order/submit获得订单号。模拟支付回调使用Postman模拟向/pay/notify发送一个POST请求携带正确的订单号和签名观察数据库订单状态是否从“1-待支付”变为“2-已支付”。5.3 后厨显示端简易实现后厨显示端可以是一个独立的Web页面通过WebSocket或定时轮询从后端获取状态为“2-已支付/待接单”和“3-制作中”的订单。1. 提供订单查询接口GetMapping(/kitchen/orders) public ResultListOrderVO getKitchenOrders(RequestParam(required false) Integer status) { // status: 2-待接单 3-制作中 LambdaQueryWrapperOrders wrapper new LambdaQueryWrapper(); wrapper.in(Orders::getStatus, Arrays.asList(2, 3)) .orderByAsc(Orders::getCreateTime); // 按下单时间排序 ListOrders orders orderService.list(wrapper); // 转换为VO包含菜品详情 ListOrderVO orderVOS orders.stream().map(order - { OrderVO vo new OrderVO(); BeanUtils.copyProperties(order, vo); ListOrderDetail details orderDetailService.listByOrderId(order.getId()); vo.setDetails(details); return vo; }).collect(Collectors.toList()); return Result.success(orderVOS); }2. 后厨接单/完成接口PostMapping(/kitchen/order/{orderId}/accept) public Result acceptOrder(PathVariable Long orderId) { Orders order orderService.getById(orderId); if (order null || order.getStatus() ! 2) { return Result.error(订单状态错误); } order.setStatus(3); // 制作中 orderService.updateById(order); return Result.success(); }6. 常见问题排查与优化实践在实际开发和部署中会遇到各种问题。以下是几个典型场景的排查思路和优化建议。6.1 常见启动与运行问题问题现象可能原因检查方式处理建议启动时报Failed to configure a DataSource数据库连接配置错误或数据库服务未启动。1. 检查application.yml中的url,username,password。2. 使用命令行或客户端连接MySQL确认服务正常。修正配置或启动数据库服务。调用接口返回4041. 请求路径错误。2. 未添加RestController或RequestMapping注解。3. 项目未正确扫描到Controller类。1. 检查Knife4j文档上的完整路径。2. 检查Controller类是否在启动类同级或子包下。3. 查看启动日志是否有Mapped {路径}的日志。修正请求路径或检查Spring组件扫描范围。Redis连接失败Redis服务未启动或配置的host/port/密码错误。1. 运行redis-cli ping看是否返回PONG。2. 检查Spring配置的Redis连接信息。启动Redis服务或修正配置。插入数据时create_time为nullMyBatis-Plus的元数据处理器未生效。1. 检查MyMetaObjectHandler类是否被Component注解。2. 检查实体类字段上的TableField(fill FieldFill.INSERT)注解。确保处理器被Spring管理且注解填写正确。6.2 业务逻辑与性能优化购物车数据持久化问题Redis是内存数据库重启后数据会丢失。对于重要的未下单购物车数据可以考虑在用户登录时将Redis中的购物车数据异步备份到数据库或者使用Redis的持久化机制RDB/AOF。库存超卖问题在高并发下单场景下简单的查询后更新会导致超卖。解决方案包括数据库乐观锁为dish表增加version字段更新时带版本条件。Redis分布式锁在扣减库存前对菜品ID加锁。Redis原子操作使用Redis的decr命令预减库存将库存信息提前加载到Redis下单时原子递减订单支付成功后再同步回数据库。订单状态管理混乱订单状态流转必须清晰、严谨。建议在OrderService中定义状态机使用枚举明确状态并提供状态转换方法避免在代码各处随意修改order.setStatus()。支付回调安全性支付回调接口必须验证签名并且要处理幂等性同一笔支付可能收到多次通知。在更新订单状态前先判断当前状态避免重复更新。API接口安全性除了JWT认证敏感操作如支付、修改密码应增加验证码或二次确认。用户输入的参数必须进行校验如使用Valid注解防止SQL注入和XSS攻击。6.3 生产环境部署建议配置外置将数据库密码、JWT密钥、第三方API密钥等敏感信息移出application.yml使用环境变量或配置中心如Nacos、Apollo管理。日志管理使用Logback或Log4j2配置合理的日志级别和滚动策略将日志输出到文件并接入ELK等日志分析系统。监控与健康检查集成Spring Boot Actuator暴露/health,/metrics端点方便监控应用状态。数据库优化根据查询模式建立合适的索引。对于订单这类增长快的表考虑历史数据归档或分表策略。前端分离部署将Vue前端项目打包成静态文件通过Nginx部署并通过Nginx反向代理到后端SpringBoot服务。这样可以减轻后端服务的静态资源压力。容器化部署使用Docker将应用及其依赖打包成镜像通过Docker Compose或K8s编排实现环境一致和快速部署。从学习到生产最大的区别在于对异常、并发、安全、监控和可维护性的考虑。在完成基础功能后应逐步将这些生产级要素融入系统设计中。例如为所有Service方法添加详细的业务日志对可能失败的外部调用如支付、短信设置重试和降级策略设计清晰的全局异常处理返回格式。这个食堂点餐系统项目麻雀虽小五脏俱全深入打磨每一个环节是提升工程能力的最佳途径。