ARTICLE DETAIL

资讯详情

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

Spring Boot + MySQL + Vue 咖啡店管理系统全栈源码实战:从建库到打包部署

Spring Boot + MySQL + Vue 咖啡店管理系统全栈源码实战:从建库到打包部署 简介这是一套面向Java全栈学习者与中小型咖啡店数字化需求的春华秋实咖啡店管理系统源码采用Spring Boot、MyBatis-Plus、MySQL与Vue.js技术栈适合作为课程设计、毕业设计或企业级后台管理项目的参考案例。压缩包共59个文件约76KB其中Java源文件38个承载后端业务逻辑XML配置15个负责框架与数据源管理另有yml、html、iml及gitignore等文件分别对应Spring Boot配置、前端页面、IDE工程与版本控制结构紧凑、层次分明。系统围绕订单处理、库存管理、顾客信息维护等核心场景展开并预留权限管理与数据统计的扩展空间可帮助读者理解前后端分离架构下接口设计与持久层封装思路。目前已有505人学习下载适合希望快速掌握Spring Boot与Vue整合开发、需要完整可运行项目骨架的开发者参考借鉴。1. 春华秋实咖啡店管理系统一套能跑通的 Spring Boot MySQL Vue 全栈源码长什么样很多做 Java 课程设计或接私活的朋友拿到「基于 Spring Boot MySQL Vue 的咖啡店管理系统源码」这类标题时第一反应是去搜现成压缩包结果下下来一堆跑不起来的半成品后端接口对不上、前端跨域报错、数据库字段缺一半。我前阵子帮一个开咖啡店的朋友做内部点单和库存管理顺手把这套技术栈从零搭了一遍发现真正能落地的关键不在代码量而在三件事——表结构设计、前后端接口约定、以及 Vue 打包后怎么塞进 Spring Boot 一起部署。这篇笔记就按这个顺序把「咖啡店管理系统」从建库到联调再到打包上线的完整路径拆开讲适合有 Java 和 Vue 基础、想拿一套能演示能交付的全栈项目练手的同学。源码结构我会给出目录约定和核心文件写法你照着改就能变成自己的东西。2. 先定业务边界咖啡店管理系统到底要管哪几张表2.1 从点单场景倒推功能模块咖啡店和普通电商最大的区别是商品有规格中杯/大杯/加糖/去冰订单高频且客单价低库存消耗和配方挂钩。所以别一上来就抄电商的 SPU/SKU 那套容易过度设计。我一般把系统拆成五个模块用户与权限、商品与分类、订单与支付状态、库存与原料、统计报表。对应到数据库核心表控制在 8 张以内新手维护起来不痛苦。具体表清单如下字段类型我按 MySQL 8.0 的习惯给表名用途关键字段sys_user员工/管理员账号id, username, password, role, statuscategory商品分类id, name, sortproduct咖啡/甜点id, category_id, name, price, image, statusproduct_spec规格杯型/冷热id, product_id, spec_name, extra_priceorders订单主表id, order_no, user_id, total_amount, pay_status, create_timeorder_item订单明细id, order_id, product_id, spec_id, quantity, pricematerial原料库存id, name, stock, unit, warn_stockmaterial_log库存流水id, material_id, change_num, type, create_time建表时有两个细节容易翻车一是orders的order_no别用自增 id 直接暴露用时间戳加随机数生成二是金额字段统一用DECIMAL(10,2)别用 float否则统计报表会出现 0.30000000000000004 这种玄学数字。2.2 用 SQL 脚本一次性初始化把建表和初始数据写成一个init.sql方便换电脑时一键恢复。下面是我常用的写法注意字符集和引擎-- 建库字符集用 utf8mb4 支持 emoji咖啡店名字可能带符号 CREATE DATABASE coffee_shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE coffee_shop; -- 商品表status 用 1 上架 0 下架 CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, name VARCHAR(64) NOT NULL, price DECIMAL(10,2) NOT NULL DEFAULT 0.00, image VARCHAR(255) DEFAULT , status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单主表order_no 唯一索引防止重复提交 CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, pay_status TINYINT DEFAULT 0 COMMENT 0待支付 1已支付 2已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 插入测试分类和商品 INSERT INTO category (name, sort) VALUES (经典咖啡, 1), (果茶, 2), (甜点, 3); INSERT INTO product (category_id, name, price) VALUES (1, 美式, 15.00), (1, 拿铁, 18.00), (2, 满杯百香果, 16.00);执行方式命令行mysql -u root -p init.sql或者在 Navicat 里直接运行。参数上唯一要改的是数据库名和字符集如果你本地 MySQL 是 5.7utf8mb4_general_ci也支持但DEFAULT CURRENT_TIMESTAMP在 5.7 的 DATETIME 上需要显式写DEFAULT CURRENT_TIMESTAMP才生效8.0 没这个问题。这一步做完用SHOW TABLES;能看到 8 张表就算过了。3. Spring Boot 后端接口分层与 MyBatis 映射的落地写法3.1 项目结构与依赖选型后端我用 Spring Boot 2.7 MyBatis-Plus不引 JPA。原因很直接咖啡店管理系统的查询条件杂按时间、按分类、按支付状态MyBatis 写动态 SQL 更顺手而且 MyBatis-Plus 的LambdaQueryWrapper能省掉大量样板代码。pom.xml核心依赖就四个dependencies !-- web 提供 REST 接口 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- mybatis-plus 简化单表 CRUD -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency !-- mysql 驱动8.0 用 com.mysql.cj.jdbc.Driver -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- lombok 省 getter/setter -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependenciesapplication.yml里数据源和端口这样配server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/coffee_shop?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 mybatis-plus: configuration: map-underscore-to-camel-case: true # 数据库下划线转 Java 驼峰 global-config: db-config: logic-delete-field: deleted # 逻辑删除字段可选serverTimezone必须写否则连接 MySQL 8 会报时区错误这是新手最常见的翻车点之一。3.2 订单接口的 Controller 与 Service 写法以「创建订单」为例前端传过来的是商品 id、规格 id、数量后端要算总价、扣库存、生成订单号。Controller 只做参数校验和转发业务逻辑放 ServiceRestController RequestMapping(/api/order) public class OrderController { Autowired private OrderService orderService; // 创建订单接收前端 JSON PostMapping(/create) public Result create(RequestBody OrderCreateDTO dto) { // dto 里包含 userId、items 列表 if (dto.getItems() null || dto.getItems().isEmpty()) { return Result.fail(订单不能为空); } String orderNo orderService.createOrder(dto); return Result.ok(orderNo); } }Service 实现里生成订单号用System.currentTimeMillis()加三位随机数扣库存用UPDATE material SET stock stock - ? WHERE id ? AND stock ?靠数据库行锁保证不超卖。这里有个参数要留意Transactional注解必须加在 Service 方法上且异常要抛出RuntimeException才会回滚捕获后不抛等于白加。Service public class OrderServiceImpl implements OrderService { Autowired private OrdersMapper ordersMapper; Autowired private MaterialMapper materialMapper; Override Transactional(rollbackFor Exception.class) public String createOrder(OrderCreateDTO dto) { String orderNo System.currentTimeMillis() String.format(%03d, new Random().nextInt(1000)); BigDecimal total BigDecimal.ZERO; for (OrderItemDTO item : dto.getItems()) { // 查商品价格累加总价 Product p productMapper.selectById(item.getProductId()); total total.add(p.getPrice().multiply(new BigDecimal(item.getQuantity()))); // 扣原料库存影响行数为 0 说明库存不足 int rows materialMapper.reduceStock(item.getMaterialId(), item.getQuantity()); if (rows 0) { throw new RuntimeException(原料库存不足); } } Orders order new Orders(); order.setOrderNo(orderNo); order.setUserId(dto.getUserId()); order.setTotalAmount(total); ordersMapper.insert(order); return orderNo; } }这段代码的逻辑说明先循环明细算总价并扣库存任何一步失败抛异常触发回滚最后插入订单主表。参数上reduceStock的 SQL 要写成stock #{num}的条件更新不能先查再改否则并发下会超卖。3.3 跨域配置与统一返回格式前端 Vue 跑在 5173 或 8081 端口后端 8080浏览器会拦跨域。最省事的做法是加一个全局配置类别在每个 Controller 上写CrossOriginConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOriginPatterns(*) // 开发阶段放开上线改成具体域名 .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); } }统一返回格式用ResultT类包含code、msg、data三个字段前端判断code 200再取 data。这样前端拦截器写起来简单不用每个接口单独处理。4. Vue 前端从路由到接口封装的完整链路4.1 环境搭建与项目初始化Vue 我用 Vue 3 Vite Element Plus比 Vue 2 的 webpack 快很多。初始化命令npm create vitelatest coffee-admin -- --template vue cd coffee-admin npm install npm install element-plus axios vue-router pinia npm run dev装完后package.json里会有vue、vue-router、axios依赖。Vite 默认端口 5173如果被占用会自动换看终端输出即可。这里有个坑Node 版本建议 16 以上低于 14 装 Vite 会报crypto.getRandomValues相关错误。4.2 路由与页面结构咖啡店管理系统的页面不多路由表控制在 6 个以内登录、点单收银、商品管理、订单列表、库存管理、数据看板。router/index.js写法import { createRouter, createWebHistory } from vue-router const routes [ { path: /login, component: () import(../views/Login.vue) }, { path: /, component: () import(../views/Layout.vue), redirect: /order, children: [ { path: order, component: () import(../views/Order.vue) }, { path: product, component: () import(../views/Product.vue) }, { path: stock, component: () import(../views/Stock.vue) } ] } ] const router createRouter({ history: createWebHistory(), routes }) // 全局前置守卫没登录跳登录页 router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path ! /login !token) { next(/login) } else { next() } }) export default routercreateWebHistory是 history 模式打包部署到 Spring Boot 后刷新页面会 404解决办法见第 6 章。开发阶段用这个模式没问题。4.3 Axios 封装与接口调用所有请求走一个request.js统一加 token、统一处理错误码import axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: /api, // 开发时配合 vite 代理 timeout: 5000 }) // 请求拦截器带上 token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization token } return config }) // 响应拦截器code 非 200 弹提示 request.interceptors.response.use( res { if (res.data.code ! 200) { ElMessage.error(res.data.msg || 请求失败) return Promise.reject(res.data) } return res.data.data }, err { ElMessage.error(网络异常) return Promise.reject(err) } ) export default requestvite.config.js里配代理解决开发跨域export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这样前端写request.get(/order/list)实际请求的是http://localhost:8080/api/order/list和后端接口路径对上。参数上changeOrigin: true必须加否则后端拿到的 Host 还是前端地址某些鉴权逻辑会出错。5. 避坑与排查这套技术栈最容易翻车的五个地方5.1 现象启动报Access denied for user rootlocalhost原因application.yml里的密码和本地 MySQL 实际密码不一致或者 MySQL 8 的 root 用户默认用caching_sha2_password插件旧驱动连不上。解决先确认密码再用ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 新密码;改插件最后FLUSH PRIVILEGES;。驱动版本用 8.0.33 以上基本不用改插件但 5.x 驱动必改。5.2 现象前端请求返回 404但浏览器 Network 里 URL 是对的原因Vite 代理没生效或者后端接口路径多了/少了/api前缀。解决检查vite.config.js的proxy配置是否在server下改完必须重启npm run dev热更新不会重载代理配置。另外确认 Controller 上的RequestMapping和前端baseURL拼起来是否一致。5.3 现象订单金额统计出现小数位异常原因金额字段用了FLOAT或DOUBLE。解决建表时改成DECIMAL(10,2)Java 里用BigDecimal且BigDecimal比较用compareTo不用equals。这个坑我在两个项目里都踩过改表结构最彻底。5.4 现象Vue 打包后放 Spring Boot 里刷新页面 404原因Vue Router 用了 history 模式Spring Boot 没有对应的转发规则。解决在 Spring Boot 里加一个配置把所有非/api开头的请求转发到index.htmlConfiguration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/**) .addResourceLocations(classpath:/static/); } }同时前端路由改成createWebHashHistory最省事URL 带#刷新不会 404。如果坚持 history 模式需要额外写ErrorController或 Nginx 的try_files。5.5 现象MyBatis-Plus 查询返回字段全是 null原因数据库字段是下划线命名create_timeJava 实体是驼峰createTime但没开自动映射。解决application.yml里加mybatis-plus.configuration.map-underscore-to-camel-case: true或者实体字段上加TableField(create_time)。前者一劳永逸。6. 打包部署与一个提效技巧把 Vue 塞进 Spring Boot 的 jar6.1 前端打包与静态资源合并前端npm run build后生成dist目录里面是index.html和assets。把这俩直接复制到 Spring Boot 的src/main/resources/static/下Spring Boot 默认就会把static当静态资源根目录。注意vite.config.js里要设base: ./否则打包后资源路径是绝对路径/assets/xxx.js放子目录会 404。// vite.config.js 生产环境配置 export default defineConfig({ base: ./, build: { outDir: dist, assetsDir: assets } })6.2 用 Maven 插件一键打包在pom.xml里加spring-boot-maven-plugin然后mvn clean package生成可执行 jar。启动命令java -jar coffee-admin-0.0.1-SNAPSHOT.jar浏览器访问http://localhost:8080就能看到前端页面。这里有个验证方法打包后先本地跑一遍点单、加商品、看库存全流程走通再交付。6.3 一个我常用的提效习惯每次改完数据库字段我会先跑一句SHOW CREATE TABLE 表名;把最新结构贴到项目根目录的db.md里再同步改实体类和前端表单。这个习惯帮我省掉了至少三次「字段对不上导致接口 500」的排查时间。另外接口联调阶段我会在 Postman 里存一份集合前端报错时先跑 Postman能快速定位是后端问题还是前端传参问题。这套 Spring Boot MySQL Vue 的组合难点从来不是写代码而是把三层的约定对齐——表结构、接口路径、字段命名对齐了剩下的就是体力活。希望帮到你。本文还有配套的精品资源点击获取
返回列表