ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SSM+Vue前后端分离网上订餐系统开发实战

2026/9/3 1:25:22 拓冰建站 浏览量
SSM+Vue前后端分离网上订餐系统开发实战 这次我们来看一个完整的 Java 网上订餐管理系统项目。这个系统基于 SSM 框架Spring Spring MVC MyBatis使用 MySQL 作为数据库前端采用 Vue.js 构建是一个典型的企业级 Web 应用。对于正在学习 Java 全栈开发的同学来说这个项目涵盖了从数据库设计到前后端联调的完整流程具有很强的实战参考价值。系统主要功能包括用户注册登录、菜品浏览、购物车管理、订单处理、支付集成和后台管理等多个模块。最值得关注的是它采用了前后端分离架构前端 Vue 负责页面渲染和用户交互后端 SSM 提供 RESTful API这种设计模式在当前企业开发中非常流行。本文将带大家完成整个系统的环境搭建、数据库初始化、前后端启动和功能测试。无论你是想学习 SSM 框架整合、Vue 前端开发还是需要一套完整的订餐系统作为课程设计或毕业项目这篇文章都能提供详细的指导。1. 核心能力速览能力项说明技术栈后端Spring Spring MVC MyBatis前端Vue.js Element UI数据库MySQL系统架构前后端分离RESTful API 接口设计主要功能用户管理、菜品展示、购物车、订单管理、支付模拟、后台管理部署方式后端 Tomcat 部署前端 Node.js 编译后静态部署开发环境JDK 1.8、MySQL 5.7、Node.js 14、Maven 3.6适合场景Java 全栈学习、毕业设计、课程实践、小型餐饮企业管理系统2. 适用场景与使用边界这个网上订餐管理系统最适合以下几类用户学习用途对于正在学习 Java Web 开发的学生这个项目展示了 SSM 框架的实际应用包括控制器编写、服务层设计、数据持久化等核心概念。前端 Vue 部分也体现了现代前端开发的最佳实践。课程设计/毕业设计如果你需要完成一个完整的 Web 系统作为课程作业或毕业项目这个订餐系统提供了完整的功能模块和文档可以快速上手并在此基础上进行个性化扩展。小型餐饮企业系统具备基本的订餐流程管理功能适合小型餐厅或外卖商家使用但需要根据实际业务需求进行二次开发。使用边界需要注意系统采用模拟支付实际商用需要接入正规支付渠道高并发场景需要优化数据库设计和缓存机制移动端适配可能需要额外开发工作后台管理功能相对基础复杂运营需求需要扩展3. 环境准备与前置条件在开始部署之前请确保你的开发环境满足以下要求3.1 基础软件版本要求后端环境JDK 1.8 或更高版本推荐 JDK 8 或 JDK 11Apache Maven 3.6.0MySQL 5.7 或 8.0 版本Tomcat 8.5 或使用 Spring Boot 内嵌容器前端环境Node.js 14.0.0 或更高版本npm 6.0.0 或 yarn 1.22.0Vue CLI 4.0如使用3.2 环境检查命令打开命令行工具依次执行以下命令检查环境# 检查 Java 版本 java -version # 检查 Maven 版本 mvn -version # 检查 Node.js 版本 node -v # 检查 npm 版本 npm -v # 检查 MySQL 版本需要先启动 MySQL 服务 mysql --version3.3 端口占用检查系统默认使用以下端口请确保这些端口没有被其他程序占用后端服务8080可配置前端服务3000 或 8081开发模式MySQL3306检查端口占用的命令# Windows 系统 netstat -ano | findstr :8080 # Linux/Mac 系统 lsof -i :80804. 数据库设计与初始化4.1 数据库创建首先登录 MySQL创建数据库和用户-- 创建数据库 CREATE DATABASE IF NOT EXISTS food_order DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 创建用户并授权生产环境建议使用更复杂的密码 CREATE USER food_userlocalhost IDENTIFIED BY food_password123; GRANT ALL PRIVILEGES ON food_order.* TO food_userlocalhost; FLUSH PRIVILEGES; -- 使用数据库 USE food_order;4.2 数据表结构主要数据表包括用户表、菜品表、订单表等以下是核心表结构示例-- 用户表 CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL UNIQUE, password varchar(255) NOT NULL, email varchar(100) DEFAULT NULL, phone varchar(20) DEFAULT NULL, address text, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 菜品分类表 CREATE TABLE category ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, description text, sort_order int(11) DEFAULT 0, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 菜品表 CREATE TABLE food ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, category_id int(11) NOT NULL, price decimal(10,2) NOT NULL, description text, image_url varchar(255) DEFAULT NULL, status tinyint(1) DEFAULT 1 COMMENT 1-上架, 0-下架, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), FOREIGN KEY (category_id) REFERENCES category(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单表 CREATE TABLE order ( id int(11) NOT NULL AUTO_INCREMENT, order_no varchar(50) NOT NULL UNIQUE, user_id int(11) NOT NULL, total_amount decimal(10,2) NOT NULL, status tinyint(1) DEFAULT 0 COMMENT 0-待支付, 1-已支付, 2-已取消, create_time datetime DEFAULT CURRENT_TIMESTAMP, pay_time datetime DEFAULT NULL, PRIMARY KEY (id), FOREIGN KEY (user_id) REFERENCES user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单详情表 CREATE TABLE order_item ( id int(11) NOT NULL AUTO_INCREMENT, order_id int(11) NOT NULL, food_id int(11) NOT NULL, quantity int(11) NOT NULL, price decimal(10,2) NOT NULL, PRIMARY KEY (id), FOREIGN KEY (order_id) REFERENCES order(id), FOREIGN KEY (food_id) REFERENCES food(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;4.3 初始化数据插入一些测试数据以便演示-- 插入分类数据 INSERT INTO category (name, description, sort_order) VALUES (中式快餐, 各种经典中式快餐, 1), (西式简餐, 汉堡、披萨等西式美食, 2), (饮品甜点, 饮料、甜品、小吃, 3); -- 插入菜品数据 INSERT INTO food (name, category_id, price, description, image_url) VALUES (红烧牛肉面, 1, 28.00, 经典红烧口味牛肉大块, /images/beef_noodle.jpg), (宫保鸡丁饭, 1, 25.00, 川味宫保鸡丁配米饭, /images/kungpao_chicken.jpg), (芝士汉堡套餐, 2, 35.00, 牛肉汉堡薯条可乐, /images/cheese_burger.jpg), (水果披萨, 2, 45.00, 9寸水果披萨多种水果, /images/fruit_pizza.jpg), (珍珠奶茶, 3, 15.00, 经典珍珠奶茶大杯, /images/bubble_tea.jpg); -- 插入测试用户 INSERT INTO user (username, password, email, phone) VALUES (testuser, 123456, testexample.com, 13800138000);5. 后端 SSM 框架部署5.1 项目结构说明典型的 SSM 项目结构如下food-order-system/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── foodorder/ │ │ │ ├── controller/ # 控制器层 │ │ │ ├── service/ # 服务层 │ │ │ ├── dao/ # 数据访问层 │ │ │ ├── entity/ # 实体类 │ │ │ └── config/ # 配置类 │ │ ├── resources/ │ │ │ ├── mapper/ # MyBatis映射文件 │ │ │ ├── application.yml # 主配置文件 │ │ │ └── mybatis-config.xml # MyBatis配置 │ │ └── webapp/ │ │ └── WEB-INF/ │ │ └── web.xml # Web配置 │ └── test/ # 测试代码 ├── pom.xml # Maven依赖配置 └── README.md5.2 关键配置文件application.yml 数据库配置spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/food_order?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: food_user password: food_password123 servlet: multipart: max-file-size: 10MB max-request-size: 10MB server: port: 8080 servlet: context-path: /api mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.foodorder.entityMyBatis 映射文件示例FoodMapper.xml?xml version1.0 encodingUTF-8 ? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.foodorder.dao.FoodDao resultMap idFoodResultMap typecom.foodorder.entity.Food id propertyid columnid / result propertyname columnname / result propertycategoryId columncategory_id / result propertyprice columnprice / result propertydescription columndescription / result propertyimageUrl columnimage_url / result propertystatus columnstatus / result propertycreateTime columncreate_time / /resultMap select idfindAll resultMapFoodResultMap SELECT * FROM food WHERE status 1 ORDER BY create_time DESC /select select idfindByCategory parameterTypeint resultMapFoodResultMap SELECT * FROM food WHERE category_id #{categoryId} AND status 1 /select select idfindById parameterTypeint resultMapFoodResultMap SELECT * FROM food WHERE id #{id} /select /mapper5.3 核心控制器代码FoodController.javaRestController RequestMapping(/food) CrossOrigin(origins *) // 允许跨域访问 public class FoodController { Autowired private FoodService foodService; GetMapping(/list) public ResponseEntityListFood getAllFoods() { try { ListFood foods foodService.findAll(); return ResponseEntity.ok(foods); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } GetMapping(/category/{categoryId}) public ResponseEntityListFood getFoodsByCategory(PathVariable Integer categoryId) { try { ListFood foods foodService.findByCategory(categoryId); return ResponseEntity.ok(foods); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } GetMapping(/{id}) public ResponseEntityFood getFoodById(PathVariable Integer id) { try { Food food foodService.findById(id); if (food ! null) { return ResponseEntity.ok(food); } else { return ResponseEntity.notFound().build(); } } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } }5.4 启动后端服务使用 Maven 编译并启动项目# 进入项目根目录 cd food-order-system # 清理并编译项目 mvn clean compile # 打包项目War包 mvn clean package -DskipTests # 直接运行如果使用Spring Boot mvn spring-boot:run如果使用传统的 Tomcat 部署将生成的 WAR 包复制到 Tomcat 的 webapps 目录下然后启动 Tomcat。6. 前端 Vue 项目部署6.1 项目结构说明Vue 前端项目典型结构food-order-frontend/ ├── public/ │ ├── index.html │ └── favicon.ico ├── src/ │ ├── components/ # 公共组件 │ ├── views/ # 页面组件 │ ├── router/ # 路由配置 │ ├── store/ # Vuex状态管理 │ ├── api/ # API接口调用 │ ├── assets/ # 静态资源 │ └── App.vue # 根组件 ├── package.json # 项目依赖 └── vue.config.js # Vue配置6.2 关键配置文件vue.config.js 开发服务器配置const { defineConfig } require(vue/cli-service) module.exports defineConfig({ transpileDependencies: true, devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: /api } } } }, // 构建配置 configureWebpack: { resolve: { alias: { : path.resolve(__dirname, src) } } } })API 接口调用示例src/api/food.jsimport request from /utils/request // 获取所有菜品 export function getFoodList() { return request({ url: /api/food/list, method: get }) } // 根据分类获取菜品 export function getFoodsByCategory(categoryId) { return request({ url: /api/food/category/${categoryId}, method: get }) } // 获取菜品详情 export function getFoodDetail(id) { return request({ url: /api/food/${id}, method: get }) }6.3 核心页面组件菜品列表页面src/views/FoodList.vuetemplate div classfood-list div classcategory-tabs el-tabs v-modelactiveCategory tab-clickhandleCategoryChange el-tab-pane v-forcategory in categories :keycategory.id :labelcategory.name :namecategory.id.toString() /el-tab-pane /el-tabs /div div classfood-grid el-row :gutter20 el-col v-forfood in foods :keyfood.id :xs12 :sm8 :md6 :lg4 el-card classfood-card :body-style{ padding: 0px } img :srcfood.imageUrl classfood-image :altfood.name div stylepadding: 14px; span classfood-name{{ food.name }}/span div classfood-price¥{{ food.price }}/div div classfood-actions el-button typeprimary sizesmall clickaddToCart(food) 加入购物车 /el-button /div /div /el-card /el-col /el-row /div /div /template script import { getFoodList, getFoodsByCategory } from /api/food export default { name: FoodList, data() { return { activeCategory: 1, categories: [ { id: 1, name: 全部 }, { id: 2, name: 中式快餐 }, { id: 3, name: 西式简餐 }, { id: 4, name: 饮品甜点 } ], foods: [] } }, mounted() { this.loadFoods() }, methods: { async loadFoods() { try { let response if (this.activeCategory 1) { response await getFoodList() } else { response await getFoodsByCategory(this.activeCategory) } this.foods response.data } catch (error) { this.$message.error(获取菜品列表失败) } }, handleCategoryChange(tab) { this.activeCategory tab.name this.loadFoods() }, addToCart(food) { this.$store.dispatch(cart/addToCart, food) this.$message.success(已添加到购物车) } } } /script style scoped .food-list { padding: 20px; } .category-tabs { margin-bottom: 20px; } .food-card { margin-bottom: 20px; } .food-image { width: 100%; height: 160px; object-fit: cover; } .food-name { font-size: 16px; font-weight: bold; display: block; margin-bottom: 8px; } .food-price { color: #ff6b35; font-size: 18px; font-weight: bold; margin-bottom: 10px; } /style6.4 启动前端服务# 进入前端项目目录 cd food-order-frontend # 安装依赖 npm install # 启动开发服务器 npm run serve # 构建生产版本 npm run build启动成功后访问 http://localhost:8081 即可看到前端界面。7. 功能测试与效果验证7.1 用户注册登录测试测试步骤访问系统首页点击注册按钮填写用户名、密码、邮箱等信息提交注册观察是否成功跳转到登录页面使用注册的账号登录系统验证登录后是否能正常访问个人中心预期结果注册成功数据库 users 表新增记录登录后能获取到用户 token 或 session页面能正确显示用户信息7.2 菜品浏览功能测试测试步骤进入菜品列表页面切换不同分类标签查看菜品图片、价格、描述信息测试搜索功能如有预期结果分类切换时菜品列表正确更新图片加载正常无404错误价格和描述信息显示正确7.3 购物车功能测试测试步骤点击加入购物车按钮查看购物车图标数量变化进入购物车页面检查商品信息修改商品数量测试总价计算删除商品测试更新逻辑预期结果加入购物车后数量实时更新购物车页面显示正确的商品信息和总价数量修改和删除操作响应及时7.4 订单流程测试测试步骤从购物车进入订单确认页面填写收货地址和备注信息提交订单生成订单号模拟支付流程在个人中心查看订单状态预期结果订单生成成功数据库 orders 表新增记录订单状态正确流转待支付→已支付个人中心能正确显示订单历史8. 接口 API 调用示例8.1 后端 API 接口列表系统提供的主要 RESTful API接口功能请求方法接口路径参数说明用户登录POST/api/user/loginusername, password用户注册POST/api/user/register用户信息对象获取菜品列表GET/api/food/list无参数按分类获取菜品GET/api/food/category/{id}分类ID加入购物车POST/api/cart/add商品ID, 数量创建订单POST/api/order/create购物车信息支付订单POST/api/order/pay订单ID8.2 API 调用示例代码用户登录接口调用// 使用 axios 调用登录接口 const loginData { username: testuser, password: 123456 }; axios.post(/api/user/login, loginData) .then(response { if (response.data.success) { // 登录成功保存 token localStorage.setItem(token, response.data.token); console.log(登录成功); } else { console.log(登录失败:, response.data.message); } }) .catch(error { console.error(登录请求失败:, error); });Java 单元测试示例SpringBootTest RunWith(SpringRunner.class) public class FoodControllerTest { Autowired private WebApplicationContext webApplicationContext; private MockMvc mockMvc; Before public void setUp() { mockMvc MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); } Test public void testGetFoodList() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get(/api/food/list) .contentType(MediaType.APPLICATION_JSON)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath($).isArray()); } }9. 系统优化与扩展建议9.1 性能优化方案数据库优化-- 为常用查询字段添加索引 CREATE INDEX idx_food_category ON food(category_id); CREATE INDEX idx_food_status ON food(status); CREATE INDEX idx_order_user ON order(user_id); CREATE INDEX idx_order_time ON order(create_time);缓存配置Redis// Spring Cache 配置示例 Configuration EnableCaching public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } } // 在Service层使用缓存 Service public class FoodService { Cacheable(value foods, key #categoryId) public ListFood findByCategory(Integer categoryId) { // 数据库查询逻辑 return foodDao.findByCategory(categoryId); } }9.2 安全增强措施密码加密存储// 使用BCrypt加密密码 Service public class UserService { public String encodePassword(String rawPassword) { return BCrypt.hashpw(rawPassword, BCrypt.gensalt()); } public boolean checkPassword(String rawPassword, String encodedPassword) { return BCrypt.checkpw(rawPassword, encodedPassword); } }API 接口权限控制// JWT token 验证拦截器 Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); if (JwtUtil.validateToken(token)) { return true; } } response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } }10. 常见问题与排查方法10.1 启动阶段问题问题1数据库连接失败Error: Access denied for user food_userlocalhost排查步骤检查 MySQL 服务是否启动systemctl status mysqlLinux或服务管理器Windows确认数据库用户和密码是否正确检查用户权限SHOW GRANTS FOR food_userlocalhost验证数据库是否存在SHOW DATABASES LIKE food_order问题2端口被占用Web server failed to start. Port 8080 was already in use.解决方案# 查找占用端口的进程 lsof -i :8080 # 杀死进程谨慎操作 kill -9 PID # 或者修改应用端口 # 在 application.yml 中修改 server.port server: port: 808110.2 运行时问题问题3前端访问后端 API 跨域错误Access to XMLHttpRequest at http://localhost:8080/api/food/list from origin http://localhost:8081 has been blocked by CORS policy解决方案在后端添加 CORS 配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(http://localhost:8081) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); } }问题4静态资源访问404GET http://localhost:8080/images/beef_noodle.jpg 404解决方案配置静态资源映射Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/images/**) .addResourceLocations(file:/path/to/your/images/); } }10.3 功能性问题问题5购物车数据不持久化刷新页面后购物车数据丢失。解决方案使用 localStorage 或 Vuex 持久化存储// 在Vuex store中添加持久化 const cartStore { state: { items: JSON.parse(localStorage.getItem(cartItems)) || [] }, mutations: { addItem(state, item) { state.items.push(item) localStorage.setItem(cartItems, JSON.stringify(state.items)) } } }问题6图片上传功能异常文件上传失败或无法保存。排查步骤检查文件大小限制配置确认上传目录权限验证文件类型过滤规则检查磁盘空间是否充足这个 Java 网上订餐管理系统项目涵盖了现代 Web 开发的完整技术栈通过实际部署和测试你可以深入理解 SSM 框架整合、Vue 前端开发、数据库设计和前后端联调的全过程。建议先从基础功能开始验证逐步扩展到高级特性和性能优化。