简介:这是一套面向计算机专业本科生的期末大作业级实战项目,基于Spring Boot后端与Vue 3前端构建的校园奶茶店线上点单系统,适用于课程设计、毕业设计选题及Java全栈开发入门练习。资源包含完整可运行源码、MySQL数据库脚本、详细设计报告文档(含需求分析、ER图、接口说明与部署指南),所有代码均经本地编译调试通过,评审得分98分,内容获助教审定认可。压缩包共2000个文件,17.02MB,涵盖49个核心Java后端类、16个Vue组件、1192个JS逻辑文件、204个CSS样式文件、150个JSON配置及143份Markdown文档,结构清晰,前后端分离规范,便于理解MVC架构与RESTful API设计实践。
1. 项目概述与核心价值
又到了期末大作业扎堆的季节,后台收到不少私信,都在问有没有一个能“抄作业”但又显得不那么“水”的完整项目。今天就来聊聊一个经典选题:校园奶茶店线上点单系统。这个项目听起来简单,但麻雀虽小五脏俱全,它完美融合了前后端主流技术栈(Spring Boot + Vue),涵盖了从用户下单、商家管理到数据统计的全流程业务,是检验你Web开发综合能力的绝佳试金石。我当年带学生做课程设计,十个里有八个选类似的外卖或点餐系统,但能把业务逻辑理清、代码写得优雅、报告写得专业的,其实不多。这个项目真正的价值在于,它不是一个孤立的Demo,而是一个具备完整业务闭环、可直接部署运行、并能写进简历作为项目经验的实战案例。无论你是计算机专业的学生需要完成期末大作业,还是刚入行的开发者想找一个全栈项目练手,它都能提供一条清晰的路径。
2. 技术选型与架构设计思路
2.1 为什么是Spring Boot + Vue?
选择这个技术组合,绝非偶然,而是基于快速开发、生态成熟和前后端分离架构的必然选择。
后端:Spring Boot 作为坚实后盾Spring Boot的核心优势在于“约定大于配置”。对于校园奶茶店这种业务逻辑明确但并发量不会特别高的系统,它能让开发者从繁琐的XML配置中解放出来,快速搭建一个稳定、可扩展的后端服务。内嵌的Tomcat服务器,使得项目可以打包成一个独立的Jar包,部署变得极其简单,无论是交给老师演示还是自己放到云服务器上,都省心省力。此外,Spring Boot生态中丰富的Starter依赖,如spring-boot-starter-web(Web开发)、spring-boot-starter-data-jpa或mybatis-spring-boot-starter(数据访问)、spring-boot-starter-security(安全控制,可选),让我们能像搭积木一样构建应用。对于点单系统,事务管理(确保下单和库存扣减的原子性)、RESTful API设计(为前端Vue提供清晰的数据接口)是Spring Boot的天然强项。
前端:Vue.js 构建灵活交互界面Vue以其轻量、渐进式和上手快的特点,成为前端框架的热门选择。对于点单系统,用户界面需要频繁的动态交互:商品列表的筛选、购物车的实时增删改、订单状态的更新等。Vue的响应式数据绑定和组件化开发模式,让这些功能的实现变得直观。通过Vue CLI可以快速搭建项目结构,配合Vue Router实现单页面应用(SPA)的无刷新跳转(如从菜单页跳到订单详情页),使用Vuex管理全局状态(如用户登录信息、购物车数据),整个前端架构清晰且易于维护。与后端的交互,则通过Axios库发起HTTP请求,调用Spring Boot提供的API接口。
前后端分离架构的优势采用前后端分离,意味着后端只负责数据和业务逻辑,通过API接口提供服务;前端专注于用户界面和交互逻辑。这种架构的好处非常明显:开发可以并行进行,前端和后端工程师只需约定好API文档即可各自开发;部署相互独立,前端可以部署在Nginx等静态服务器,后端部署在Java应用服务器,互不影响;技术栈灵活,未来即使要替换前端框架(比如换成React),后端也几乎不需要改动。
2.2 系统核心模块设计
在动手敲代码之前,必须把系统的核心模块和它们之间的关系想清楚。一个线上点单系统,通常可以划分为以下四个核心模块:
- 用户端模块:面向校园内的学生和教职工。核心功能包括:用户注册/登录、浏览奶茶菜单(按分类、热度排序)、将商品加入购物车、编辑购物车、提交订单(选择配送地址、支付方式模拟)、查看个人订单历史及状态。
- 商品与菜单管理模块:这是系统的基石。需要管理奶茶的品类(如水果茶、芝士奶盖、经典奶茶)、具体商品(名称、图片、描述、价格、库存)以及可定制的选项(如甜度、冰度、加料)。这个模块通常提供后台管理界面。
- 订单处理模块:系统的业务核心。负责处理用户提交的订单,包括订单的创建、状态流转(待支付->已支付->制作中->待取餐/配送中->已完成)、库存的同步扣减、以及简单的订单查询与统计。
- 后台管理模块:面向奶茶店管理员。功能包括:商品的上架/下架/信息修改、订单的集中查看与处理(接单、出餐完成标记)、销售数据的简单图表统计(如今日销量Top5)、用户管理(通常仅查看,校园系统注册一般开放)。
这些模块通过数据库的表关系紧密连接。例如,一个用户(user表)可以有多个订单(order表),一个订单包含多个订单项(order_item表),每个订单项关联一个商品(product表)。理清这些实体关系,是设计数据库表结构的前提。
注意:在真正的商业系统中,支付和配送是两大复杂子系统。作为期末作业,我们通常采用“模拟”策略。支付可以集成一个简单的支付宝/微信支付沙箱接口,或者更简单地,在下单后直接置订单状态为“已支付”。配送则简化为“到店自取”和“校内配送(需填写宿舍楼号)”两种选择,状态由后台管理员手动更新。这既能体现业务完整性,又控制了项目复杂度。
3. 数据库设计与关键表结构解析
数据库设计是项目的“地基”,设计得好,后续开发事半功倍。这里采用MySQL作为关系型数据库,因为它免费、通用,且与Spring Boot集成简单。以下是核心表结构的设计思路与SQL示例。
3.1 核心表字段设计
用户表 (user)这是系统的基础。除了基本字段,role字段用于区分普通用户(USER)和管理员(ADMIN),是实现权限控制的关键。
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID', `username` varchar(50) NOT NULL UNIQUE COMMENT '用户名(学号/工号)', `password` varchar(255) NOT NULL COMMENT '加密后的密码', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `avatar` varchar(500) DEFAULT NULL COMMENT '头像URL', `role` varchar(20) NOT NULL DEFAULT 'USER' COMMENT '角色:USER, ADMIN', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';商品分类表 (category) 与商品表 (product)分类表用于管理奶茶大类。商品表是核心,status字段控制上架(ON_SALE)/下架(OFF_SALE),stock字段是实现库存扣减的关键。
CREATE TABLE `category` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '分类名称(如:果茶,奶盖)', `sort_order` int(11) DEFAULT '0' COMMENT '排序值', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品分类表'; CREATE TABLE `product` ( `id` int(11) NOT NULL AUTO_INCREMENT, `category_id` int(11) NOT NULL COMMENT '所属分类ID', `name` varchar(100) NOT NULL COMMENT '商品名称', `price` decimal(10,2) NOT NULL COMMENT '商品价格', `description` text COMMENT '商品描述', `image_url` varchar(500) DEFAULT NULL COMMENT '商品图片URL', `stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存数量', `status` varchar(20) NOT NULL DEFAULT 'ON_SALE' COMMENT '状态:ON_SALE, OFF_SALE', `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`), CONSTRAINT `fk_product_category` FOREIGN KEY (`category_id`) REFERENCES `category` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';订单表 (order) 与订单项表 (order_item)这是业务的核心表。订单表记录订单概要,order_no(订单号)需要全局唯一,通常用时间戳+随机数生成。total_amount是订单总金额,status跟踪订单生命周期。订单项表是订单与商品的多对多关联表,记录了购买时的快照信息(如商品当时的价格、选择的规格)。
CREATE TABLE `order` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_no` varchar(64) NOT NULL UNIQUE COMMENT '订单号(唯一)', `user_id` int(11) NOT NULL COMMENT '下单用户ID', `total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额', `status` varchar(30) NOT NULL DEFAULT 'UNPAID' COMMENT '订单状态:UNPAID, PAID, MAKING, READY_FOR_PICKUP, DELIVERING, COMPLETED, CANCELLED', `delivery_method` varchar(20) DEFAULT 'PICKUP' COMMENT '配送方式:PICKUP(自取), DELIVERY(配送)', `delivery_address` varchar(255) 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`), KEY `idx_user_id` (`user_id`), KEY `idx_order_no` (`order_no`), CONSTRAINT `fk_order_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表'; CREATE TABLE `order_item` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_id` int(11) NOT NULL COMMENT '所属订单ID', `product_id` int(11) NOT NULL COMMENT '商品ID', `product_name` varchar(100) NOT NULL COMMENT '商品名称(快照)', `product_image` varchar(500) DEFAULT NULL COMMENT '商品图片(快照)', `unit_price` decimal(10,2) NOT NULL COMMENT '商品单价(快照)', `quantity` int(11) NOT NULL COMMENT '购买数量', `total_price` decimal(10,2) NOT NULL COMMENT '该项总价', `specification` varchar(200) DEFAULT NULL COMMENT '规格(如:少糖,去冰,加珍珠)', PRIMARY KEY (`id`), KEY `idx_order_id` (`order_id`), KEY `idx_product_id` (`product_id`), CONSTRAINT `fk_order_item_order` FOREIGN KEY (`order_id`) REFERENCES `order` (`id`) ON DELETE CASCADE, CONSTRAINT `fk_order_item_product` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单项表';3.2 设计要点与避坑指南
- 密码存储:绝对不要明文存储密码!在Java后端,使用
BCryptPasswordEncoder这类强哈希函数对密码进行加密后存储。这是安全底线。 - 金额字段:涉及金额的字段(如
price,total_amount)必须使用DECIMAL类型,而不是FLOAT或DOUBLE,以避免浮点数计算带来的精度丢失问题。 - 订单号生成:订单号(
order_no)需要全局唯一且尽可能难以猜测。一个常见的做法是:时间戳(yyyyMMddHHmmss) + 随机数(6位) + 用户ID后4位。也可以考虑使用分布式ID生成算法,如雪花算法(Snowflake)。 - 数据快照:
order_item表中存储了商品下单时的名称、图片和单价,而不是直接关联product表的当前信息。这是因为商品信息可能会变(调价、改名),但订单历史必须保持原样,这是电商类系统的通用设计。 - 索引优化:在经常用于查询条件的字段上建立索引,如
user.id、order.order_no、product.category_id,可以大幅提升查询性能。外键字段自动会有索引,但像order.status这种高频筛选字段,也值得考虑加索引。
4. Spring Boot后端核心功能实现
后端采用经典的MVC分层架构:Controller层处理HTTP请求,Service层实现业务逻辑,Repository层(或Mapper层)负责数据持久化。这里以MyBatis-Plus作为数据访问层框架,因为它提供了强大的CRUD封装和条件构造器,能极大减少SQL编写。
4.1 项目结构与依赖配置
使用Spring Initializr(或IDE直接创建)初始化项目,主要依赖包括:
Spring Web:用于构建RESTful API。MyBatis Framework+MyBatis-Plus:数据持久化。MySQL Driver:连接MySQL数据库。Lombok:简化实体类代码(如自动生成getter/setter)。Spring Boot DevTools:开发热部署。
在application.yml中配置数据库连接、MyBatis-Plus和日志:
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/campus_tea_shop?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: your_password 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 mapper-locations: classpath:mapper/*.xml logging: level: com.yourpackage.mapper: debug # 打印特定包下的SQL日志4.2 用户认证与权限控制
对于校园系统,一个简单实用的认证方案是使用Spring Security结合JWT(JSON Web Token)。这里简述流程:
- 登录接口:用户提交用户名和密码,后端验证通过后,使用JJWT库生成一个JWT Token(包含用户ID、角色等信息),返回给前端。
- Token校验:创建一个JWT认证过滤器(
JwtAuthenticationFilter),拦截除登录、注册外的所有请求。从请求头中解析Token,验证有效性并从中提取用户信息,存入SecurityContextHolder。 - 权限注解:在Controller方法上使用
@PreAuthorize(“hasRole(‘ADMIN’)”)这样的注解,即可轻松实现方法级别的权限控制。例如,商品管理、订单处理的后台接口只允许ADMIN角色访问。
实操心得:如果觉得Spring Security配置稍显复杂,对于课程作业,也可以采用更简单的方案:在用户登录成功后,将用户信息(剔除密码)存入Redis,并生成一个Session Key返回给前端(作为Token)。后续请求前端携带此Token,后端从Redis中获取用户信息。这种方式避免了JWT的令牌刷新问题,实现起来更直观。关键在于,无论哪种方式,密码比对必须在服务器端进行(对比加密后的哈希值),且Token或Session需设置合理的过期时间。
4.3 商品与订单业务逻辑实现
商品服务(ProductService)核心逻辑:
- 列表查询:支持按分类、关键词、状态分页查询。使用MyBatis-Plus的
QueryWrapper可以轻松构建动态查询条件。 - 库存扣减:这是下单的关键,必须保证原子性,防止超卖。通常有两种做法:
- 悲观锁:在查询商品库存时使用
SELECT ... FOR UPDATE,但这会影响并发性能。 - 乐观锁:为商品表增加一个版本号字段
version。更新库存时,使用UPDATE product SET stock = stock - ?, version = version + 1 WHERE id = ? AND version = ? AND stock >= ?。如果更新影响行数为0,说明库存不足或版本冲突,需要回滚或提示用户。乐观锁在高并发场景下更优。
- 悲观锁:在查询商品库存时使用
订单服务(OrderService)下单流程: 这是系统最核心的业务,必须保证事务性。
@Transactional(rollbackFor = Exception.class) // 声明式事务,任何异常都回滚 public Order createOrder(OrderCreateRequest request, Integer userId) { // 1. 基础校验:验证用户、配送地址等 User user = userService.getById(userId); if (user == null) { throw new BusinessException(“用户不存在”); } // 2. 生成唯一订单号 String orderNo = generateOrderNo(); // 3. 遍历购物车项,计算总价,并逐个校验库存(使用乐观锁) BigDecimal totalAmount = BigDecimal.ZERO; List<OrderItem> orderItemList = new ArrayList<>(); for (CartItem cartItem : request.getCartItems()) { Product product = productService.getProductForUpdate(cartItem.getProductId()); // 这里可以封装一个带乐观锁校验的方法 if (product.getStock() < cartItem.getQuantity()) { throw new BusinessException(product.getName() + “库存不足”); } // 扣减库存 boolean updateSuccess = productService.reduceStock(cartItem.getProductId(), cartItem.getQuantity()); if (!updateSuccess) { throw new BusinessException(product.getName() + “库存扣减失败,请重试”); } // 构建订单项快照 OrderItem orderItem = new OrderItem(); orderItem.setProductName(product.getName()); orderItem.setUnitPrice(product.getPrice()); // ... 设置其他属性 orderItemList.add(orderItem); totalAmount = totalAmount.add(product.getPrice().multiply(new BigDecimal(cartItem.getQuantity()))); } // 4. 创建订单主记录 Order order = new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setTotalAmount(totalAmount); order.setStatus(OrderStatusEnum.UNPAID.getCode()); // ... 设置其他属性 orderMapper.insert(order); // 5. 批量保存订单项,并关联订单ID for (OrderItem item : orderItemList) { item.setOrderId(order.getId()); } orderItemService.saveBatch(orderItemList); // 6. (可选)清空该用户的购物车 cartService.clearCart(userId); return order; }注意事项:上述流程中,库存扣减和订单创建必须在同一个数据库事务中。
@Transactional注解确保了这一点。在高并发场景下,第3步的库存校验和扣减是性能瓶颈,需要仔细设计。对于课程项目,如果并发压力不大,此方案足够。此外,可以考虑引入消息队列(如RabbitMQ)进行异步削峰,将下单和扣库存解耦,但这会显著增加系统复杂度。
5. Vue前端项目搭建与核心页面开发
前端使用Vue 3 + Composition API + Vite构建,UI库选用Element Plus,因为它组件丰富,风格中性,适合后台管理系统。
5.1 项目初始化与路由配置
使用Vite快速创建项目,并安装必要依赖:
npm create vue@latest campus-tea-shop-frontend cd campus-tea-shop-frontend npm install element-plus @element-plus/icons-vue axios vue-router pinia在src/router/index.js中配置路由,实现SPA:
import { createRouter, createWebHistory } from ‘vue-router’; const routes = [ { path: ‘/‘, redirect: ‘/home‘ }, { path: ‘/home‘, name: ‘Home‘, component: () => import(‘@/views/Home.vue‘) // 首页,商品列表 }, { path: ‘/cart‘, name: ‘Cart‘, component: () => import(‘@/views/Cart.vue‘) // 购物车页 }, { path: ‘/order‘, name: ‘Order‘, component: () => import(‘@/views/Order.vue‘) // 订单页 }, { path: ‘/admin‘, name: ‘Admin‘, component: () => import(‘@/views/admin/AdminLayout.vue‘), // 后台布局 meta: { requiresAuth: true, requiresAdmin: true }, // 路由元信息,用于权限校验 children: [ { path: ‘products‘, component: () => import(‘@/views/admin/ProductManage.vue‘) }, { path: ‘orders‘, component: () => import(‘@/views/admin/OrderManage.vue‘) } ] } ]; const router = createRouter({ history: createWebHistory(), routes }); // 全局前置守卫,进行权限检查 router.beforeEach((to, from, next) => { const userStore = useUserStore(); // 假设使用Pinia管理用户状态 if (to.meta.requiresAuth && !userStore.isLoggedIn) { next(‘/login‘); } else if (to.meta.requiresAdmin && userStore.userRole !== ‘ADMIN‘) { next(‘/home‘); // 非管理员跳回首页 } else { next(); } }); export default router;5.2 状态管理与API请求封装
使用Pinia进行全局状态管理,存储用户信息、购物车数据等。 在src/stores/user.js中:
import { defineStore } from ‘pinia‘; import { login as apiLogin } from ‘@/api/auth‘; // 封装的API模块 export const useUserStore = defineStore(‘user‘, { state: () => ({ token: localStorage.getItem(‘token‘) || ‘‘, userInfo: JSON.parse(localStorage.getItem(‘userInfo‘)) || null }), getters: { isLoggedIn: (state) => !!state.token, userRole: (state) => state.userInfo?.role }, actions: { async login(loginForm) { const res = await apiLogin(loginForm); this.token = res.data.token; this.userInfo = res.data.userInfo; // 存储到localStorage localStorage.setItem(‘token‘, this.token); localStorage.setItem(‘userInfo‘, JSON.stringify(this.userInfo)); // 设置axios默认请求头 axios.defaults.headers.common[‘Authorization‘] = `Bearer ${this.token}`; }, logout() { this.token = ‘‘; this.userInfo = null; localStorage.removeItem(‘token‘); localStorage.removeItem(‘userInfo‘); delete axios.defaults.headers.common[‘Authorization‘]; } } });在src/api/request.js中封装Axios实例,统一处理请求拦截(添加Token)、响应拦截(处理错误):
import axios from ‘axios‘; import { ElMessage } from ‘element-plus‘; import router from ‘@/router‘; const service = axios.create({ baseURL: process.env.VITE_API_BASE_URL, // 从环境变量读取后端API地址 timeout: 10000 }); service.interceptors.request.use( config => { const userStore = useUserStore(); if (userStore.token) { config.headers.Authorization = `Bearer ${userStore.token}`; } return config; }, error => { return Promise.reject(error); } ); service.interceptors.response.use( response => { const res = response.data; // 假设后端统一返回格式为 { code: 200, data: {}, message: ‘success‘ } if (res.code !== 200) { ElMessage.error(res.message || ‘Error‘); // 如果是token过期或无效 if (res.code === 401) { userStore.logout(); router.push(‘/login‘); } return Promise.reject(new Error(res.message || ‘Error‘)); } else { return res; } }, error => { ElMessage.error(error.message || ‘网络请求失败‘); return Promise.reject(error); } ); export default service;5.3 核心页面组件开发示例:商品列表与购物车
商品列表页 (Home.vue): 关键点在于使用el-row和el-col进行栅格布局,循环渲染商品卡片。每个卡片包含商品图片、名称、价格和“加入购物车”按钮。点击按钮时,调用Vuex/Pinia的action,将商品信息(id, name, price, count等)添加到全局的购物车状态中,并可以同时更新本地存储(localStorage)以实现页面刷新后数据不丢失。
<template> <div class=“home”> <el-row :gutter=“20”> <el-col :span=“6” v-for=“product in productList” :key=“product.id”> <el-card :body-style=“{ padding: ‘0px’ }”> <img :src=“product.imageUrl” class=“product-image” /> <div style=“padding: 14px;”> <span>{{ product.name }}</span> <div class=“bottom”> <span class=“price”>¥{{ product.price }}</span> <el-button type=“primary” size=“small” @click=“addToCart(product)”>加入购物车</el-button> </div> </div> </el-card> </el-col> </el-row> </div> </template> <script setup> import { ref, onMounted } from ‘vue‘; import { getProductList } from ‘@/api/product‘; import { useCartStore } from ‘@/stores/cart‘; const productList = ref([]); const cartStore = useCartStore(); const loadProducts = async () => { const res = await getProductList({ page: 1, pageSize: 12 }); productList.value = res.data.list; }; const addToCart = (product) => { cartStore.addItem({ id: product.id, name: product.name, price: product.price, image: product.imageUrl, count: 1 }); ElMessage.success(‘已加入购物车‘); }; onMounted(() => { loadProducts(); }); </script>购物车页 (Cart.vue): 购物车页面需要展示所有已选商品,允许用户修改数量、删除商品,并实时计算总价。关键逻辑在于:
- 从全局状态(Pinia Store)中获取购物车列表。
- 使用
el-table展示,数量列使用el-input-number组件方便增减。 - 任何数量修改或删除操作,都需要同步更新全局状态和本地存储。
- 底部展示总价,并提供“去结算”按钮,点击后跳转到订单确认页。
实操心得:购物车数据在前端管理,但提交订单时,后端需要重新校验价格和库存。因为用户可能在提交前,商品信息已发生变化。所以,前端购物车更多是一个“草稿”,真正的校验和计价发生在后端下单接口。此外,购物车状态持久化到
localStorage时,建议只存商品ID和数量,商品详情在每次进入页面时从接口重新获取,以保证信息的实时性。
6. 项目部署与报告文档撰写要点
6.1 后端部署(以Linux服务器为例)
- 环境准备:服务器安装JDK 8+、MySQL。
- 数据库初始化:在MySQL中执行项目SQL目录下的建表脚本,创建数据库和表结构。
- 应用打包:在项目根目录下执行
mvn clean package -DskipTests(Maven)或使用Gradle打包,会在target目录生成一个可执行的jar文件(如campus-tea-shop-0.0.1-SNAPSHOT.jar)。 - 上传与运行:将
jar包上传至服务器。可以使用nohup命令在后台运行:nohup java -jar campus-tea-shop-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > app.log 2>&1 &。这里的prodprofile对应application-prod.yml配置文件,用于配置生产环境的数据库连接等。 - 使用Nginx反向代理(可选但推荐):为了让前端能访问后端API,且隐藏端口,可以配置Nginx。在Nginx配置文件中添加一个
location块,将/api/路径的请求代理到后端Spring Boot应用(默认端口8080)。server { listen 80; server_name your-domain.com; # 或服务器IP location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 前端静态文件部署配置(见下节) }
6.2 前端部署
- 构建生产版本:在前端项目目录下运行
npm run build,会在dist目录生成优化后的静态文件(HTML, JS, CSS)。 - 部署静态文件:将
dist目录下的所有文件上传到服务器的某个目录,例如/var/www/html/campus-tea-shop。 - 配置Nginx:在同一个Nginx配置中,添加一个
location /块,指向前端静态文件目录,并配置try_files以实现Vue Router的history模式支持。server { listen 80; server_name your-domain.com; location / { root /var/www/html/campus-tea-shop; index index.html; try_files $uri $uri/ /index.html; # 支持Vue Router history模式 } location /api/ { proxy_pass http://127.0.0.1:8080; # ... 其他代理配置 } } - 重启Nginx服务:
sudo systemctl restart nginx。
6.3 期末报告文档撰写核心要点
报告文档是展示你项目思考和总结能力的关键。一份优秀的报告不应只是代码的堆砌,而应体现系统化思维。
- 需求分析:清晰描述校园奶茶店线上点单系统的功能性需求(用户、商家分别能做什么)和非功能性需求(性能、安全性、易用性等)。
- 系统设计:
- 架构设计图:绘制前后端分离的架构图,标明浏览器、Nginx、Spring Boot应用、MySQL之间的关系和数据流向。
- 数据库ER图:使用工具(如PDManer、Navicat)生成实体关系图,并附上核心表结构的详细说明。
- 核心功能流程图:如下单流程、支付模拟流程的流程图。
- 详细设计与实现:这是报告的主体。分模块阐述,例如:
- 用户模块:注册登录的时序图、密码加密方式、JWT/Session认证流程。
- 商品模块:后台管理的界面设计、富文本编辑器(如用于商品描述)的集成。
- 订单模块:重点!详细画出下单的序列图,说明如何解决超卖问题(悲观锁/乐观锁),订单状态机是如何设计的。
- 前端组件设计:说明关键组件(如商品卡片、购物车浮层)的数据流和交互逻辑。
- 系统测试:列出测试用例表。包括:
- 单元测试:对核心Service方法(如库存扣减)的测试。
- 接口测试:使用Postman或Swagger测试主要API接口。
- 界面测试:主要功能的界面操作测试。
- 性能测试(可选):使用JMeter模拟多用户并发下单,给出响应时间、吞吐量数据。
- 部署说明:详细记录从环境准备到服务上线的每一步命令和配置,让评审老师能按步骤重现。
- 总结与展望:总结项目中学到的技术点和遇到的挑战(如跨域问题、事务管理、前端状态保持)。展望可能的优化方向,如引入Redis缓存热门商品、使用WebSocket实现订单状态实时推送、集成真正的支付网关等。
避坑指南:报告中最容易丢分的地方是“只有描述,没有论证”。例如,提到“使用了JWT做认证”,要接着说明“为什么选JWT而不是Session?JWT的无状态特性适合分布式部署,但我们也考虑了其令牌刷新机制,在本项目中我们设置了1小时的有效期……”。这种深层次的思考,才是区分普通作业和优秀作业的关键。
本文还有配套的精品资源,点击获取