1. 项目概述校园外卖服务系统技术架构解析这个基于SpringBootVue3MyBatis的校园外卖系统是我去年为某高校信息化建设项目开发的核心平台。系统采用经典的前后端分离架构后端使用SpringBoot 2.7 MyBatis 3.5前端采用Vue3 Element Plus数据库选用MySQL 8.0。整套方案在保证性能的同时特别考虑了校园场景下的高并发订餐需求和食品安全监管要求。提示校园外卖系统与商业平台的最大区别在于需要整合校园卡支付、宿舍楼栋配送等特殊场景需求系统主要包含三大模块用户端学生订餐、支付、评价功能Vue3实现响应式界面商户端餐饮商家入驻、菜品管理、订单处理SpringBoot提供REST API管理端数据统计、食品安全监管MyBatis动态SQL实现复杂报表2. 核心技术栈选型与配置2.1 SpringBoot后端工程搭建使用IDEA 2022创建SpringBoot项目时我推荐以下关键依赖配置dependencies !-- Web核心 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis整合 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.2.2/version /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- 校园卡支付对接必备 -- dependency groupIdorg.apache.httpcomponents/groupId artifactIdhttpclient/artifactId version4.5.13/version /dependency /dependencies数据库连接池配置示例application.ymlspring: datasource: url: jdbc:mysql://localhost:3306/campus_delivery?useSSLfalseserverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 20 connection-timeout: 300002.2 Vue3前端工程最佳实践通过Vite创建Vue3项目能获得更好的开发体验npm create vitelatest campus-delivery-frontend --template vue关键依赖安装npm install element-plus axios vue-router4 piniaaxios拦截器配置示例src/utils/request.jsimport axios from axios const service axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API, timeout: 15000 }) // 请求拦截器 - 处理校园卡token service.interceptors.request.use(config { const token localStorage.getItem(campus_token) if (token) { config.headers[X-Auth-Token] token } return config }) // 响应拦截器 - 统一处理401状态 service.interceptors.response.use( response response.data, error { if (error.response.status 401) { router.push(/login) } return Promise.reject(error) } )3. 核心业务模块实现细节3.1 订单状态机设计MyBatis实现校园外卖的订单状态流转比普通电商更复杂需要处理食堂备餐、配送员接单、楼栋配送等特殊状态。我在MyBatis中采用状态模式实现// 订单状态枚举 public enum OrderStatus { UNPAID(0, 待支付), PAID(1, 已支付), PREPARING(2, 备餐中), DELIVERING(3, 配送中), COMPLETED(4, 已完成), CANCELLED(-1, 已取消); // 省略getter/setter } // Mapper接口 Mapper public interface OrderMapper { Update(UPDATE orders SET status#{status} WHERE order_id#{orderId}) int updateStatus(Param(orderId) String orderId, Param(status) OrderStatus status); // 动态SQL查询 SelectProvider(type OrderSqlBuilder.class, method buildQueryOrdersSql) ListOrder queryOrders(OrderQueryCondition condition); } // 动态SQL构造器 public class OrderSqlBuilder { public String buildQueryOrdersSql(OrderQueryCondition condition) { return new SQL() {{ SELECT(*); FROM(orders); if (condition.getUserId() ! null) { WHERE(user_id #{userId}); } if (condition.getStatus() ! null) { WHERE(status #{status.value}); } ORDER_BY(create_time DESC); }}.toString(); } }3.2 食堂档口管理功能校园场景下需要特别处理食堂档口的营业时间管理和菜品库存// 档口营业时间校验逻辑 Service public class StallServiceImpl implements StallService { Override public boolean isOperating(Long stallId) { Stall stall stallMapper.selectById(stallId); LocalTime now LocalTime.now(); // 检查是否在营业时间段内 return !now.isBefore(stall.getOpenTime()) !now.isAfter(stall.getCloseTime()) stall.getStatus() 1; } // 使用Transactional处理库存扣减 Transactional(rollbackFor Exception.class) public void reduceInventory(Long dishId, int quantity) { Dish dish dishMapper.selectById(dishId); if (dish.getInventory() quantity) { throw new BusinessException(库存不足); } dishMapper.updateInventory(dishId, dish.getInventory() - quantity); } }4. 安全防护与性能优化4.1 SQL注入防护方案针对MyBatis使用中的SQL注入风险我采取了以下防护措施严格使用#{}占位符发现项目中有同学误用${}导致漏洞!-- 错误示范 -- select idfindByDangerous parameterTypeString resultTypeUser SELECT * FROM users WHERE username ${value} /select !-- 正确做法 -- select idfindBySafe parameterTypeString resultTypeUser SELECT * FROM users WHERE username #{value} /select添加MyBatis插件拦截危险SQLIntercepts(Signature(type StatementHandler.class, methodprepare, args{Connection.class, Integer.class})) public class SqlInjectionInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler handler (StatementHandler) invocation.getTarget(); String sql handler.getBoundSql().getSql(); if (sql.contains(sleep() || sql.matches(.*(drop|alter)\\stable.*)) { throw new SQLException(检测到危险SQL操作); } return invocation.proceed(); } }4.2 高并发场景优化针对中午订餐高峰期的并发问题我实施了以下方案使用Redis缓存热门档口数据Service public class StallServiceImpl implements StallService { private final RedisTemplateString, Stall redisTemplate; Cacheable(value stalls, key #stallId) public Stall getStallById(Long stallId) { return stallMapper.selectById(stallId); } CachePut(value stalls, key #stall.id) public Stall updateStall(Stall stall) { stallMapper.updateById(stall); return stall; } }订单创建采用分布式锁public class OrderServiceImpl implements OrderService { private final RedissonClient redissonClient; public String createOrder(OrderDTO orderDTO) { RLock lock redissonClient.getLock(order_lock: orderDTO.getUserId()); try { if (lock.tryLock(3, 10, TimeUnit.SECONDS)) { // 订单创建核心逻辑 return doCreateOrder(orderDTO); } throw new BusinessException(操作过于频繁); } finally { lock.unlock(); } } }5. 校园特色功能实现5.1 校园卡支付对接与学校一卡通系统的对接是核心难点我们采用RSA加密签名验证方案public class CampusCardService { private static final String CARD_API https://card.yourschool.edu.cn/pay; public boolean pay(String studentId, BigDecimal amount) { MapString, String params new HashMap(); params.put(sid, studentId); params.put(amount, amount.toString()); params.put(timestamp, String.valueOf(System.currentTimeMillis())); // 生成签名 String sign generateRSASign(params); params.put(sign, sign); // 发送请求 String result HttpUtil.post(CARD_API, params); return SUCCESS.equals(result); } private String generateRSASign(MapString, String params) { // 省略具体实现 } }5.2 宿舍楼栋配送系统针对校园场景设计的配送算法public class DeliveryScheduler { public ListDeliveryTask schedule(ListOrder orders) { // 按楼栋分组订单 MapString, ListOrder buildingGroups orders.stream() .collect(Collectors.groupingBy(Order::getBuilding)); // 生成配送任务 return buildingGroups.entrySet().stream() .map(entry - new DeliveryTask( entry.getKey(), entry.getValue(), calculateOptimalPath(entry.getKey()) )) .collect(Collectors.toList()); } private ListString calculateOptimalPath(String building) { // 基于校园地图数据计算最优路径 } }6. 部署与监控方案6.1 多环境部署配置使用SpringBoot Profile实现环境隔离# application-dev.yml spring: datasource: url: jdbc:mysql://dev-db:3306/campus_delivery username: dev_user # application-prod.yml spring: datasource: url: jdbc:mysql://prod-db:3306/campus_delivery username: prod_user password: ${DB_PASSWORD} # 从环境变量读取6.2 Prometheus监控配置添加SpringBoot Actuator和Prometheus支持dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency dependency groupIdio.micrometer/groupId artifactIdmicrometer-registry-prometheus/artifactId /dependencyapplication.yml配置management: endpoints: web: exposure: include: health,info,prometheus metrics: tags: application: campus-delivery7. 踩坑经验与解决方案MyBatis缓存导致的数据不一致问题现象开启事务后一级缓存导致查询不到最新数据解决方案在方法上添加Transactional(propagation Propagation.REQUIRES_NEW)Vue3路由守卫异步问题// 错误写法会导致导航失效 router.beforeEach((to, from) { const isAuthenticated checkAuth() // 同步检查 if (!isAuthenticated) return /login }) // 正确写法 router.beforeEach(async (to, from) { const isAuthenticated await checkAuth() // 异步检查 if (!isAuthenticated) return /login })MySQL批量插入性能优化!-- MyBatis批量插入配置 -- insert idbatchInsert parameterTypejava.util.List INSERT INTO order_detail (order_id, dish_id, quantity) VALUES foreach collectionlist itemitem separator, (#{item.orderId}, #{item.dishId}, #{item.quantity}) /foreach /insert配合JDBC参数提升性能spring: datasource: hikari: >