ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

校园点餐系统全栈开发实战:Spring Boot+Vue.js构建高并发电商平台

校园点餐系统全栈开发实战:Spring Boot+Vue.js构建高并发电商平台 简介这是一套面向Java Web初学者与高校课程设计者的校园点餐管理系统完整源码基于SpringMyBatis主流技术栈构建专为解决高校食堂线上订餐效率低、排队久、管理粗放等实际问题而开发。资源包共2000个文件涵盖28个核心Java后端类、572个HTML页面、2126个JS交互脚本、694个CSS样式文件及600张PNG图标资源辅以XML配置、JSP视图和WAR部署包完整呈现前后端分离雏形与传统MVC混合架构实践压缩包大小为102.42MB结构清晰含用户登录、菜单浏览、订单提交与状态追踪、后台菜品管理及基础销售统计等全链路功能模块。已有5269人学习下载适合用于Java Web综合实训、毕业设计参考或Spring框架入门实战——开箱即用含完整数据库脚本、Maven依赖配置及Tomcat部署说明可快速本地运行并深入理解业务逻辑分层与MyBatis动态SQL实践。1. 项目缘起为什么我们需要一个校园点餐系统如果你在大学食堂或者校园周边的餐饮店有过排长队的经历或者经历过中午下课高峰期所有外卖平台都显示“配送繁忙”的窘境那你一定能理解这个项目的初衷。校园点餐看似简单背后却是一个涉及集中流量、高峰并发、多角色协作的复杂场景。学生、商家、配送员或自提点、系统管理员每一方都有自己独特且迫切的需求。学生希望快速找到想吃的、不用排队、能提前下单、支付方便、能实时看到订单状态商家希望高效处理订单、减少错单漏单、能管理菜单和库存、分析销售数据对于校园场景可能还需要考虑集中自提点或校内配送团队的管理而系统管理员则需要一个清晰的后台来管理整个生态。市面上的通用外卖平台虽然功能强大但往往无法深度定制校园特有的规则比如仅限校内人员使用、与校园卡支付对接、设置特定的配送区域如仅限宿舍楼等。因此一个量身定制的“校园点餐管理系统”就有了它的生存空间。它不是一个简单的商品展示网站而是一个小型的、垂直领域的电商交易平台。今天我们就来深度拆解这样一个系统的源代码从架构设计、技术选型到核心模块实现手把手带你理解如何从零构建它。我们将基于最常见的Web开发技术栈Spring Boot Vue.js来展开但重点在于思路和设计你可以用任何你熟悉的技术进行替换和实现。2. 系统架构与核心模块设计一个完整的校园点餐管理系统其核心架构通常遵循经典的前后端分离模式。前端负责用户交互和展示后端提供API接口处理业务逻辑和数据持久化。数据库则作为数据的最终存储地。让我们先俯瞰整个系统的全貌。2.1 整体技术栈选型与理由后端技术栈Spring Boot MyBatis-Plus MySQLSpring BootJava领域事实上的微服务标准框架。它提供了极简的配置和快速启动能力内嵌Tomcat服务器让我们能专注于业务开发而无需纠缠于复杂的SSHStrutsSpringHibernate整合。其强大的IoC容器和AOP支持对于管理订单、支付、用户等复杂业务对象及其生命周期至关重要。MyBatis-Plus是对原生MyBatis的增强工具。在点餐系统中存在大量对商品、订单、用户表的CRUD增删改查操作。MyBatis-Plus提供的通用Mapper、条件构造器、分页插件等功能能极大减少重复的SQL编写工作提升开发效率。例如快速实现按时间范围查询订单、按分类分页查询商品等。MySQL成熟、稳定、开源的关系型数据库。点餐系统的数据关系明确用户-订单-商品-商家事务性要求强支付、库存扣减需保证一致性MySQL是经过无数项目验证的可靠选择。对于校园级别的数据量和并发其性能完全足够。前端技术栈Vue.js Element UIVue.js渐进式JavaScript框架易于上手生态丰富。其组件化开发思想非常适合构建点餐系统这种多页面的管理后台和用户端。数据驱动视图的模式使得处理动态变化的订单状态、购物车内容等变得非常直观。Element UI基于Vue.js的桌面端组件库。它为后台管理系统提供了现成的、美观且功能丰富的UI组件如表格、表单、对话框、导航菜单等。使用它能快速搭建出专业的管理界面让我们不必从零开始设计每个按钮和布局。辅助技术Redis用作缓存和会话存储。例如缓存热门商家的菜单减少数据库压力、存储用户购物车临时数据读写快、管理短信验证码或登录令牌设置自动过期。微信支付/支付宝支付SDK集成主流支付渠道。校园场景下微信支付几乎是必选项。WebSocket或SSE用于实现订单状态实时推送。当商家接单、开始配送、送达时能实时通知到用户端页面提升体验。2.2 核心功能模块拆解系统主要围绕四个角色展开学生用户、商家、配送员/自提点管理员、系统超级管理员。其核心模块如下用户端模块注册登录支持手机号验证码、微信一键登录校园场景常用。首页与发现商家列表按距离、评分、销量排序、菜品分类浏览、搜索菜品/商家。商家与商品商家主页、商品详情页含图片、规格、价格、库存。购物车添加/删除商品、修改数量、清空。订单流程生成订单选择地址、配送时间、备注、在线支付调用支付接口、查看订单列表与详情、订单状态跟踪待付款、待接单、制作中、配送中、已完成、已取消、评价与售后。个人中心收货地址管理、我的收藏、优惠券、历史订单。商家端模块店铺管理修改店铺信息、营业状态开业/打烊、公告。商品管理对菜品进行增删改查、设置分类、规格、价格、库存、上传图片。订单管理接收新订单通知、接单/拒单、更改订单状态制作中、可自提/待配送、打印订单小票可选。数据统计查看今日/本周/本月销售额、订单量、热门商品排行。配送端模块若含配送任务大厅查看待配送的订单列表包含取货点、送货地址、距离。接单与状态更新抢单或系统派单、确认取货、开始配送、确认送达。我的行程查看今日已完成配送的订单及收益。管理后台模块全局管理管理所有商家、用户、配送员的账号与信息审核。订单监控查看全平台所有订单具备介入处理异常订单的权限。运营配置管理首页轮播图、公告、优惠券活动。数据分析全平台销售数据报表、用户增长分析。3. 数据库设计与核心表结构解析数据库设计是系统的基石设计的好坏直接影响到后续开发的复杂度和系统性能。这里我们设计最核心的几张表。3.1 用户体系表 (user)这是所有角色的基表通过user_type字段区分身份0-学生1-商家2-配送员99-管理员。CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) DEFAULT NULL COMMENT 用户名可手机号, password varchar(255) DEFAULT NULL COMMENT 加密后的密码, phone varchar(20) NOT NULL UNIQUE COMMENT 手机号, avatar varchar(500) DEFAULT NULL COMMENT 头像URL, user_type tinyint(4) NOT NULL DEFAULT 0 COMMENT 用户类型, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 状态 1-正常 0-禁用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB COMMENT用户表;注意密码存储务必使用BCrypt等强哈希算法加密绝对禁止明文存储。phone字段设为唯一索引既是登录账号也用于后续通知。3.2 商家扩展表 (merchant) 与商品表 (product)商家表需要关联用户ID并存储额外的商业信息。CREATE TABLE merchant ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL UNIQUE COMMENT 关联user.id, shop_name varchar(100) NOT NULL COMMENT 店铺名称, shop_logo varchar(500) DEFAULT NULL, shop_address varchar(200) DEFAULT NULL COMMENT 物理地址用于计算距离, business_status tinyint(4) NOT NULL DEFAULT 1 COMMENT 营业状态 1-营业 0-打烊, avg_delivery_time int(11) DEFAULT NULL COMMENT 平均配送时间分钟, month_sales int(11) DEFAULT 0 COMMENT 月销量用于排序, score decimal(3,2) DEFAULT 5.00 COMMENT 评分, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), FOREIGN KEY (user_id) REFERENCES user (id) ) ENGINEInnoDB COMMENT商家信息表;商品表是系统的核心资产之一设计时需考虑规格如大杯/中杯、库存扣减等问题。CREATE TABLE product ( id bigint(20) NOT NULL AUTO_INCREMENT, merchant_id bigint(20) NOT NULL COMMENT 所属商家, category_id bigint(20) DEFAULT NULL COMMENT 分类ID, name varchar(100) NOT NULL COMMENT 商品名称, description text COMMENT 描述, price decimal(10,2) NOT NULL COMMENT 价格, original_price decimal(10,2) DEFAULT NULL COMMENT 原价用于显示折扣, stock int(11) NOT NULL DEFAULT 0 COMMENT 库存关键字段, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 状态 1-上架 0-下架, image_url varchar(500) DEFAULT NULL COMMENT 主图, sort_order int(11) DEFAULT 0 COMMENT 排序值, specs_json json DEFAULT NULL COMMENT 规格JSON如[{name:规格,values:[大杯,中杯]}], create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), INDEX idx_merchant_status (merchant_id, status), FOREIGN KEY (merchant_id) REFERENCES merchant (id) ) ENGINEInnoDB COMMENT商品表;实操心得specs_json字段使用JSON类型存储商品规格这是一种灵活的方案避免了为规格单独建表带来的复杂关联。前端可以将规格组合渲染成SKU库存量单位后端在生成订单项时需要将选中的规格信息如“大杯”完整地保存下来。stock库存的扣减是并发安全的重灾区必须在业务逻辑层使用数据库乐观锁如update product set stock stock - 1 where id ? and stock 1或悲观锁来保证一致性防止超卖。3.3 订单核心表 (order与order_item)订单设计是电商系统最复杂的一环我们采用主表(order)子表(order_item)的结构。CREATE TABLE order ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL UNIQUE COMMENT 订单号唯一用于支付, user_id bigint(20) NOT NULL COMMENT 下单用户, merchant_id bigint(20) NOT NULL COMMENT 商家, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额, pay_amount decimal(10,2) NOT NULL COMMENT 实付金额, pay_status tinyint(4) NOT NULL DEFAULT 0 COMMENT 支付状态 0-待支付 1-已支付 2-已退款, pay_type tinyint(4) DEFAULT NULL COMMENT 支付方式 1-微信 2-支付宝, pay_time datetime DEFAULT NULL COMMENT 支付时间, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 订单状态 0-待接单 1-已接单/制作中 2-待配送/待自提 3-配送中 4-已完成 5-已取消, delivery_type tinyint(4) NOT NULL DEFAULT 1 COMMENT 配送方式 1-配送 2-自提, delivery_address varchar(500) DEFAULT NULL COMMENT 配送地址, delivery_time datetime DEFAULT NULL COMMENT 期望送达/自提时间, remark varchar(500) DEFAULT NULL COMMENT 用户备注, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), INDEX idx_user_id (user_id), INDEX idx_merchant_id (merchant_id), INDEX idx_create_time (create_time), FOREIGN KEY (user_id) REFERENCES user (id), FOREIGN KEY (merchant_id) REFERENCES merchant (id) ) ENGINEInnoDB COMMENT订单主表;关键点解析order_no订单号必须全局唯一且有一定业务含义如时间戳随机数它是与支付网关交互的唯一凭证。订单状态(status)和支付状态(pay_status)要分开设计因为一个已支付的订单可能被取消退款状态流转更清晰。订单项表记录了订单中包含的具体商品信息。CREATE TABLE order_item ( id bigint(20) NOT NULL AUTO_INCREMENT, order_id bigint(20) NOT NULL COMMENT 关联order.id, product_id bigint(20) NOT NULL COMMENT 商品ID, product_name varchar(200) NOT NULL COMMENT 商品名称快照, product_image varchar(500) DEFAULT NULL COMMENT 商品图片快照, specs varchar(200) DEFAULT NULL COMMENT 购买时选择的规格快照, price decimal(10,2) NOT NULL COMMENT 购买时单价快照, quantity int(11) NOT NULL COMMENT 购买数量, total_price decimal(10,2) NOT NULL COMMENT 该项总价, PRIMARY KEY (id), INDEX idx_order_id (order_id), FOREIGN KEY (order_id) REFERENCES order (id) ON DELETE CASCADE ) ENGINEInnoDB COMMENT订单商品明细表;核心技巧order_item表中存储的商品名称、图片、单价、规格都是“快照”数据。这意味着即使商家后来修改了商品信息历史订单显示的内容也不会改变保证了订单的不可变性。这是电商系统设计的黄金法则之一。4. 后端核心业务逻辑实现与避坑指南有了清晰的数据结构我们开始实现后端的业务逻辑。这里以Spring Boot为例讲解几个最核心的流程。4.1 用户下单与库存扣减的原子性操作这是系统最核心、最容易出错的流程。必须保证“生成订单”和“扣减库存”要么一起成功要么一起失败并且在高并发下不能超卖。Service Transactional(rollbackFor Exception.class) // 声明式事务管理 public class OrderService { Autowired private ProductMapper productMapper; Autowired private OrderMapper orderMapper; Autowired private OrderItemMapper orderItemMapper; Autowired private RedisTemplateString, String redisTemplate; public Order createOrder(CreateOrderRequest request) { // 1. 参数校验略 // 2. 生成唯一订单号 String orderNo generateOrderNo(); // 3. 关键步骤遍历商品预扣库存使用数据库乐观锁 ListOrderItem itemList new ArrayList(); BigDecimal totalAmount BigDecimal.ZERO; for (CartItem cartItem : request.getCartItems()) { Long productId cartItem.getProductId(); Integer quantity cartItem.getQuantity(); // 使用MyBatis-Plus的UpdateWrapper进行乐观锁更新 UpdateWrapperProduct updateWrapper new UpdateWrapper(); updateWrapper.eq(id, productId) .ge(stock, quantity) // 条件库存必须大于等于购买量 .setSql(stock stock - quantity); // 原子性扣减 int updateCount productMapper.update(null, updateWrapper); if (updateCount 0) { // 更新失败说明库存不足或商品不存在 throw new BusinessException(商品【 cartItem.getProductName() 】库存不足或已下架); } // 扣减成功后查询商品最新信息用于快照 Product product productMapper.selectById(productId); // 构建订单项快照... // 计算总价... } // 4. 库存预扣全部成功后创建订单主记录 Order order new Order(); order.setOrderNo(orderNo); order.setUserId(request.getUserId()); order.setMerchantId(request.getMerchantId()); order.setTotalAmount(totalAmount); order.setPayAmount(totalAmount); // 暂不考虑优惠券 order.setStatus(OrderStatusEnum.WAITING_PAYMENT.getCode()); orderMapper.insert(order); // 5. 批量保存订单项 for (OrderItem item : itemList) { item.setOrderId(order.getId()); orderItemMapper.insert(item); } // 6. 将订单号存入Redis设置过期时间如15分钟用于前端轮询支付状态 redisTemplate.opsForValue().set(ORDER_PAY_STATUS: orderNo, UNPAID, 15, TimeUnit.MINUTES); return order; } private String generateOrderNo() { // 格式年月日时分秒6位随机数例如 20240520143025123456 SimpleDateFormat sdf new SimpleDateFormat(yyyyMMddHHmmss); String timePart sdf.format(new Date()); String randomPart String.valueOf((int)((Math.random() * 9 1) * 100000)); return timePart randomPart; } }避坑指南超卖问题上述代码通过update ... where stock quantity的原子SQL操作结合数据库事务有效防止了超卖。这是最常用且可靠的方式。切勿先查询库存再计算因为在查询和更新之间库存可能已被其他请求修改。事务边界Transactional注解确保了从库存扣减到订单创建要么全部成功要么全部回滚。但要注意事务方法不宜过长避免占用数据库连接过久。订单号生成不要在循环或高并发中使用数据库自增ID作为业务订单号建议使用时间戳随机数/序列号的方式并确保唯一性数据库唯一索引兜底。快照存储务必在创建OrderItem时从当前Product对象中复制信息而不是只存一个product_id。这是保证数据追溯性的关键。4.2 支付回调处理与订单状态更新用户支付成功后微信/支付宝会异步通知我们的服务器回调。处理回调的逻辑必须保证幂等性即同一笔支付通知无论收到多少次处理结果都一样和安全性验证通知真伪。RestController RequestMapping(/api/pay) public class PayCallbackController { Autowired private OrderService orderService; Autowired private WxPayService wxPayService; // 封装了微信支付验证逻辑 PostMapping(/wx/callback) public String wxPayCallback(HttpServletRequest request) { // 1. 解析微信回调的XML数据 MapString, String callbackMap parseXmlRequest(request); String orderNo callbackMap.get(out_trade_no); // 商户订单号 String transactionId callbackMap.get(transaction_id); // 微信支付订单号 // 2. 验证签名防止伪造通知非常重要 boolean isValid wxPayService.isSignatureValid(callbackMap); if (!isValid) { return generateFailXml(签名验证失败); } // 3. 查询本地订单 Order order orderService.getOrderByNo(orderNo); if (order null) { return generateFailXml(订单不存在); } // 4. 检查订单状态避免重复处理幂等性关键 if (order.getPayStatus() PayStatusEnum.PAID.getCode()) { // 已经支付成功了直接返回成功响应避免重复业务操作 return generateSuccessXml(); } // 5. 校验金额是否一致防止金额被篡改 String totalFee callbackMap.get(total_fee); // 单位是分 BigDecimal callbackAmount new BigDecimal(totalFee).divide(new BigDecimal(100)); if (order.getPayAmount().compareTo(callbackAmount) ! 0) { return generateFailXml(支付金额不符); } // 6. 核心更新订单状态支付成功 boolean updateSuccess orderService.updateOrderAfterPayment(orderNo, transactionId); if (updateSuccess) { // 7. 更新成功后可以触发后续业务发送微信模板消息、更新商家后台订单列表等 sendPaymentSuccessNotification(order); return generateSuccessXml(); } else { return generateFailXml(系统错误); } } private boolean updateOrderAfterPayment(String orderNo, String transactionId) { // 使用乐观锁更新防止并发回调导致状态覆盖 UpdateWrapperOrder updateWrapper new UpdateWrapper(); updateWrapper.eq(order_no, orderNo) .eq(pay_status, PayStatusEnum.UNPAID.getCode()) // 只有待支付状态才能更新 .set(pay_status, PayStatusEnum.PAID.getCode()) .set(pay_time, new Date()) .set(transaction_id, transactionId) .set(status, OrderStatusEnum.WAITING_MERCHANT.getCode()); // 支付成功等待商家接单 int rows orderMapper.update(null, updateWrapper); return rows 0; } }核心技巧幂等性通过判断订单当前支付状态只有未支付时才进行后续业务处理。这是防止因网络重发导致重复发货、重复增加积分的根本方法。签名验证必须使用支付平台提供的公钥或密钥验证回调请求的签名确保请求来自可信的支付平台而不是黑客伪造的。金额校验对比回调金额与订单金额防止恶意用户支付0.01元却修改回调参数冒充大额支付。乐观锁更新更新订单状态时带上旧状态作为条件利用数据库行锁保证并发安全。4.3 商家接单与订单状态机订单状态流转是业务逻辑的直观体现。一个清晰的状态机设计能让代码更易维护。public enum OrderStatusEnum { WAITING_PAYMENT(0, 待支付), WAITING_MERCHANT(1, 等待商家接单), PREPARING(2, 制作中), WAITING_PICKUP(3, 待取餐/待配送), DELIVERING(4, 配送中), COMPLETED(5, 已完成), CANCELLED(6, 已取消), REFUNDED(7, 已退款); // ... 构造方法、getter } Service public class OrderStatusService { // 定义允许的状态转换 Map当前状态, Set下一个允许状态 private static final MapInteger, SetInteger STATUS_TRANSITION_MAP new HashMap(); static { STATUS_TRANSITION_MAP.put(WAITING_PAYMENT.getCode(), Set.of(CANCELLED.getCode())); // 待支付只能-取消 STATUS_TRANSITION_MAP.put(WAITING_MERCHANT.getCode(), Set.of(PREPARING.getCode(), CANCELLED.getCode())); // 待接单-制作中/取消 STATUS_TRANSITION_MAP.put(PREPARING.getCode(), Set.of(WAITING_PICKUP.getCode(), CANCELLED.getCode())); // 制作中-待取餐/取消 STATUS_TRANSITION_MAP.put(WAITING_PICKUP.getCode(), Set.of(DELIVERING.getCode(), COMPLETED.getCode())); // 待取餐-配送中/已完成(自提) STATUS_TRANSITION_MAP.put(DELIVERING.getCode(), Set.of(COMPLETED.getCode())); // 配送中-已完成 // COMPLETED, CANCELLED, REFUNDED 是终态不允许再转换 } public void changeOrderStatus(Long orderId, Integer targetStatus, String operator, String remark) { Order order orderMapper.selectById(orderId); Integer currentStatus order.getStatus(); // 1. 检查状态转换是否合法 if (!isValidTransition(currentStatus, targetStatus)) { throw new BusinessException(订单状态转换非法: currentStatus - targetStatus); } // 2. 更新订单状态并记录状态变更日志用于追溯 UpdateWrapperOrder updateWrapper new UpdateWrapper(); updateWrapper.eq(id, orderId).eq(status, currentStatus) // 乐观锁 .set(status, targetStatus); if (targetStatus.equals(COMPLETED.getCode())) { updateWrapper.set(finish_time, new Date()); } int updated orderMapper.update(null, updateWrapper); if (updated 0) { // 3. 插入状态变更日志 OrderStatusLog log new OrderStatusLog(); log.setOrderId(orderId); log.setFromStatus(currentStatus); log.setToStatus(targetStatus); log.setOperator(operator); log.setRemark(remark); log.setCreateTime(new Date()); orderStatusLogMapper.insert(log); // 4. 根据新状态触发后续动作如通知用户 notifyUserAboutStatusChange(order.getUserId(), orderNo, targetStatus); } } private boolean isValidTransition(Integer from, Integer to) { SetInteger allowedNextStatus STATUS_TRANSITION_MAP.get(from); return allowedNextStatus ! null allowedNextStatus.contains(to); } }设计优势将状态流转规则集中管理在STATUS_TRANSITION_MAP中任何状态变更都必须通过changeOrderStatus方法。这使得业务规则清晰可见易于维护和扩展。新增一个状态时只需修改这个Map和枚举即可。同时记录完整的状态变更日志(OrderStatusLog)对于客服排查问题、用户查看订单轨迹至关重要。5. 前端关键功能实现与用户体验优化后端保证了数据和逻辑的可靠性前端则直接决定了用户的使用体验。我们以用户下单流程为例看看前端如何与后端配合。5.1 购物车状态管理使用Vuex/Pinia购物车数据需要在多个页面间共享首页、商品详情页、购物车页、订单确认页使用Vue的状态管理库是最佳实践。// store/modules/cart.js (以Pinia为例) import { defineStore } from pinia export const useCartStore defineStore(cart, { state: () ({ items: [], // 购物车项列表 { productId, name, price, image, specs, quantity } selectedShopId: null, // 当前选中的商家ID校园点餐通常一次只下一家店的单 }), getters: { totalCount: (state) state.items.reduce((sum, item) sum item.quantity, 0), totalPrice: (state) state.items.reduce((sum, item) sum (item.price * item.quantity), 0), // 检查是否为空且所有商品属于同一家店 isValid: (state) { if (state.items.length 0) return false; const shopIds new Set(state.items.map(item item.shopId)); return shopIds.size 1; } }, actions: { // 添加商品到购物车 addItem(product, selectedSpecs , quantity 1) { const existingItemIndex this.items.findIndex(item item.productId product.id item.specs selectedSpecs ); if (existingItemIndex -1) { // 已存在增加数量 this.items[existingItemIndex].quantity quantity; // 可在此处检查库存如果超过库存提示用户 if (this.items[existingItemIndex].quantity product.stock) { // 提示库存不足并恢复数量 this.items[existingItemIndex].quantity product.stock; throw new Error(库存不足); } } else { // 新商品检查是否跨店校园点餐通常禁止 if (this.items.length 0 product.merchantId ! this.items[0].shopId) { // 提示用户是否清空当前购物车添加新店铺商品 // 这里可以根据产品需求决定通常是清空或提示 if (!confirm(添加不同商家的商品将清空当前购物车。是否继续)) { return; } this.clearCart(); } this.items.push({ productId: product.id, shopId: product.merchantId, name: product.name, price: product.price, image: product.imageUrl, specs: selectedSpecs, quantity: quantity }); this.selectedShopId product.merchantId; } // 持久化到本地存储防止页面刷新丢失 this.persistToLocalStorage(); }, // 生成提交订单的请求数据 generateOrderRequest(deliveryInfo) { if (!this.isValid) { throw new Error(购物车无效); } return { merchantId: this.selectedShopId, cartItems: this.items.map(item ({ productId: item.productId, productName: item.name, // 用于后端校验非快照 quantity: item.quantity, specs: item.specs })), deliveryType: deliveryInfo.type, deliveryAddress: deliveryInfo.address, deliveryTime: deliveryInfo.time, remark: deliveryInfo.remark }; }, // 清空购物车下单成功后调用 clearCart() { this.items []; this.selectedShopId null; localStorage.removeItem(cart); }, persistToLocalStorage() { localStorage.setItem(cart, JSON.stringify(this.items)); } } })用户体验要点跨店提示在用户尝试添加第二家店的商品时给予明确提示符合“一次只点一家”的常见场景避免结算时混淆。本地持久化将购物车数据存入localStorage用户即使关闭浏览器再打开购物车内容依然存在提升体验。实时库存校验在增加数量时可以实时或定期从后端同步库存信息避免用户将已售罄的商品加入购物车。5.2 订单状态实时推送WebSocket应用为了让学生实时掌握订单动态如“商家已接单”、“骑手已取餐”使用WebSocket是比定时轮询更高效的方式。// utils/websocket.js class OrderWebSocket { constructor(orderNo) { this.orderNo orderNo; this.ws null; this.messageHandlers []; this.reconnectAttempts 0; this.maxReconnectAttempts 5; } connect() { const wsUrl wss://your-domain.com/ws/order/${this.orderNo}; this.ws new WebSocket(wsUrl); this.ws.onopen () { console.log(WebSocket连接已建立); this.reconnectAttempts 0; // 重置重连次数 }; this.ws.onmessage (event) { const message JSON.parse(event.data); console.log(收到状态更新:, message); // 通知所有注册的消息处理器 this.messageHandlers.forEach(handler handler(message)); // 根据消息类型更新UI例如 // if (message.type STATUS_CHANGED) { // updateOrderStatusUI(message.newStatus); // } else if (message.type DELIVERY_UPDATED) { // updateDeliveryInfoUI(message.riderInfo); // } }; this.ws.onclose (event) { console.log(WebSocket连接关闭, event.code, event.reason); // 非正常关闭且未超过重试次数尝试重连 if (event.code ! 1000 this.reconnectAttempts this.maxReconnectAttempts) { this.reconnect(); } }; this.ws.onerror (error) { console.error(WebSocket错误:, error); }; } reconnect() { this.reconnectAttempts; const delay Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000); // 指数退避 console.log(${delay}ms后尝试第${this.reconnectAttempts}次重连...); setTimeout(() this.connect(), delay); } onMessage(handler) { this.messageHandlers.push(handler); } disconnect() { if (this.ws) { this.ws.close(1000, 用户主动离开); // 1000为正常关闭状态码 this.ws null; } } } // 在订单详情页使用 // import OrderWebSocket from /utils/websocket; // const orderNo 20240520143025123456; // const wsClient new OrderWebSocket(orderNo); // wsClient.onMessage((msg) { // // 更新页面状态例如使用Vue的响应式数据 // this.currentStatus msg.newStatus; // this.$notify({ type: success, message: 订单状态已更新: ${msg.newStatusText} }); // }); // wsClient.connect(); // // 在组件销毁时断开连接 // onBeforeUnmount(() { // wsClient.disconnect(); // });后端WebSocket实现要点以Spring Boot STOMP为例连接鉴权WebSocket连接建立时需要验证用户身份如通过Token确保用户只能订阅自己订单的频道。主题订阅可以为每个订单创建一个唯一的主题如/topic/order/{orderNo}当订单状态变更时后端向该主题广播消息。优雅断开处理客户端异常断开清理相关资源。降级方案考虑到WebSocket的兼容性和稳定性必须提供降级方案。例如在连接失败或某些浏览器不支持时自动切换到定时轮询setInterval来查询订单状态。6. 部署、运维与性能考量一个完整的系统开发完成只是第一步如何让它稳定、高效地运行起来同样重要。6.1 基础部署架构对于校园级别的应用初期可以采用单机部署后期根据压力进行扩展。[用户/商家] --HTTPS/WS-- [Nginx (反向代理/负载均衡)] | v [Spring Boot 应用集群 (2-3个实例)] | v [MySQL (主从)] | v [Redis (缓存)]Nginx作为网关处理静态资源前端打包后的HTML、JS、CSS反向代理到后端的Spring Boot应用实现负载均衡。还可以配置SSL证书启用HTTPS。Spring Boot集群通过Nginx的upstream模块将请求分发到多个后端实例提高并发处理能力和可用性。实例间无状态session可存储在Redis中。MySQL主从主库负责写操作下单、支付从库负责读操作商品浏览、订单查询。通过读写分离提升数据库整体性能。可以使用sharding-jdbc等中间件透明化地实现。Redis缓存热点数据如首页商家列表、商品分类存储用户会话用作分布式锁等。6.2 关键性能优化点数据库层面索引优化为所有常用的查询条件建立索引如order表的(user_id, create_time)、(merchant_id, status)product表的(merchant_id, status)。使用EXPLAIN命令分析慢SQL。查询优化避免SELECT *只查询需要的字段。多表关联查询时注意关联字段是否有索引。分库分表当单表数据量过大如订单表超过千万考虑按时间如按月进行分表。应用层面缓存策略将变化不频繁但访问频繁的数据放入Redis并设置合理的过期时间。例如Cacheable(value merchants, key hot: #page : #size) public ListMerchant getHotMerchants(int page, int size) { // 从数据库查询热门商家 return merchantMapper.selectHotMerchants(page, size); }异步处理对于非实时核心的操作如发送下单成功短信、生成报表、清理临时数据可以放入消息队列如RabbitMQ、RocketMQ或使用Spring的Async注解异步执行避免阻塞主请求线程。连接池正确配置数据库连接池如HikariCP和Redis连接池的参数避免连接泄露和等待。前端层面图片优化商品图片使用WebP格式并配合CDN加速。上传时生成多种尺寸的缩略图列表页使用小图详情页加载原图。代码分割与懒加载使用Vue Router的懒加载和Webpack的动态导入将不同路由对应的组件分割成不同的代码块用户访问时才加载提升首屏速度。API防抖与节流对搜索框输入等频繁触发的事件进行防抖处理减少不必要的API请求。6.3 监控与日志“可观测性”是系统稳定的眼睛。应用日志使用SLF4J Logback合理设置日志级别INFO, ERROR。关键业务节点如订单创建、支付回调、状态变更必须打印日志并包含唯一的业务标识如orderNo方便链路追踪。错误监控集成Sentry或类似平台自动捕获前端和后端的未处理异常并发送告警。健康检查Spring Boot Actuator提供/health,/metrics等端点可用于监控应用状态。业务监控监控核心指标如每分钟订单数、支付成功率、接口平均响应时间。可以使用Prometheus Grafana搭建可视化监控面板。7. 安全与风控措施校园系统虽然用户群体相对固定但安全防线一刻也不能松懈。接口安全HTTPS全站强制使用HTTPS防止数据在传输中被窃听或篡改。参数校验后端对所有入参进行严格校验包括类型、范围、长度防止SQL注入和XSS攻击。推荐使用Hibernate Validator或自定义校验器。SQL注入防护坚持使用MyBatis的#{}预编译占位符绝对禁止字符串拼接SQL。XSS防护对用户输入的内容如商品评价、备注进行转义或过滤后再存储和展示。可以使用Jsoup等库进行HTML清洗。业务安全权限控制使用Spring Security或Shiro实现基于角色的访问控制RBAC。确保学生只能访问自己的订单商家只能管理自己的店铺和订单。防刷与限流对短信验证码接口、下单接口进行限流。例如使用Redis记录手机号或IP在单位时间内的请求次数超过阈值则拒绝或要求图形验证码。支付安全如前所述支付回调必须验证签名和金额。支付成功后订单状态的更新必须保证幂等性。数据脱敏在日志或返回给前端的非必要场景中对用户手机号、身份证号等敏感信息进行脱敏处理如138****1234。数据安全密码加密用户密码必须使用BCrypt、PBKDF2等强哈希算法加盐存储。敏感信息加密用户身份证号如果需要、银行卡号等极度敏感信息应在数据库层面进行加密存储。定期备份制定数据库备份策略如每日全备每小时增量备份并定期演练恢复流程。开发这样一个系统从设计到上线是一个庞大的工程。它不仅仅是对CRUD的练习更是对业务抽象、并发处理、数据一致性、系统架构和用户体验的综合考验。建议从最小可行产品MVP开始先跑通“浏览-加购-下单-支付”的核心流程再逐步迭代商家端、管理后台、配送模块等。在编码过程中多思考边界情况比如库存为0时怎么办支付回调网络超时怎么办你的系统才会更加健壮。最后别忘了写文档和测试它们是你未来维护和扩展时最得力的助手。本文还有配套的精品资源点击获取
返回列表