简介基于Spring Boot、MySQL与Vue.js实现的春华秋实咖啡店管理系统是一套面向咖啡店日常运营管理的完整Java后端与Vue前端源码项目适合Java学习者、毕业设计开发者及小型门店数字化管理人员参考。压缩包共59个文件包括38个Java源文件、15个XML配置文件、2个gitignore文件、1个YML配置、1个HTML文件、1个IML及1个txt说明整体仅76KB。Java源码覆盖订单处理、库存管理、顾客信息等核心业务逻辑XML与YML负责Spring Boot及项目运行配置HTML与Vue.js构建前端交互界面Mybatis-plus持久层封装了数据库操作细节并支持用户权限、数据统计与报表生成等扩展能力。已有505人学习下载。资源附带readme.txt辅助说明可导入IntelliJ IDEA配合MySQL直接运行适合对照源码梳理前后端分离结构、理解权限与统计模块实现是快速上手Spring Boot项目开发的实用参考便于二次开发或课程设计复用。1. 一个咖啡店管理系统为什么值得用 Spring Boot Vue 全栈复现一遍看到一个项目标题写着“春华秋实咖啡店管理系统”第一反应是这名字怎么一股杂粮礼盒味。别被名字带偏这套系统拆开就是 Spring Boot 提供接口、MySQL 存账、Vue 渲染页面再加一摞能直接落地的管理源码。对准备毕业设计或课程设计的从业者来说它解决的核心问题不是“做咖啡”而是怎么在一个月内交出一个前后端分离、能演示、能答辩的管理系统。适合两类人想拿现成源码改写成自己课设的和想照着前后端分离架构把全栈流程完整跑一遍的。下面按自己动手复现这套系统的顺序把数据库、后端、前端、联调和答辩改造一次说透。2. 先画架构再建表Spring Boot、MySQL、Vue 各自负责哪一段附可执行建表 SQL2.1 前后端分离的边界谁出接口、谁管渲染、谁存状态先明确一个前提标题里“Spring Boot MySQL Vue”是最常见的中小管理系统组合不需要微服务、不需要 Redis、也不需要消息队列。Spring Boot 负责对外暴露 HTTP 接口、做业务校验和事务控制Vue 只负责把操作页面渲染出来通过 axios 调接口MySQL 落在中间所有状态都持久化到表里。后端接口写完用 Postman 验证前端不用等后端也能用 mock 数据先把页面画出来。很多初学的人拿到源码第一件事是找启动入口结果看到前后端文件堆在一起就懵了。常见做法是拆成两个目录server 放 Spring Boot 工程ui 放 Vue 工程。后端打包成 jar 独立跑在 8080前端本地开发跑在 5173Vite 默认端口生产时前端执行 npm run build 生成 dist 静态文件交给后端托管或用 Nginx 托管并反向代理 /api。这套系统的核心业务链路只有一条员工登录 → 看到商品列表 → 把咖啡加进购物车 → 结算生成订单 → 扣减库存。会员表和员工表是支撑数据报表统计是在订单表上做聚合。把这条链路想清楚后面的表和接口都是顺着长出来的不会乱。所谓“源码设计”设计的就是这张业务网络而不是某个炫技的算法。2.2 五张核心表的设计依据字段、类型、索引怎么定咖啡店管理系统不是大型电商表控制在五张左右最合适员工表、商品表、会员表、订单主表、订单明细表。很多毕设源码喜欢把商品分类单独拆一张表但咖啡店商品通常只有咖啡、甜品、周边三类用字符串字段 category 就够了表多了反而增加答辩时讲不清的风险。每张表字段按“必须字段 业务字段 审计字段”三层来定。例如商品表id、name、price、stock 是必须的category、image_url、status 是业务字段status 用来做上架下架created_at 这类审计字段按需保留。订单表要把“谁买的、买了什么、收了多少钱、当前什么状态”这几件事落到字段上所以 order_no、member_id、user_id、total_amount、status、pay_type 缺一不可。金额一律用 DECIMAL(10,2)不要用 double后面联调章节会细说为什么。索引方面主键 id 自然有索引还要给 order_no 加唯一索引给 t_order.created_at 加普通索引因为报表统计基本都是按天查订单。外键可以不加物理外键用逻辑关联避免删除商品时被订单明细卡住——毕设答辩老师问起来回答“用逻辑外键保证业务灵活性”比解释一大堆约束更省事。2.3 建库建表 SQL复制到 Navicat 就能直接执行先创建一个数据库字符集用 utf8mb4否则 emoji 和生僻字会变问号。下面是完整脚本直接复制到 Navicat 查询窗口执行即可。CREATE DATABASE IF NOT EXISTS coffee_shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE coffee_shop; CREATE TABLE t_user ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL COMMENT BCrypt加密后的密文, real_name VARCHAR(50), role VARCHAR(20) DEFAULT staff COMMENT admin/staff, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_product ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, category VARCHAR(50) COMMENT 咖啡/甜品/周边, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, image_url VARCHAR(255), status TINYINT DEFAULT 1 COMMENT 1上架 0下架, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_member ( id INT AUTO_INCREMENT PRIMARY KEY, phone VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50), points INT DEFAULT 0, level VARCHAR(20) DEFAULT normal, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_order ( id INT AUTO_INCREMENT PRIMARY KEY, order_no VARCHAR(30) NOT NULL UNIQUE, member_id INT, user_id INT COMMENT 操作员工, total_amount DECIMAL(10,2) NOT NULL, pay_type VARCHAR(20) COMMENT cash/wechat/alipay, status VARCHAR(20) DEFAULT pending COMMENT pending/paid/finished, remark VARCHAR(255), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_create_time (created_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_order_item ( id INT AUTO_INCREMENT PRIMARY KEY, order_id INT NOT NULL, product_id INT NOT NULL, product_name VARCHAR(100) COMMENT 下单时商品快照, price DECIMAL(10,2) COMMENT 下单时单价快照, quantity INT NOT NULL, subtotal DECIMAL(10,2) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段脚本里有三个容易被忽略的关键点。一是 t_order_item 里的 product_name 和 price 是快照字段不是关联字段。因为商品价格或名称后期变了历史订单也必须保留下单时的信息这是订单系统的常识也是答辩加分点。二是 t_user.password 存的是 BCrypt 密文不是明文长度留 100 是给加密结果留余量。三是所有表都显式指定 utf8mb4 和 InnoDB如果初始化数据后中文乱码优先查这一步。执行完后可以在 t_product 里插几条美式、拿铁、提拉米苏的测试数据后面前端联调直接用。3. 后端落地Spring Boot 连上 MySQL把商品、订单接口跑通到 Postman 能调3.1 项目骨架搭建pom.xml 依赖与 application.yml 关键参数后端工程我一般直接用 Spring Initializr 生成然后手动加 MyBatis-Plus 依赖。版本组合上最稳妥的是 Spring Boot 2.7.18 MyBatis-Plus 3.5.5JDK 8 和 JDK 11 都能跑不会像 Spring Boot 3 那样强制 JDK 17。如果你机器上只有 JDK 8这个组合是后悔药最少的选择。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies依赖里最容易出问题的是 MySQL 驱动坐标。Spring Boot 2.7.x 较新的版本用 com.mysql:mysql-connector-j老教程里写的 mysql:mysql-connector-java 在部分版本下会拉不到对应驱动类。我用的是前者连接 MySQL 8.x 没有问题。Lombok 可加可不加但几乎所有的毕设源码都用了它实体类不用写一堆 getter setter简洁很多。接着写核心配置 application.yml。这里直接给出我调通的版本。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/coffee_shop?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 jackson: time-zone: GMT8 date-format: yyyy-MM-dd HH:mm:ss mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: autourl 里那几个参数每个都有来头。characterEncodingutf8mb4 保证中文写入正确serverTimezoneAsia/Shanghai 解决日期差 8 小时useSSLfalse 避免连接 MySQL 8 时 SSL 握手报警allowPublicKeyRetrievaltrue 是给 MySQL 8 的 caching_sha2_password 认证用的不加会报 Public Key Retrieval 错误。jackson.time-zone 与 date-format 是让接口返回的日期格式统一成 yyyy-MM-dd HH:mm:ss前端不用再做额外解析。mybatis-plus 的 map-underscore-to-camel-case 打开后数据库字段 order_no 会自动映射到 Java 属性的 orderNo不用写一堆 TableField。3.2 用 MyBatis-Plus 快速写出商品接口和订单接口实体类直接照表结构写。以商品为例Data TableName(t_product) public class Product { TableId(type IdType.AUTO) private Integer id; private String name; private String category; private BigDecimal price; private Integer stock; private String imageUrl; private Integer status; }TableName 指定表名TableId 标注主键并声明自增。字段名 orderNo 由下划线自动转驼峰对应 order_no不需要额外注解。Mapper 层写一个接口继承 BaseMapper 就有常用的增删改查了public interface ProductMapper extends BaseMapperProduct { }Controller 里写商品列表接口这是前后端第一次联调的标志性接口RestController RequestMapping(/api/product) public class ProductController { Autowired private ProductMapper productMapper; GetMapping(/list) public Result list() { ListProduct list productMapper.selectList( new LambdaQueryWrapperProduct() .eq(Product::getStatus, 1) .orderByAsc(Product::getCategory)); return Result.ok(list); } }selectList 配合 LambdaQueryWrapper 是 MyBatis-Plus 最常用的查询姿势.eq 表示 status 等于 1 的上架商品.orderByAsc 按分类排序让同类咖啡挨在一起。Result 是一个统一返回体我一般定义成 code、msg、data 三个字段code 为 0 表示成功。Postman 访问 http://localhost:8080/api/product/list 能看到 JSON 数组了说明 Spring Boot 到 MySQL 这条链路已经通了。订单创建接口是这套系统的核心稍微复杂一点要同时处理订单主表和明细表并扣减库存必须加事务。下面是一个精简但能跑的版本PostMapping(/save) Transactional(rollbackFor Exception.class) public Result save(RequestBody OrderDTO dto) { BigDecimal total BigDecimal.ZERO; // 第一步遍历明细计算总额 for (OrderItemDTO item : dto.getItems()) { Product product productMapper.selectById(item.getProductId()); if (product null || product.getStatus() ! 1) { return Result.error(商品不存在或已下架); } BigDecimal subtotal product.getPrice() .multiply(BigDecimal.valueOf(item.getQuantity())); total total.add(subtotal); } // 第二步生成订单主表 Order order new Order(); order.setOrderNo(UUID.randomUUID().toString().replace(-, ).substring(0, 20)); order.setMemberId(dto.getMemberId()); order.setTotalAmount(total); order.setPayType(dto.getPayType()); order.setStatus(paid); orderMapper.insert(order); // 第三步写明细并扣库存 for (OrderItemDTO item : dto.getItems()) { Product product productMapper.selectById(item.getProductId()); OrderItem orderItem new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setProductId(product.getId()); orderItem.setProductName(product.getName()); orderItem.setPrice(product.getPrice()); orderItem.setQuantity(item.getQuantity()); orderItem.setSubtotal(product.getPrice() .multiply(BigDecimal.valueOf(item.getQuantity()))); orderItemMapper.insert(orderItem); product.setStock(product.getStock() - item.getQuantity()); productMapper.updateById(product); } return Result.ok(order.getId()); }Transactional 是整个方法的关键任何一步抛异常都会整体回滚不会出现订单写入了库存却没扣的脏数据。明细里的 productName 和 price 从库里查出来再写进快照字段而不是信任前端传的值——这是给“前端改价提交”这种低级攻击留的防线。orderNo 用 UUID 截取 20 位够用答辩时如果被问为什么不用自增订单号可以说这是为了避免订单号被猜到。3.3 后端接口设计里值得注意的约定后端接口统一以 /api 开头这个前缀在联调阶段意义巨大前端代理和后端跨域配置都围绕它做后续部署到 Nginx 时只需要转发这一个路径。Controller 里的方法命名按业务动作走list、save、delete、dashboard不要写成 do1、do2 这种答辩时说不清的名字。分页查询建议用 MyBatis-Plus 自带的分页插件传 pageNum 和 pageSize 两个参数。咖啡店系统的商品和订单量都不大但答辩老师很爱问“数据量大了怎么办”能说出分页、索引、逻辑外键这三个词比在代码里堆一堆设计模式更打动人。4. 前端落地用 Vue 实现点单页、购物车和结算Vite 代理打通 /api4.1 用 Vite 创建 Vue 工程并配好开发代理前端部分我推荐 Vue 3 Vite。很多人拿到老毕设源码会看到 Vue 2 的写法this.$axios 满天飞项目能跑但改造空间小。如果你是想拿这套源码做课设Bootstrap 的经典写法改起来更省事如果想顺便练一下组合式 API新建一个 Vue 3 工程把这套源码的接口重新接一遍收获会大很多。下面按 Vue 3 走。npm create vitelatest coffee-ui -- --template vue cd coffee-ui npm install npm install axios npm run devVite 创建的项目默认跑在 5173 端口直接访问后端 8080 会触发跨域。开发环境最简单可靠的方案是在 vite.config.js 里配置代理。import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这段配置的含义是页面里请求 /api/product/list 时Vite 的开发服务器会把它转发到 http://localhost:8080/api/product/list。浏览器看到的请求是同源的跨域问题在开发阶段被代理解掉了。changeOrigin 设置为 true 是因为后端如果做了域名校验转发后 Host 头会变成目标地址不然某些严格配置下会被后端拒绝。这个方案只对本地开发有效生产部署时还要把这段逻辑搬到 Nginx 或后端静态托管里。4.2 点单页的数据流商品列表、购物车、结算按钮先封装 axios。所有接口请求都通过同一个实例发出baseURL 统一带上 /api超时设置 10 秒响应拦截器直接取出 data 包页面代码可以少写一层 res.data.data 的嵌套。// src/api/request.js import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.response.use( res res.data, err Promise.reject(err) ) export default request点单页的核心是商品列表加载和购物车状态。这一段用一个简化的单文件组件说明数据流script setup import { ref } from vue import request from ../api/request const products ref([]) const cart ref([]) const loadProducts async () { const res await request.get(/product/list) products.value res.data } const addToCart (item) { const existed cart.value.find(c c.id item.id) if (existed) { existed.quantity } else { cart.value.push({ ...item, quantity: 1 }) } } loadProducts() /scriptref 是 Vue 3 组合式 API 的响应式容器products.value 和 cart.value 的改变会自动触发页面更新。addToCart 里先找购物车里有没有同一商品有则数量加一没有则把商品对象展开并补一个 quantity 字段。这里没有用 Vuex 或 Pinia因为购物车状态只在点单页内部流转不跨页面共享用 ref 就能满足。如果将来要做“购物车角标跨页面同步”再引入 Pinia 不迟。商品列表渲染部分直接在模板里 v-for 遍历 products每个卡片显示名称、价格、库存和“加入购物车”按钮。注意商品图片字段 image_url 可能为空模板里要用 v-if 判断否则会出现一排破图图标观感很差。4.3 结算接口对接把购物车 POST 给后端结算按钮触发 submitOrder把购物车里的数据映射成后端需要的 JSON 结构然后 POST 到 /order/save。这里最容易出错的地方是字段名。const submitOrder async () { const payload { payType: wechat, remark: remark.value, memberId: 1, items: cart.value.map(c ({ productId: c.id, quantity: c.quantity })) } const res await request.post(/order/save, payload) if (res.code 0) { cart.value [] alert(下单成功) } }前端只传 productId 和 quantity价格、名称、快照都由后端重新从库查这是上一章说过的安全约定。后端 OrderDTO 里的字段是 payType、remark、memberId、items前端 payload 里必须完全一致大小写也不能差。如果后端收不到字段返回的异常信息或日志里会暴露提示再多调试也无济于事——联调阶段接口报错时要养成的第一个习惯是看后端控制台日志不是盯前端浏览器。接口通了之后可以顺手在后端控制台看到 MyBatis-Plus 打印的 SQL 语句。这些日志是排查问题的第一现场例如可以清楚看到 order_item 表插入了哪些字段值。如果发现某个字段是 null对着 SQL 去查 DTO 和实体的字段名比猜快得多。5. 联调避坑前后端对接最容易翻车的 5 个细节及排查顺序5.1 跨域拦截开发环境代理与后端 CORS 双保险现象前端访问 http://localhost:8080/api/product/list 时浏览器控制台报 “Access-Control-Allow-Origin” 相关错误Network 面板里请求状态为 failed 或 CORS error。原因5173 和 8080 是两个不同源浏览器默认拦截跨源请求。如果你跳过了 Vite 代理直接调后端地址就会触发这个拦截如果配了代理还报错多半是代理没生效配置文件改完没有重启 dev server。解决开发环境按上一章的方式配 Vite proxy同时在后端加一个 CORS 配置类双保险。很多人以为前端代理配了就万事大吉但打包部署到 Nginx 后如果后端换地址代理迁移没跟上依然会翻车。后端配置如下Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(http://localhost:5173) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); } }allowedOrigins 写的是前端开发服务器地址上线时改成线上域名。allowCredentials(true) 表示允许携带 Cookie如果系统里做了登录状态保持这行是必须的。maxAge 3600 表示预检请求结果缓存一小时减少 OPTIONS 请求次数。5.2 MySQL 时间差 8 小时从连接串到 JSON 序列化的统一时区现象数据库里 created_at 显示是 14:30前端页面上却显示 06:30或者后端查询出来的时间比实际早 8 小时。原因时区配置链路断了。MySQL 连接串里的 serverTimezone 没设或设成 UTCJava 拿到的时间就不对就算数据库读对了Jackson 序列化时用的也是 JVM 默认时区两边不一致就会错乱。这是一个典型的“黑匣子”问题数据库、JDBC、Jackson 三个环节任何一环掉链子最终表现都是时间不对。解决按第 3 章配置来连接串固定带 serverTimezoneAsia/Shanghaiapplication.yml 里固定配置 jackson.time-zoneGMT8 和 date-formatyyyy-MM-dd HH:mm:ss。有时候你改了配置发现还是不对检查一下数据库连接的驱动版本旧版本驱动对 Asia/Shanghai 的解析会有问题升级到 com.mysql:mysql-connector-j 即可。数据库这边还可以直接查一下 session 时区用 SELECT session.time_zone 确认避免排查走弯路。5.3 金额精度double 算账翻车BigDecimal 与 DECIMAL 才是对的现象两件 9.9 元的美式咖啡结算总价显示 19.800000000000004 之类的一长串小数或者和数据库里的金额对不上。原因double 是二进制浮点0.1 在二进制里是无限循环小数累加必然产生精度误差。数据库表用 DECIMAL(10,2) 只是存储精度正确Java 实体类如果用了 Double 类型计算过程照样翻车。解决实体和 DTO 里金额字段一律用 BigDecimal数据库一律用 DECIMAL(10,2)。做加法乘法时用 add 和 multiply 方法不要用 和 *。注意 BigDecimal 构造时要用 BigDecimal.valueOf 或字符串构造直接 new BigDecimal(0.1) 会把 double 的误差带进来。BigDecimal subtotal product.getPrice() .multiply(BigDecimal.valueOf(item.getQuantity())); BigDecimal total total.add(subtotal);我的习惯是只要涉及钱就把它当作和一个布尔值一样严格的类型对待。前端展示金额时也要注意从接口拿到的可能是字符串直接做算术会出现字符串拼接效果必要时用 parseFloat 转一下再做展示但最终以 BigDecimal 为准。5.4 JSON 字段对不上productId 收不到、orderId 是 null 的排查思路现象前端 POST 请求发出去了后端接口也进来了但 DTO 里的 productId、memberId 等字段全为 null或者前端拿到订单列表后 orderId 显示 undefined。原因绝大多数是字段名不匹配。前端对象里写了 product_id后端 DTO 里是 productId或者后端返回的实体属性叫 order_no没开驼峰映射前端用 res.data.orderNo 取取不到。MyBatis-Plus 的下划线转驼峰只负责数据库字段到 Java 属性的映射不负责 JSON 序列化和反序列化的字段匹配。解决先抓后端的请求日志确认 JSON 实际长什么样再对着 DTO 字段逐一核对。最好的约定是前后端统一用驼峰这个约定要在第 4 章封装 axios 时就跟前端同事讲清楚。在 Vue 项目里如果第三方接口用下划线风格无力改变可以用 axios 的 transformRequest 把 key 转换一遍但自己做的系统没必要加这一层。5.5 打包部署后刷新 404history 路由的坑与最省事解法现象本地开发一切正常把前端 build 出来的 dist 文件夹放进后端 resources/static 或交给 Nginx 后首页能打开但一刷新某个子页面就 404。原因Vue Router 默认用 history 模式路由地址如 /order/list 是前端路由服务器上并不存在这个物理文件。刷新时浏览器向服务器请求 /order/list服务器找不到对应资源就返回了 404。这在毕设答辩现场是社死级别的问题。解决两个方案二选一。最省事的是把 createWebHistory 改成 createWebHashHistory路由地址变成 /#/order/list刷新时永远不会请求一个不存在的路径代价是地址栏带个 # 号。如果想要干净的地址后端要写一个路径转发把所有非 /api 开头的 GET 请求转发到 index.html用 Nginx 的话配 try_files 规则实现同样的效果。我的建议是答辩项目用房最省事的 hash 方案把风险控制在零地址栏好不好看不重要现场不翻车才重要。6. 把源码改成自己的毕业设计改造清单与十分钟验收演示顺序6.1 必改清单从标题栏到数据库名的替换操作拿到源码后原样的系统名“春华秋实”和你自己的课设题目大概率不一致。要改的不只是首页一个大标题按下面清单过一遍避免答辩 PPT 和系统界面穿帮。改动位置推荐改法浏览器标题ui 目录 index.html 里的 title 标签登录页与侧边栏搜索源码中的“春华秋实”字符串全局替换数据库名把 coffee_shop 改成你的项目名例如 campus_cafe注意修改 application.yml 里的连接地址前端请求地址前缀如果后台改了 context-path代理配置同步修改系统主色调找到全局 CSS 里的主色变量或按钮主题色换成与你题目相符的配色我不建议把数据库表前缀 t_ 也全部改掉表名改动会牵连所有实体注解和 SQL 脚本风险远大于收益。答辩老师关注的是业务是否讲得通不是表名前缀是不是你的项目缩写。但如果课设题目是“校园咖啡店”把商品表里加一个 campus 相关字段比如 sale_point、window_id并让报表页展示出来这比改表名更有说服力。6.2 答辩当天十分钟验收演示顺序演示顺序直接影响答辩分数我的习惯是严格按业务流程走不给老师打断问“这个是什么”的机会。第一步启动 MySQL 并确认服务端口运行建表脚本第二步启动后端观察控制台输出 Tomcat started on port 8080第三步启动前端 dev server打开浏览器。全程大约三分钟。接下来按业务讲先登录说明密码用 BCrypt 加密再展示商品列表页点一个商品加进购物车加两件后结算选支付方式后提交订单立刻切到订单列表页展示新订单出现再切回商品列表演示库存相应减少。如果系统里有库存预警就挑一个低于阈值的商品展示它的高亮状态最后可以打开会员页面说明下单时绑定会员后积分变动的规则。整个过程控制在七分钟内剩下时间留给老师提问。结尾想分享一个教训我第一次做类似系统时答辩前一天才想起来数据库导出脚本没带上演示当天换机器直接连不上库全程在调试环境。从那以后我的交付清单里永远有一条——换一台干净机器从头跑一遍搭建流程确认建库脚本、后端、前端三个环节能一次性启动成功。这套咖啡店管理系统不算难真正值钱的是你把这条全栈链路跑通的经验以及踩过坑之后记住的每一个错误信息。希望帮到你。本文还有配套的精品资源点击获取