SpringBoot+Vue3实战:从零搭建一个咖啡店后台管理系统(附完整源码和数据库设计)
SpringBoot+Vue3实战:从零搭建咖啡店后台管理系统
咖啡店后台管理系统是现代餐饮企业数字化转型的核心工具。想象一下,当你走进一家精品咖啡店,店员用平板电脑快速记录你的订单,后台实时更新库存,经理通过手机查看当日销售报表——这一切流畅体验的背后,都离不开一个健壮的后台管理系统。本文将带你从零开始,用SpringBoot和Vue3构建一个功能完备的咖啡店管理系统,涵盖从技术选型到部署上线的全流程。
1. 技术栈选型与环境搭建
1.1 为什么选择SpringBoot+Vue3组合
SpringBoot和Vue3的组合已经成为现代全栈开发的主流选择:
后端优势:
- SpringBoot的自动配置减少了XML配置
- 内嵌Tomcat简化部署
- 丰富的starter依赖快速集成常用功能
- 完善的生态和社区支持
前端优势:
- Vue3的Composition API提供更好的代码组织
- 更小的打包体积和更快的渲染速度
- TypeScript支持更友好
- Pinia状态管理比Vuex更简洁
1.2 开发环境准备
后端环境:
# 使用SDKMAN安装JDK sdk install java 17.0.3-tem # 验证安装 java -version前端环境:
# 使用nvm管理Node版本 nvm install 16.14.2 nvm use 16.14.2 # 安装Vue CLI npm install -g @vue/cli数据库选择: 对于咖啡店管理系统,MySQL 8.0是最佳选择:
| 特性 | MySQL 5.7 | MySQL 8.0 |
|---|---|---|
| JSON支持 | 基础 | 完善 |
| 性能 | 一般 | 提升40% |
| 窗口函数 | 无 | 支持 |
| 默认字符集 | latin1 | utf8mb4 |
提示:开发阶段可以使用Docker快速启动MySQL容器,避免污染本地环境。
2. 数据库设计与领域建模
2.1 核心表结构设计
咖啡店业务的核心是订单、产品和库存管理。我们设计了以下主要表结构:
产品表(products):
CREATE TABLE `products` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `name` VARCHAR(100) NOT NULL, `category` ENUM('咖啡','茶饮','甜点','轻食') NOT NULL, `price` DECIMAL(10,2) NOT NULL, `cost` DECIMAL(10,2) NOT NULL, `description` TEXT, `image_url` VARCHAR(255), `is_active` BOOLEAN DEFAULT TRUE, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;订单系统设计要点:
- 采用主订单+订单项的两级结构
- 支持多种支付方式
- 记录完整的操作日志
2.2 库存管理策略
咖啡店库存管理需要特别关注:
原料库存:
- 咖啡豆、牛奶、糖浆等
- 需要设置最低库存预警
成品库存:
- 预包装食品
- 保质期管理
库存变动类型:
- 采购入库
- 销售出库
- 报损出库
- 调拨
// 库存扣减示例代码 @Transactional public void deductInventory(Long productId, int quantity) { Inventory inventory = inventoryRepository.findByProductId(productId) .orElseThrow(() -> new BusinessException("库存记录不存在")); if (inventory.getQuantity() < quantity) { throw new BusinessException("库存不足"); } inventory.setQuantity(inventory.getQuantity() - quantity); inventoryRepository.save(inventory); // 记录库存变动日志 InventoryLog log = new InventoryLog(); log.setProductId(productId); log.setQuantity(-quantity); log.setType("SALE"); inventoryLogRepository.save(log); }3. 后端API开发实战
3.1 订单模块实现
订单是咖啡店系统的核心模块,我们采用DDD分层架构:
com.cafe.order ├── application │ ├── OrderService.java │ └── OrderQueryService.java ├── domain │ ├── Order.java │ ├── OrderItem.java │ └── OrderStatus.java └── infrastructure ├── OrderRepository.java └── OrderJpaRepository.java创建订单API设计:
@PostMapping("/orders") public ResponseEntity<OrderDTO> createOrder( @Valid @RequestBody CreateOrderCommand command) { // 验证产品是否存在且可用 List<Long> productIds = command.getItems().stream() .map(OrderItemCommand::getProductId) .collect(Collectors.toList()); Map<Long, Product> products = productService.getProductsByIds(productIds) .stream() .collect(Collectors.toMap(Product::getId, Function.identity())); // 构建订单聚合根 Order order = new Order(); order.setCustomerId(command.getCustomerId()); order.setStatus(OrderStatus.CREATED); // 添加订单项 for (OrderItemCommand itemCmd : command.getItems()) { Product product = products.get(itemCmd.getProductId()); if (product == null || !product.isActive()) { throw new ProductNotFoundException(itemCmd.getProductId()); } OrderItem item = new OrderItem(); item.setProductId(product.getId()); item.setProductName(product.getName()); item.setUnitPrice(product.getPrice()); item.setQuantity(itemCmd.getQuantity()); order.addItem(item); } // 计算总价 order.calculateTotal(); // 保存订单 Order savedOrder = orderRepository.save(order); // 返回DTO return ResponseEntity.ok(OrderMapper.INSTANCE.toDTO(savedOrder)); }3.2 权限控制设计
咖啡店系统通常需要以下角色:
- 管理员:全系统权限
- 店长:门店管理、报表查看
- 咖啡师:订单处理、库存查看
- 收银员:收银、基础查询
我们使用Spring Security + JWT实现权限控制:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**").permitAll() .antMatchers("/api/admin/**").hasRole("ADMIN") .antMatchers("/api/reports/**").hasAnyRole("ADMIN", "MANAGER") .antMatchers(HttpMethod.POST, "/api/orders").hasAnyRole("BARISTA", "CASHIER") .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } }4. 前端Vue3实现
4.1 项目初始化与架构
使用Vue CLI创建项目:
vue create cafe-admin-frontend选择特性:
- TypeScript
- Vuex → Pinia
- Router
- CSS Pre-processors (Sass)
目录结构优化:
src/ ├── api/ # API请求封装 ├── assets/ # 静态资源 ├── components/ # 通用组件 ├── composables/ # 组合式函数 ├── router/ # 路由配置 ├── stores/ # Pinia状态管理 ├── types/ # TypeScript类型定义 ├── utils/ # 工具函数 ├── views/ # 页面组件 └── main.ts # 应用入口4.2 订单管理页面实现
使用Composition API和TypeScript实现订单列表:
<script setup lang="ts"> import { ref, onMounted } from 'vue' import { useOrderStore } from '@/stores/order' import type { Order } from '@/types' const orderStore = useOrderStore() const orders = ref<Order[]>([]) const isLoading = ref(false) const pagination = ref({ page: 1, pageSize: 10, total: 0 }) const fetchOrders = async () => { isLoading.value = true try { const res = await orderStore.fetchOrders({ page: pagination.value.page, size: pagination.value.pageSize }) orders.value = res.data pagination.value.total = res.total } finally { isLoading.value = false } } onMounted(() => { fetchOrders() }) </script> <template> <div class="order-list"> <el-table :data="orders" v-loading="isLoading"> <el-table-column prop="orderNumber" label="订单号" width="180" /> <el-table-column prop="customerName" label="顾客" /> <el-table-column prop="totalAmount" label="总金额" align="right"> <template #default="{ row }"> ¥{{ (row.totalAmount / 100).toFixed(2) }} </template> </el-table-column> <el-table-column prop="status" label="状态"> <template #default="{ row }"> <el-tag :type="statusTagType(row.status)"> {{ formatStatus(row.status) }} </el-tag> </template> </el-table-column> </el-table> <el-pagination v-model:current-page="pagination.page" :page-size="pagination.pageSize" :total="pagination.total" @current-change="fetchOrders" /> </div> </template>4.3 实时数据看板
使用ECharts实现销售数据可视化:
<script setup lang="ts"> import { ref, onMounted } from 'vue' import * as echarts from 'echarts' import { useReportStore } from '@/stores/report' const reportStore = useReportStore() const chartRef = ref<HTMLDivElement>() onMounted(async () => { const salesData = await reportStore.getDailySales() const chart = echarts.init(chartRef.value!) const option = { title: { text: '当日销售趋势' }, tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: salesData.map(item => item.hour + ':00') }, yAxis: { type: 'value', name: '销售额(元)' }, series: [{ data: salesData.map(item => item.amount), type: 'line', smooth: true, areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: 'rgba(58, 77, 233, 0.8)' }, { offset: 1, color: 'rgba(58, 77, 233, 0.1)' } ]) } }] } chart.setOption(option) // 响应式调整 window.addEventListener('resize', () => chart.resize()) }) </script> <template> <div ref="chartRef" style="width: 100%; height: 400px;"></div> </template>5. 系统集成与部署
5.1 前后端联调要点
跨域问题是前后端分离项目常见挑战:
后端解决方案:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://localhost:8080") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }前端代理配置(vue.config.js):
module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '' } } } } }5.2 生产环境部署
后端部署方案:
- 使用Docker容器化:
FROM openjdk:17-jdk-slim ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT ["java","-jar","/app.jar"]- 构建并运行:
mvn clean package docker build -t cafe-backend . docker run -d -p 8080:8080 --name cafe-backend cafe-backend前端部署方案:
- 静态资源构建:
npm run build- Nginx配置示例:
server { listen 80; server_name cafe.example.com; location / { root /var/www/cafe-admin; try_files $uri $uri/ /index.html; } location /api { proxy_pass http://backend:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }6. 性能优化与监控
6.1 数据库查询优化
咖啡店系统常见的性能瓶颈及解决方案:
N+1查询问题:
- 使用JPA的@EntityGraph
- 或MyBatis的嵌套结果映射
报表查询优化:
- 添加适当的索引
- 考虑使用物化视图
- 对大表进行分库分表
@Entity @NamedEntityGraph( name = "Order.withItems", attributeNodes = @NamedAttributeNode("items") ) public class Order { // ... } // 在Repository中使用 @EntityGraph("Order.withItems") Page<Order> findByStatus(OrderStatus status, Pageable pageable);6.2 前端性能优化
代码分割:
// 动态导入组件 const OrderDetail = () => import('@/views/OrderDetail.vue')API请求优化:
- 使用防抖/节流
- 请求缓存
- 批量请求
图片优化:
- 使用WebP格式
- 懒加载
- CDN加速
7. 扩展功能与未来演进
7.1 微信小程序集成
咖啡店系统可以扩展小程序端:
扫码点餐:
- 桌台二维码关联
- 自助下单支付
会员系统:
- 积分累计
- 优惠券发放
接口安全考虑:
- 小程序登录态验证
- 接口签名
- 频率限制
7.2 数据分析扩展
用户行为分析:
- 热门产品
- 消费时段分布
库存预测:
- 基于历史销量预测需求
- 自动生成采购建议
# 示例:使用Python进行销售预测 import pandas as pd from prophet import Prophet # 加载历史销售数据 df = pd.read_csv('sales_history.csv') df['ds'] = pd.to_datetime(df['date']) df['y'] = df['quantity'] # 创建并拟合模型 model = Prophet() model.fit(df) # 生成未来30天的预测 future = model.make_future_dataframe(periods=30) forecast = model.predict(future)在实际项目中,我们通常会遇到各种边界情况,比如高峰时段的并发订单处理、库存的实时同步等。通过合理的架构设计和代码优化,这些挑战都可以得到有效解决。
