SSM+Vue网上订餐系统开发:高并发订单与库存一致性实战
2026/9/13 2:17:25 网站建设 项目流程

网上订餐管理系统是典型的电商类应用,涉及用户端下单、商家端管理、后台运营等多个角色。实际项目中,这类系统需要处理高并发订单、库存扣减、支付对接、配送跟踪等复杂场景。本文将以 SSM(Spring+SpringMVC+MyBatis)框架为基础,结合 MySQL 数据库和 Vue 前端,从零搭建一个具备完整业务流程的网上订餐管理系统。

适合已经掌握 Java Web 基础,但缺乏完整项目经验的开发者。通过本文,你将理解多模块系统如何划分、前后端数据如何交互、订单状态机如何设计,以及在高并发场景下如何保证数据一致性。

1. 系统架构设计与技术选型理由

1.1 为什么选择 SSM 而不是 Spring Boot

SSM 框架组合在传统企业级项目中仍然广泛使用,尤其适合需要深度定制化配置的场景。与 Spring Boot 的自动配置相比,SSM 需要手动整合各个组件,但这反而能让开发者更清楚框架之间的依赖关系和数据流动路径。

Spring 负责业务对象管理和事务控制,SpringMVC 处理 Web 层请求分发,MyBatis 作为数据持久层框架,通过 XML 或注解方式管理 SQL 映射。这种分层架构让职责清晰,便于后期维护和扩展。

1.2 数据库选型与表结构设计要点

MySQL 5.7 或 8.0 版本都能满足订餐系统的需求,关键是要合理设计表结构和索引。订单相关表需要重点考虑读写性能,菜单和店铺信息这类读多写少的表可以适当增加缓存层。

核心表至少包括:

  • 用户表(user):账号、密码、联系方式、地址等
  • 店铺表(shop):名称、描述、配送范围、营业状态
  • 商品表(product):名称、价格、库存、分类、图片
  • 订单表(orders):订单号、用户ID、总金额、状态、创建时间
  • 订单项表(order_item):订单ID、商品ID、数量、单价

1.3 前端技术栈的考虑因素

Vue 2.x 或 3.x 都可以作为前端框架,重点在于组件化开发和数据绑定机制。订餐系统需要频繁更新页面状态(如购物车数量、订单状态),Vue 的响应式系统能有效简化这类交互逻辑。

对于移动端兼容性,可以使用 Vant 或 Mint UI 等移动端组件库;如果侧重 PC 端,Element UI 是更合适的选择。前后端分离架构下,需要配置代理解决跨域问题,并统一接口响应格式。

2. 开发环境准备与项目初始化

2.1 基础环境要求与版本确认

在开始编码前,需要确保本地环境包含以下组件:

环境组件推荐版本验证命令备注
JDK1.8 或 11java -version避免使用过高版本,防止兼容性问题
Maven3.6+mvn -v统一依赖管理
MySQL5.7+mysql --version需开启 InnoDB 引擎
Tomcat8.5+访问 http://localhost:8080或使用内嵌服务器
Node.js14+node -vVue 脚手架运行环境

项目初始化阶段最容易出现版本冲突,特别是 Spring 相关依赖。建议使用 Maven 的 dependencyManagement 统一管理框架版本:

<properties> <spring.version>5.3.18</spring.version> <mybatis.version>3.5.9</mybatis.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-framework-bom</artifactId> <version>${spring.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

2.2 数据库初始化脚本

创建数据库和基础表结构时,要注意字符集、存储引擎和默认值设置。以下为核心表的创建示例:

CREATE DATABASE IF NOT EXISTS `food_order` DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; USE `food_order`; CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录账号', `password` varchar(100) NOT NULL COMMENT '加密密码', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; CREATE TABLE `product` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '商品名称', `price` decimal(10,2) NOT NULL COMMENT '当前售价', `stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存数量', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1上架 0下架', `shop_id` int(11) NOT NULL COMMENT '所属店铺', PRIMARY KEY (`id`), KEY `idx_shop_status` (`shop_id`,`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';

2.3 SSM 框架整合配置

SSM 整合的关键在于配置文件的正确衔接。web.xml 中需要配置 Spring 上下文加载器和 SpringMVC 前端控制器:

<!-- web.xml --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>

Spring 主配置文件 applicationContext.xml 需要完成数据源、事务管理和 MyBatis 的集成:

<!-- 数据源配置 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="maxActive" value="20"/> </bean> <!-- 事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </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.food.order.entity"/> </bean> <!-- Mapper 接口扫描 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.food.order.dao"/> </bean>

3. 核心业务模块实现

3.1 用户认证与权限控制

订餐系统需要区分普通用户、商家和管理员角色。Spring Security 虽然功能完善,但对于中小型项目可能过于重量级。可以采用基于拦截器的简单权限方案:

@Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (StringUtils.isEmpty(token)) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } User user = jwtUtil.parseToken(token); if (user == null) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } // 将用户信息存入请求上下文 UserContext.setCurrentUser(user); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 请求完成后清理上下文,防止内存泄漏 UserContext.clear(); } }

在 SpringMVC 配置中注册拦截器,并排除登录、注册等公开接口:

<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/api/**"/> <mvc:exclude-mapping path="/api/user/login"/> <mvc:exclude-mapping path="/api/user/register"/> <bean class="com.food.order.interceptor.AuthInterceptor"/> </mvc:interceptor> </mvc:interceptors>

3.2 商品浏览与购物车管理

商品列表查询需要支持分页、分类筛选和排序。PageHelper 是 MyBatis 分页的常用方案:

@RestController @RequestMapping("/api/product") public class ProductController { @Autowired private ProductService productService; @GetMapping("/list") public ApiResponse listProducts( @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) Integer categoryId, @RequestParam(required = false) String orderBy) { // 分页参数 PageHelper.startPage(page, size); // 构建查询条件 ProductQuery query = new ProductQuery(); query.setCategoryId(categoryId); query.setOrderBy(orderBy); List<Product> products = productService.findByQuery(query); PageInfo<Product> pageInfo = new PageInfo<>(products); return ApiResponse.success(pageInfo); } }

购物车数据可以存储在 Redis 中,提高读写性能并减轻数据库压力。购物车操作需要保证原子性:

@Service public class CartService { @Autowired private RedisTemplate<String, Object> redisTemplate; public void addToCart(Long userId, Long productId, Integer quantity) { String key = "cart:" + userId; // 使用 Lua 脚本保证原子操作 String script = "local current = redis.call('HGET', KEYS[1], ARGV[1]) " + "local newQuantity = tonumber(ARGV[2]) " + "if current then newQuantity = newQuantity + tonumber(current) end " + "redis.call('HSET', KEYS[1], ARGV[1], newQuantity) " + "return newQuantity"; redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(key), productId.toString(), quantity.toString()); } }

3.3 订单创建与库存扣减

订单创建是系统最核心也最复杂的业务流程,需要在高并发下保证数据一致性。典型的问题包括超卖、重复下单和库存不一致。

@Service @Transactional(rollbackFor = Exception.class) public class OrderService { public Order createOrder(OrderRequest orderRequest) { Long userId = UserContext.getCurrentUser().getId(); // 1. 验证商品信息和库存 List<OrderItem> items = validateProducts(orderRequest.getItems()); // 2. 计算总金额 BigDecimal totalAmount = calculateTotalAmount(items); // 3. 生成订单号(时间戳+随机数+用户ID哈希) String orderNo = generateOrderNo(userId); // 4. 创建订单主体 Order order = new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setTotalAmount(totalAmount); order.setStatus(OrderStatus.WAITING_PAYMENT.getCode()); orderMapper.insert(order); // 5. 保存订单明细 for (OrderItem item : items) { item.setOrderId(order.getId()); orderItemMapper.insert(item); // 6. 扣减库存(悲观锁或乐观锁) int rows = productMapper.decreaseStock(item.getProductId(), item.getQuantity()); if (rows == 0) { throw new BusinessException("库存不足,下单失败"); } } return order; } }

库存扣减的 SQL 语句要保证原子性,避免超卖:

<!-- ProductMapper.xml --> <update id="decreaseStock"> UPDATE product SET stock = stock - #{quantity} WHERE id = #{productId} AND stock >= #{quantity} </update>

3.4 支付状态回调处理

支付成功后,第三方支付平台会异步通知系统更新订单状态。这类接口需要处理幂等性和安全性:

@RestController @RequestMapping("/api/payment") public class PaymentController { @PostMapping("/callback/{channel}") public String paymentCallback(@PathVariable String channel, HttpServletRequest request) { // 1. 验证签名防止伪造请求 if (!verifySignature(request, channel)) { return "FAIL"; } // 2. 解析回调参数 String orderNo = request.getParameter("order_no"); String tradeNo = request.getParameter("trade_no"); BigDecimal amount = new BigDecimal(request.getParameter("amount")); // 3. 处理订单状态更新(幂等处理) try { paymentService.handlePaymentSuccess(orderNo, tradeNo, amount); return "SUCCESS"; } catch (Exception e) { log.error("支付回调处理失败,orderNo: {}", orderNo, e); return "FAIL"; } } }

4. 前端 Vue 项目搭建与关键组件

4.1 Vue 项目结构规划

前端项目采用标准的 Vue CLI 创建,关键目录结构如下:

src/ ├── api/ # 接口请求封装 ├── components/ # 可复用组件 ├── views/ # 页面组件 ├── router/ # 路由配置 ├── store/ # 状态管理(Vuex) ├── utils/ # 工具函数 └── assets/ # 静态资源

API 请求层使用 Axios 封装,统一处理错误和权限验证:

// api/request.js import axios from 'axios' const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) // 请求拦截器:添加 token service.interceptors.request.use( config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }, error => { return Promise.reject(error) } ) // 响应拦截器:统一处理错误 service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { // 处理业务错误 return Promise.reject(new Error(res.message || 'Error')) } else { return res } }, error => { // 处理 HTTP 错误 return Promise.reject(error) } ) export default service

4.2 购物车页面数据流管理

购物车页面需要实时显示商品数量、价格计算和库存验证。使用 Vuex 管理全局状态:

// store/modules/cart.js const state = { items: [], totalPrice: 0, totalCount: 0 } const mutations = { ADD_ITEM(state, product) { const existing = state.items.find(item => item.id === product.id) if (existing) { existing.quantity += 1 } else { state.items.push({ ...product, quantity: 1 }) } updateTotals(state) }, UPDATE_QUANTITY(state, { productId, quantity }) { const item = state.items.find(item => item.id === productId) if (item) { item.quantity = quantity updateTotals(state) } } } function updateTotals(state) { state.totalCount = state.items.reduce((sum, item) => sum + item.quantity, 0) state.totalPrice = state.items.reduce((sum, item) => sum + (item.price * item.quantity), 0) } export default { namespaced: true, state, mutations }

4.3 订单页面与支付流程

订单确认页面需要展示配送地址、商品清单和价格明细。支付流程涉及多个状态切换:

<template> <div class="order-confirm"> <h3>订单确认</h3> <!-- 配送地址 --> <address-card :address="selectedAddress" @change="showAddressPicker = true"/> <!-- 商品列表 --> <div class="product-list"> <product-item v-for="item in cartItems" :key="item.id" :product="item"/> </div> <!-- 价格明细 --> <price-detail :items="cartItems" :deliveryFee="deliveryFee"/> <!-- 支付方式选择 --> <payment-method v-model="paymentMethod"/> <div class="actions"> <van-button type="primary" block @click="submitOrder"> 确认支付 ¥{{ totalAmount }} </van-button> </div> </div> </template> <script> export default { data() { return { selectedAddress: null, paymentMethod: 'wechat', deliveryFee: 5.00 } }, computed: { cartItems() { return this.$store.state.cart.items }, totalAmount() { const productTotal = this.$store.getters['cart/totalPrice'] return productTotal + this.deliveryFee } }, methods: { async submitOrder() { try { const orderData = { addressId: this.selectedAddress.id, items: this.cartItems, paymentMethod: this.paymentMethod } const result = await this.$api.order.create(orderData) // 跳转到支付页面 this.$router.push(`/payment/${result.orderNo}`) } catch (error) { this.$notify({ type: 'danger', message: '下单失败:' + error.message }) } } } } </script>

5. 常见问题排查与性能优化

5.1 订单相关异常处理

订单系统常见的异常场景和解决方案:

问题现象可能原因排查方法解决方案
库存扣减成功但订单创建失败事务未正确配置检查 @Transactional 注解和异常类型确保异常能触发回滚,配置 rollbackFor
同一订单重复支付支付回调处理非幂等检查订单状态更新逻辑在更新前判断当前状态,避免重复处理
订单查询缓慢缺少合适索引或存在慢查询使用 EXPLAIN 分析 SQL 执行计划为 order_no、user_id、create_time 等字段加索引
购物车数据丢失Redis 过期或内存不足检查 Redis 内存使用情况和过期时间调整过期时间,设置内存淘汰策略

5.2 数据库性能优化策略

针对订餐系统的读写特点,可以采取以下优化措施:

  1. 读写分离:订单写入操作频繁,可以将查询路由到从库
  2. 分表分库:按时间或用户ID哈希对订单表进行水平拆分
  3. 缓存策略:商品信息、店铺信息等读多写少的数据加入 Redis 缓存
  4. 索引优化:为常用查询条件建立复合索引,避免全表扫描

查询订单列表的优化示例:

-- 为常用查询条件创建复合索引 ALTER TABLE orders ADD INDEX idx_user_status (user_id, status); ALTER TABLE orders ADD INDEX idx_create_time (create_time DESC); -- 分页查询优化:避免使用 OFFSET SELECT * FROM orders WHERE user_id = 123 AND status = 1 ORDER BY create_time DESC LIMIT 10; -- 下一页查询:使用游标方式 SELECT * FROM orders WHERE user_id = 123 AND status = 1 AND create_time < '2023-01-01 00:00:00' ORDER BY create_time DESC LIMIT 10;

5.3 前端性能优化要点

Vue 项目在打包部署时需要注意以下性能问题:

  1. 路由懒加载:将不同页面对应的组件分割成不同的代码块
// router/index.js const Order = () => import('@/views/Order.vue') const Payment = () => import('@/views/Payment.vue') const routes = [ { path: '/order', component: Order }, { path: '/payment', component: Payment } ]
  1. 组件按需引入:UI 库只引入使用的组件
// 按需引入 Vant 组件 import { Button, List, Cell } from 'vant' const app = createApp(App) app.use(Button).use(List).use(Cell)
  1. 图片资源优化:使用 WebP 格式,实现懒加载和响应式图片
<template> <img v-lazy="product.image" :alt="product.name" class="product-image"> </template>

6. 生产环境部署注意事项

6.1 后端服务部署配置

生产环境部署需要关注日志、监控和故障恢复:

# application-prod.properties # 数据库连接池配置 spring.datasource.druid.initial-size=5 spring.datasource.druid.min-idle=5 spring.datasource.druid.max-active=20 spring.datasource.druid.test-while-idle=true # MyBatis 配置 mybatis.configuration.map-underscore-to-camel-case=true mybatis.configuration.cache-enabled=false # 日志配置 logging.level.com.food.order.dao=DEBUG logging.file.name=/app/logs/food-order.log logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n # 文件上传限制 spring.servlet.multipart.max-file-size=10MB spring.servlet.multipart.max-request-size=10MB

6.2 前端项目构建优化

Vue 项目生产构建需要配置资源压缩和 CDN 加速:

// vue.config.js module.exports = { publicPath: process.env.NODE_ENV === 'production' ? 'https://cdn.yourdomain.com/food-order/' : '/', configureWebpack: { optimization: { splitChunks: { chunks: 'all', cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendors', priority: 10, chunks: 'initial' } } } } }, chainWebpack: config => { // 压缩图片 config.module .rule('images') .use('image-webpack-loader') .loader('image-webpack-loader') .options({ bypassOnDebug: true }) } }

6.3 安全防护措施

线上环境必须加强安全防护,防止常见攻击:

  1. SQL 注入防护:使用 MyBatis 参数绑定,避免字符串拼接
  2. XSS 攻击防护:对用户输入进行转义,设置 HttpOnly Cookie
  3. CSRF 防护:添加 Token 验证,重要操作使用二次确认
  4. 接口限流:对登录、下单等接口实施限流策略
// 使用注解实现接口限流 @RestController public class OrderController { @RateLimit(key = "createOrder", limit = 10, period = 60) @PostMapping("/api/order/create") public ApiResponse createOrder(@RequestBody OrderRequest request) { // 业务逻辑 } }

网上订餐管理系统的核心价值在于业务流程的完整性和数据一致性保障。实际开发中,除了完成基本功能外,还需要重点关注异常处理、日志记录和监控告警。建议在本地环境完全测试通过后再部署到生产环境,特别是支付回调、库存扣减等关键流程要进行充分的压力测试和异常场景测试。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询