在开发网上订餐系统时,很多初学者会面临技术栈选择困难、前后端分离架构不熟悉、数据库设计不合理等问题。本文将基于SSM框架+Vue.js,完整实现一个功能完备的网上订餐管理系统,包含前端用户界面、后端业务逻辑和数据库设计,提供可直接运行的代码示例和详细配置说明。
1. 系统概述与技术选型
网上订餐管理系统是一个典型的电子商务应用,需要处理用户注册登录、菜品浏览、购物车管理、订单处理等核心业务。针对这些需求,我们选择以下技术栈:
1.1 技术架构说明
系统采用前后端分离架构,后端使用SSM框架(Spring+Spring MVC+MyBatis)提供RESTful API接口,前端使用Vue.js构建用户界面,数据库采用MySQL存储业务数据。
后端技术栈:
- Spring Framework:负责依赖注入和事务管理
- Spring MVC:处理HTTP请求和响应
- MyBatis:数据库ORM映射框架
- MySQL:关系型数据库
- Maven:项目构建和依赖管理
前端技术栈:
- Vue.js:渐进式JavaScript框架
- Element UI:基于Vue的组件库
- Axios:HTTP请求库
- Vue Router:路由管理
1.2 系统功能模块
系统主要包含以下功能模块:
- 用户管理:注册、登录、个人信息管理
- 菜品管理:菜品分类、菜品信息维护
- 购物车管理:添加商品、修改数量、清空购物车
- 订单管理:下单、支付、订单状态跟踪
- 后台管理:数据统计、系统配置
2. 开发环境准备
2.1 软件版本要求
在开始开发前,需要确保本地环境已安装以下软件:
后端环境:
- JDK 1.8或更高版本
- Apache Maven 3.6+
- MySQL 5.7或8.0版本
- Tomcat 8.5+或使用Spring Boot内嵌容器
前端环境:
- Node.js 14.0+
- npm 6.0+或yarn包管理器
- Vue CLI 4.0+
2.2 数据库环境配置
首先创建数据库并配置连接信息:
-- 创建数据库 CREATE DATABASE IF NOT EXISTS `food_order` DEFAULT CHARACTER SET utf8mb4; -- 使用数据库 USE `food_order`; -- 创建用户表 CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL UNIQUE, `password` varchar(100) NOT NULL, `email` varchar(100) NOT NULL, `phone` varchar(20) DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 创建菜品分类表 CREATE TABLE `category` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `description` varchar(200) DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 创建菜品表 CREATE TABLE `dish` ( `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(200) 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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 创建订单表 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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 创建订单明细表 CREATE TABLE `order_item` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_id` int(11) NOT NULL, `dish_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 (`dish_id`) REFERENCES `dish`(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;3. 后端项目搭建
3.1 Maven项目结构创建
使用Maven创建多模块项目,结构如下:
food-order-system/ ├── food-order-api/ # 接口定义模块 ├── food-order-service/ # 业务逻辑模块 ├── food-order-dao/ # 数据访问模块 ├── food-order-common/ # 公共工具模块 └── food-order-web/ # Web控制器模块pom.xml核心依赖配置:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.foodorder</groupId> <artifactId>food-order-system</artifactId> <version>1.0.0</version> <packaging>pom</packaging> <properties> <spring.version>5.3.18</spring.version> <mybatis.version>3.5.9</mybatis.version> <mybatis-spring.version>2.0.7</mybatis-spring.version> <mysql.version>8.0.28</mysql.version> <jackson.version>2.13.3</jackson.version> </properties> <dependencies> <!-- Spring核心依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!-- MyBatis相关 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>${mybatis-spring.version}</version> </dependency> <!-- 数据库连接 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> <!-- JSON处理 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <!-- 其他工具 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> <scope>provided</scope> </dependency> </dependencies> </project>3.2 Spring配置文件
创建Spring核心配置文件applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 组件扫描 --> <context:component-scan base-package="com.foodorder"/> <!-- 数据源配置 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/food_order?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="123456"/> <property name="initialSize" value="5"/> <property name="minIdle" value="5"/> <property name="maxActive" value="20"/> </bean> <!-- MyBatis SqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> <property name="typeAliasesPackage" value="com.foodorder.entity"/> </bean> <!-- Mapper扫描 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.foodorder.dao"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!-- 事务管理 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 注解驱动事务 --> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>3.3 实体类设计
创建对应的Java实体类,使用Lombok简化代码:
// User.java package com.foodorder.entity; import lombok.Data; import java.util.Date; @Data public class User { private Integer id; private String username; private String password; private String email; private String phone; private Date createTime; private Date updateTime; } // Dish.java package com.foodorder.entity; import lombok.Data; import java.math.BigDecimal; import java.util.Date; @Data public class Dish { private Integer id; private String name; private Integer categoryId; private BigDecimal price; private String description; private String imageUrl; private Integer status; private Date createTime; // 关联字段 private String categoryName; } // Order.java package com.foodorder.entity; import lombok.Data; import java.math.BigDecimal; import java.util.Date; import java.util.List; @Data public class Order { private Integer id; private String orderNo; private Integer userId; private BigDecimal totalAmount; private Integer status; private Date createTime; private Date payTime; // 关联字段 private List<OrderItem> orderItems; private String username; } // OrderItem.java package com.foodorder.entity; import lombok.Data; import java.math.BigDecimal; @Data public class OrderItem { private Integer id; private Integer orderId; private Integer dishId; private Integer quantity; private BigDecimal price; // 关联字段 private String dishName; private String imageUrl; }4. 核心业务实现
4.1 用户管理模块
用户管理包含注册、登录、信息修改等功能:
// UserService.java package com.foodorder.service; import com.foodorder.entity.User; public interface UserService { User register(User user); User login(String username, String password); User getUserById(Integer id); boolean updateUser(User user); boolean checkUsernameExists(String username); } // UserServiceImpl.java package com.foodorder.service.impl; import com.foodorder.dao.UserDao; import com.foodorder.entity.User; import com.foodorder.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.DigestUtils; @Service @Transactional public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public User register(User user) { // 检查用户名是否已存在 if (userDao.findByUsername(user.getUsername()) != null) { throw new RuntimeException("用户名已存在"); } // 密码加密 String encryptedPassword = DigestUtils.md5DigestAsHex(user.getPassword().getBytes()); user.setPassword(encryptedPassword); // 保存用户 userDao.insert(user); return userDao.findById(user.getId()); } @Override public User login(String username, String password) { User user = userDao.findByUsername(username); if (user == null) { throw new RuntimeException("用户不存在"); } String encryptedPassword = DigestUtils.md5DigestAsHex(password.getBytes()); if (!user.getPassword().equals(encryptedPassword)) { throw new RuntimeException("密码错误"); } return user; } @Override public User getUserById(Integer id) { return userDao.findById(id); } @Override public boolean updateUser(User user) { return userDao.update(user) > 0; } @Override public boolean checkUsernameExists(String username) { return userDao.findByUsername(username) != null; } }4.2 菜品管理模块
菜品管理包含菜品查询、添加、修改、上下架等功能:
// DishService.java package com.foodorder.service; import com.foodorder.entity.Dish; import java.util.List; public interface DishService { List<Dish> getDishesByCategory(Integer categoryId); List<Dish> getAllDishes(); Dish getDishById(Integer id); boolean addDish(Dish dish); boolean updateDish(Dish dish); boolean updateDishStatus(Integer id, Integer status); } // DishController.java package com.foodorder.web; import com.foodorder.entity.Dish; import com.foodorder.service.DishService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController @RequestMapping("/api/dish") public class DishController { @Autowired private DishService dishService; @GetMapping("/list") public Map<String, Object> getDishList(@RequestParam(required = false) Integer categoryId) { Map<String, Object> result = new HashMap<>(); try { List<Dish> dishes; if (categoryId != null) { dishes = dishService.getDishesByCategory(categoryId); } else { dishes = dishService.getAllDishes(); } result.put("success", true); result.put("data", dishes); } catch (Exception e) { result.put("success", false); result.put("message", e.getMessage()); } return result; } @PostMapping("/add") public Map<String, Object> addDish(@RequestBody Dish dish) { Map<String, Object> result = new HashMap<>(); try { boolean success = dishService.addDish(dish); result.put("success", success); result.put("message", success ? "添加成功" : "添加失败"); } catch (Exception e) { result.put("success", false); result.put("message", e.getMessage()); } return result; } }4.3 订单管理模块
订单管理是系统的核心功能,包含下单、支付、取消等操作:
// OrderService.java package com.foodorder.service; import com.foodorder.entity.Order; import java.util.List; public interface OrderService { Order createOrder(Order order); Order getOrderById(Integer id); List<Order> getOrdersByUserId(Integer userId); boolean cancelOrder(Integer orderId); boolean payOrder(Integer orderId); } // OrderServiceImpl.java package com.foodorder.service.impl; import com.foodorder.dao.OrderDao; import com.foodorder.dao.OrderItemDao; import com.foodorder.entity.Order; import com.foodorder.entity.OrderItem; import com.foodorder.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.Date; import java.util.List; import java.util.UUID; @Service @Transactional public class OrderServiceImpl implements OrderService { @Autowired private OrderDao orderDao; @Autowired private OrderItemDao orderItemDao; @Override public Order createOrder(Order order) { // 生成订单号 String orderNo = generateOrderNo(); order.setOrderNo(orderNo); order.setCreateTime(new Date()); order.setStatus(0); // 待支付 // 计算总金额 BigDecimal totalAmount = BigDecimal.ZERO; for (OrderItem item : order.getOrderItems()) { totalAmount = totalAmount.add(item.getPrice().multiply(new BigDecimal(item.getQuantity()))); } order.setTotalAmount(totalAmount); // 保存订单 orderDao.insert(order); // 保存订单明细 for (OrderItem item : order.getOrderItems()) { item.setOrderId(order.getId()); orderItemDao.insert(item); } return orderDao.findById(order.getId()); } @Override public Order getOrderById(Integer id) { Order order = orderDao.findById(id); if (order != null) { order.setOrderItems(orderItemDao.findByOrderId(id)); } return order; } @Override public List<Order> getOrdersByUserId(Integer userId) { List<Order> orders = orderDao.findByUserId(userId); for (Order order : orders) { order.setOrderItems(orderItemDao.findByOrderId(order.getId())); } return orders; } @Override public boolean cancelOrder(Integer orderId) { Order order = orderDao.findById(orderId); if (order == null || order.getStatus() != 0) { return false; } order.setStatus(2); // 已取消 return orderDao.update(order) > 0; } @Override public boolean payOrder(Integer orderId) { Order order = orderDao.findById(orderId); if (order == null || order.getStatus() != 0) { return false; } order.setStatus(1); // 已支付 order.setPayTime(new Date()); return orderDao.update(order) > 0; } private String generateOrderNo() { return "ORDER" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); } }5. 前端Vue.js实现
5.1 前端项目初始化
使用Vue CLI创建前端项目:
# 创建Vue项目 vue create food-order-frontend # 进入项目目录 cd food-order-frontend # 安装必要依赖 npm install element-ui axios vue-router5.2 前端项目结构
src/ ├── components/ # 公共组件 ├── views/ # 页面组件 ├── router/ # 路由配置 ├── api/ # API接口 ├── utils/ # 工具函数 └── assets/ # 静态资源5.3 主要页面组件实现
登录页面组件:
<template> <div class="login-container"> <el-form :model="loginForm" :rules="rules" ref="loginForm" class="login-form"> <h2>网上订餐系统</h2> <el-form-item prop="username"> <el-input v-model="loginForm.username" placeholder="用户名"></el-input> </el-form-item> <el-form-item prop="password"> <el-input v-model="loginForm.password" type="password" placeholder="密码"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="handleLogin" style="width:100%">登录</el-button> </el-form-item> <el-form-item> <el-button @click="handleRegister" style="width:100%">注册</el-button> </el-form-item> </el-form> </div> </template> <script> import { login } from '@/api/user' export default { name: 'Login', data() { return { loginForm: { username: '', password: '' }, rules: { username: [ { required: true, message: '请输入用户名', trigger: 'blur' } ], password: [ { required: true, message: '请输入密码', trigger: 'blur' } ] } } }, methods: { handleLogin() { this.$refs.loginForm.validate(valid => { if (valid) { login(this.loginForm).then(response => { if (response.data.success) { this.$message.success('登录成功') localStorage.setItem('user', JSON.stringify(response.data.data)) this.$router.push('/home') } else { this.$message.error(response.data.message) } }).catch(error => { this.$message.error('登录失败') }) } }) }, handleRegister() { this.$router.push('/register') } } } </script> <style scoped> .login-container { height: 100vh; display: flex; justify-content: center; align-items: center; background-color: #f5f5f5; } .login-form { width: 400px; padding: 30px; background: white; border-radius: 5px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); } </style>菜品列表页面组件:
<template> <div class="dish-container"> <el-row :gutter="20"> <el-col :span="6" v-for="dish in dishList" :key="dish.id"> <el-card class="dish-card"> <img :src="dish.imageUrl" class="dish-image" alt="菜品图片"> <div class="dish-info"> <h3>{{ dish.name }}</h3> <p class="price">¥{{ dish.price }}</p> <p class="description">{{ dish.description }}</p> <el-button type="primary" @click="addToCart(dish)">加入购物车</el-button> </div> </el-card> </el-col> </el-row> </div> </template> <script> import { getDishList } from '@/api/dish' export default { name: 'DishList', data() { return { dishList: [] } }, mounted() { this.loadDishes() }, methods: { loadDishes() { getDishList().then(response => { if (response.data.success) { this.dishList = response.data.data } }) }, addToCart(dish) { this.$store.dispatch('addToCart', dish) this.$message.success('已加入购物车') } } } </script> <style scoped> .dish-card { margin-bottom: 20px; } .dish-image { width: 100%; height: 200px; object-fit: cover; } .dish-info h3 { margin: 10px 0; } .price { color: #ff6b35; font-size: 18px; font-weight: bold; } .description { color: #666; font-size: 14px; margin: 10px 0; } </style>5.4 API接口封装
创建统一的API接口管理文件:
// api/dish.js import request from '@/utils/request' export function getDishList(params) { return request({ url: '/api/dish/list', method: 'get', params }) } export function addDish(data) { return request({ url: '/api/dish/add', method: 'post', data }) } // api/order.js import request from '@/utils/request' export function createOrder(data) { return request({ url: '/api/order/create', method: 'post', data }) } export function getOrderList(params) { return request({ url: '/api/order/list', method: 'get', params }) } // utils/request.js import axios from 'axios' import { Message } from 'element-ui' // 创建axios实例 const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) // 请求拦截器 service.interceptors.request.use( config => { const user = JSON.parse(localStorage.getItem('user') || '{}') if (user.token) { config.headers['Authorization'] = 'Bearer ' + user.token } return config }, error => { return Promise.reject(error) } ) // 响应拦截器 service.interceptors.response.use( response => { const res = response.data if (!res.success) { Message.error(res.message || '请求失败') return Promise.reject(new Error(res.message || 'Error')) } return response }, error => { Message.error(error.message || '请求失败') return Promise.reject(error) } ) export default service6. 系统集成与部署
6.1 前后端联调配置
配置前端代理解决跨域问题:
// vue.config.js module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '' } } } } }6.2 数据库连接优化
在生产环境中,建议使用连接池配置:
# application.properties spring.datasource.url=jdbc:mysql://localhost:3306/food_order?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # 连接池配置 spring.datasource.druid.initial-size=5 spring.datasource.druid.min-idle=5 spring.datasource.druid.max-active=20 spring.datasource.druid.max-wait=600006.3 项目打包部署
前后端分别打包后部署到服务器:
后端打包:
mvn clean package -Dmaven.test.skip=true前端打包:
npm run build7. 常见问题与解决方案
7.1 数据库连接问题
问题现象:启动时报数据库连接失败解决方案:
- 检查MySQL服务是否启动
- 确认数据库连接URL、用户名、密码正确
- 检查防火墙设置,确保3306端口可访问
7.2 跨域问题
问题现象:前端请求后端接口时报CORS错误解决方案:
- 后端配置CORS过滤器
- 使用Nginx反向代理
- 开发环境配置webpack代理
7.3 事务管理问题
问题现象:数据库操作未回滚解决方案:
- 确保使用@Transactional注解
- 检查异常类型,默认只回滚RuntimeException
- 配置事务传播行为
7.4 前端路由问题
问题现象:刷新页面后404解决方案:
- 配置Nginx try_files指令
- 使用history模式时配置fallback
- 检查路由配置是否正确
8. 系统优化建议
8.1 性能优化
- 使用Redis缓存热点数据(如菜品信息、用户信息)
- 数据库查询优化,添加合适索引
- 图片资源使用CDN加速
- 前端组件懒加载,减少首屏加载时间
8.2 安全优化
- 密码加密存储(使用BCrypt等强哈希算法)
- API接口添加权限验证
- 防止SQL注入(MyBatis使用#{}占位符)
- XSS攻击防护(前端输入过滤)
8.3 功能扩展
- 添加第三方登录(微信、支付宝)
- 集成支付功能(支付宝、微信支付)
- 实现推荐算法(基于用户历史订单)
- 添加后台管理系统
- 支持多种配送方式
通过以上完整的实现方案,我们可以构建一个功能完备、性能稳定的网上订餐管理系统。在实际开发过程中,建议根据具体业务需求进行调整和优化,确保系统能够满足实际的运营需求。