news 2026/10/20 8:06:56

前后端分离校园外卖系统实战:SpringBoot+Vue+MyBatis全栈开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前后端分离校园外卖系统实战:SpringBoot+Vue+MyBatis全栈开发

前后端分离的校园外卖服务系统,技术栈锁定SpringBoot+Vue+MyBatis+MySQL,很多人第一眼看到这种项目会觉得是“老套路”,但真正自己从零搭一遍才发现,里面能踩的坑一点都不少。这个项目覆盖了用户端点餐、商家端接单、订单流转、地址管理、购物车合并、JWT登录鉴权这些完整闭环,再加上前后端分离架构下的跨域处理、接口联调、打包部署,非常适合用来练手或者作为毕业设计的核心框架。这篇博文我打算把整套系统从设计思路开始,讲到数据库表结构、后端接口实现、前端Vue页面、最后到服务器部署,全程带上源码级别的细节和排错经验,希望对正在做类似校园系统或外卖服务的同学有点实际帮助。

1. 为什么要做校园外卖系统:场景痛点决定功能划分

1.1 校园场景的特殊性

校园和外部的商业外卖场景差别其实很大。首先用户高度集中,宿舍区、教学区、食堂这几个地点覆盖了绝大多数订单,配送范围短,配送时间也相对集中,基本就是你上午课程结束时和晚饭前这两个窗口期单量爆发。其次商家多样,既有食堂窗口,也有校内小餐馆和周边商户,它们的菜品种类、出餐速度、价格区间差异很大,这就对系统的商品分类和商家信息展示提出了要求,而不是简简单单一个商品列表就能糊弄过去的。

还有一个很实际的问题:校园用户的作息规律非常强,下单高峰几乎可以预测到分钟级,这就意味着订单状态流转的效率很重要,学生用户等不了太久,商家端也必须能够快速看到新订单并及时处理。传统的电话点餐和微信群接龙在这个场景下效率极低,商家经常漏单错单,所以才需要一个前后端分离的Web系统来支撑整个点餐和接单流程。

在做需求拆分的时候,我建议按照角色来划分,而不是按照功能模块来划分。这个系统的核心角色是三个:普通学生用户、商家、系统管理员。学生用户关心的是能不能快速找到商家和菜品、加购物车、下单、看订单状态;商家关心的是新订单提醒、更新菜品、修改价格、上架下架商品;管理员关心的则更偏向于用户状态管控、商家入驻审核和数据统计。把角色理清楚之后,再去设计接口和页面就会顺手很多。

1.2 核心功能范围界定

对于一个前后端分离的外卖系统,功能范围不要一上来就铺得很大。大多数校园外卖项目的失败不是功能少,而是功能太多导致后期根本做不完。我当时定的核心功能范围是:

  • 用户端:登录注册、浏览商家列表、按分类查看菜品、加购物车、查看购物车、提交订单、查看订单列表和详情、订单状态更新、收货地址管理
  • 商家端:菜品管理(增删改查)、菜品上下架、订单管理(查看新订单、接单、标记完成)
  • 管理端:用户禁用/启用、商家入驻审核、基础数据统计(用户数、订单数)

支付功能我是建议先不做的,直接用“模拟支付+货到付款”的方式代替。原因很简单,接入真实支付渠道需要商户号、证书、回调域名等等,一套流程下来会把项目周期拖长很多,而且对于学习性质的系统来说,支付环节并没有带来多少架构上的价值。把支付状态设计成一个可以扩展的字段,后续要接支付渠道时改起来也不费劲。

1.3 哪些功能被刻意省略

我特意省略了这几块内容:优惠券系统、订单超时自动取消的定时任务、多商家购物车拆分、管理员后台的图表报表。这些功能不是不需要,而是它们和核心流程的耦合度不高,完全可以在主流程跑通之后再迭代。尤其是订单超时取消,项目后期如果想要优化,可以用SpringBoot的定时任务加上状态判断来处理,核心逻辑就是查询超过一定时间仍未支付的订单,把状态改成已取消并把库存释放,这个不难,但初期实现反而会分散精力。

2. 技术选型:SpringBoot+Vue+MyBatis+MySQL的取舍理由

2.1 后端为什么选SpringBoot

如果从学习成本、生态成熟度、招聘市场需求三个维度去衡量,SpringBoot在Java后端项目里还是最稳妥的选择。版本选择上我建议直接用SpringBoot 2.7.x系列,不要一上来就上SpringBoot 3。原因很现实:3.x版本要求JDK17起步,一些旧版依赖和新版不兼容,网上能搜到的解决方案大部分都是针对2.x的,排错成本明显更高。2.7.x直接配JDK8或者JDK11,在中国绝大多数教学环境和企业老项目里都是通行配置。

SpringBoot带来的最大便利就是自动配置和Starter机制,你只要在pom里引入spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java这些依赖,再配好application.yml里的数据源信息,一个能跑起来的服务基本上就有了。对于校园外卖这样的中小型系统来说,SpringBoot默认内置的Tomcat完全够用,不需要额外部署外置Tomcat,打包成jar直接跑就行。

2.2 前端为什么锁定Vue

Vue的优势在校园项目场景里非常突出:上手快、中文文档齐全、社区解决方案丰富。Vue2和Vue3之间的选择也是很多新手纠结的问题。我的建议是:如果你从零开始写,直接上Vue3加Composition API;如果你是参考别人项目改造,那大概率会遇到Vue2的老代码,也不用急着升级,稳定性优先。定位是学习练手,选择哪种技术版本都行,重点是搞懂数据流和组件通信。

Vue前端通常配合Element UI系列组件库,表格、表单、对话框、分页这些高频组件都有现成的,不需要自己手写样式。整个前端项目通过Vite或者Vue CLI构建,开发环境下用proxy代理解决跨域,生产环境下编译成静态文件交给Nginx托管,这也是前后端分离项目最标准的部署形态。

2.3 MyBatis和MySQL的搭配理由

MyBatis和MySQL的组合在校园类项目里占有率极高。MyBatis相对于Spring Data JPA来说,SQL掌控力更强,复杂多表查询时你可以在XML里直接写联表SQL,即使不会写动态查询和复杂条件,至少排查数据问题时更直观。MySQL作为数据库选型没什么好争论的,免费、轻量、安装简单,主流的云服务器都能轻松跑起来,单表几百万数据量对这个项目来说完全不是瓶颈。

我强烈建议用代码生成器来生成基础的实体类、Mapper接口和XML文件,比如MyBatis Generator,或者直接用IDEA插件。手动去写几十个表的实体和CRUD不仅效率低,而且容易出错。项目重点应该放在业务逻辑的编排和接口的设计上,而不是重复的增删改查代码。

3. 项目结构与数据库设计:地基决定上层建筑

3.1 前后端分离的项目目录结构

前后端分离项目在物理上就是两个完全独立的目录,不要混在一起提交到同一个代码仓库的同一层级。我常用的目录结构是这样的:

campus-delivery/ ├── backend/ # SpringBoot后端 │ ├── src/main/java │ │ └── com/example/delivery │ │ ├── common/ # 通用类:Result、异常处理 │ │ ├── config/ # 配置类:跨域、拦截器 │ │ ├── controller/ # 控制层 │ │ ├── entity/ # 实体类 │ │ ├── mapper/ # MyBatis Mapper接口 │ │ ├── service/ # 业务层接口与实现 │ │ └── util/ # 工具类:JWT、密码加密 │ └── resources │ ├── mapper/ # MyBatis XML映射文件 │ └── application.yml └── frontend/ # Vue前端 ├── src │ ├── api/ # 接口请求封装 │ ├── router/ # 路由配置 │ ├── store/ # 状态管理 │ ├── views/ # 页面组件 │ └── main.js └── vite.config.js

后端采用传统的三层结构controller-service-mapper,这个分层在中小型项目中已经非常成熟。前端按模块拆,api层单独抽出所有请求方法,view层只关注渲染和用户交互,这样团队成员分工时可以各自开发互不阻塞。

3.2 数据库表结构设计概览

数据库设计是这个项目最值得花时间琢磨的地方。我先把核心表列举出来,然后逐个说明设计原因和关键字段。

user用户表:

CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录名', `password` varchar(100) NOT NULL COMMENT 'BCrypt加密后的密码', `phone` varchar(20) DEFAULT NULL, `avatar` varchar(255) DEFAULT NULL, `role` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1-用户 2-商家 3-管理员', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1-正常 0-禁用', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

用户表里用role字段区分三种角色,比单独建管理员表和商家表要简洁很多。因为用户和商家很多基础字段是共通的,比如登录名、手机号、状态,合并在一张表里维护起来更方便。至于商家独有的信息,比如店铺名、营业时间,可以单独建business_info表,通过user_id关联。

business商家信息表:

CREATE TABLE `business` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `name` varchar(100) NOT NULL, `image` varchar(255) DEFAULT NULL, `address` varchar(255) DEFAULT NULL, `notice` varchar(255) DEFAULT NULL COMMENT '商家公告', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1-营业 0-打烊', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

菜品表dish,和商家关联,这里要注意价格字段不要用double,精确到分的场景用decimal更可靠,尤其后续如果涉及优惠计算,浮点数误差会让人非常头疼。非法请求或者脏数据可能导致订单总额计算错误,这一类线上问题通常很难发现。

购物车表cart的设计,很多新手会忽略用户和菜品的唯一约束。同一个用户把同一个菜品加入购物车,应该执行的是更新数量而不是插入新记录。

订单表orders和订单明细表order_detail是核心。订单表存总金额、订单状态、收货地址快照,明细表存下单时的菜品快照。这里有一个非常重要的设计原则:明细表里一定要冗余下单时的菜品名称和价格,不能通过外键去关联菜品表实时查询。因为菜品价格以后可能调整,如果用户下单后商家改了价格,你再去菜品表查历史订单就会得到错误的数据。快照是把当时的交易现场固定下来,这在订单类系统中是通用做法。

3.3 关联查询与索引设计

订单查询场景里最常见的SQL是根据用户id查订单列表,再查这个订单下面的明细。这个场景需要给orders表的user_id加上索引,order_detail表的order_id也加索引。商家端需要查自己名下的菜品、订单,所以dish表的business_id和orders表的business_id也要建索引。别小看这几条索引,校园外卖系统虽然数据量不大,但高峰期并发查询时会明显感受到区别。索引缺失时,MySQL会走全表扫描,数据量一旦上万,查询耗时就会从几毫秒飙到几百毫秒。

另外我建议订单号不用自增id,单独生成一个业务订单号。自增id暴露在接口里其实是一种安全隐患,别人通过修改id就能遍历你的订单数据。业务订单号可以用当前时间加用户id和随机数的组合生成,比如yyyyMMddHHmmss + userId + 四位随机数。我在实际项目中见过更优雅的做法是引入分布式ID组件,但对这个小系统来说,时间戳加随机数的组合已经足够。

4. 后端核心实现:接口设计与业务编排

4.1 统一返回结果与业务异常体系

前后端分离项目中,后端返回给前端的数据一定要有统一的格式。我用的返回结构非常简单,就是一个Result泛型类:

public class Result<T> { private Integer code; // 200-成功 500-失败 401-未登录 private String message; private T data; // 静态方法 success/error }

所有的Controller接口统一返回Result对象,前端axios拦截器里拿到的也是这个结构,判断code是否等于200来决定下一步操作。这样做的好处是,错误处理逻辑可以完全收口,前端不需要为每个接口单独写错误分支,后端出现业务异常时也只需要抛出统一的异常对象。

配合这个Result结构,我还写了一个全局异常处理器,用@RestControllerAdvice注解标记,分别拦截自定义业务异常、参数校验异常、其他兜底异常。尤其是运行时异常,如果没有统一处理,默认会返回SpringBoot的Whitelabel Error Page,前端拿不到任何有效信息,排错就变得非常困难。

4.2 JWT登录鉴权与拦截器配置

登录这块我没有用传统的Session方案,而是用JWT生成令牌返回给前端,前端每次请求在请求头里带上Token,后端通过拦截器校验。选择JWT的理由是它天然适合前后端分离架构,服务端不需要存储会话状态,接口天然支持跨域,而且后面的移动端扩展也能直接复用同一套鉴权逻辑。

JWT工具类主要提供三个方法:根据用户id生成Token、解析Token获取用户id、校验Token是否有效。生成时设置一个合理的过期时间,我的建议是2小时到12小时之间。校园项目里学生使用场景往往集中在白天课间,过期时间太短会导致用户频繁重新登录,太长又会增加安全风险。

拦截器的核心逻辑:

@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (request.getMethod().equals("OPTIONS")) { return true; // 放行预检请求 } String token = request.getHeader("token"); // 校验token,解析失败则返回401提示登录过期 }

拦截器里放行OPTIONS请求这个细节特别重要。跨域请求时,浏览器会先发送一个预检请求,如果拦截器把这个请求拦下来,前端就会报跨域错误,而且排查时容易误判为后端没做跨域处理。这种问题有个很明显的特征:开发环境下接口偶尔能通偶尔报错,刷新页面后第一次请求大概率失败,实际上就是预检请求被拦截了。

密码存储必须加密,我建议用BCrypt。MD5和SHA这类散列算法已经很不安全了,撞库和彩虹表攻击很容易破解。Spring Security的BCryptPasswordEncoder可以单独引入,即使不用Security框架也可以只用它的加密工具类。

4.3 核心业务流程:购物车到生成订单

从购物车提交生成订单是这个项目最核心的业务流程,涉及到事务处理和多个表的联动。我在service层实现这个逻辑时,事务注解@Transactional是必须加上的,否则中间任何一步失败都可能导致数据不一致。

下单流程拆解如下:

  • 第一步,根据购物车id列表,查询当前用户的购物车明细,这里必须校验购物车里的菜品归属当前用户,防止越权下单
  • 第二步,遍历购物车明细,联表查询对应菜品信息,判断菜品是否在售,如果某个菜品已经下架,直接中断下单流程并提示用户
  • 第三步,计算订单总金额,这里踩了一个很常见的坑:金额计算全部用BigDecimal,乘以数量后累加,最后作为订单总金额
  • 第四步,插入订单主表,订单状态设为待支付,收货地址从用户默认地址快照进去
  • 第五步,循环插入订单明细表,记录菜品名称、单价、数量快照
  • 第六步,删除用户对应的购物车记录,订单完成

这个流程里最容易出错的就是事务边界。很多人会把查询购物车和最后删购物车放在同一个方法里,中间业务逻辑穿插网络调用或者耗时操作,导致事务时间过长。MyBatis的默认配置一般不会主动开启Spring事务,如果不加@Transactional,第一步插入了订单但第五步失败了,就会出现只有订单没有明细的脏数据。

4.4 MyBatis与XML映射的实际写法

虽然现在有很多代码生成工具,但核心SQL我还是建议手写。比如订单列表联查商家信息,一句简单的LEFT JOIN就能避免前端再发一个查询商家信息的请求。我在OrderMapper.xml里这样写:

<select id="findOrderWithBusiness" resultType="com.example.delivery.entity.OrderVO"> SELECT o.*, b.name as businessName FROM orders o LEFT JOIN business b ON o.business_id = b.id WHERE o.user_id = #{userId} ORDER BY o.create_time DESC </select>

动态SQL也是MyBatis的强项,商家端订单列表需要按状态筛选,我通常用<if test="status != null">标签来拼接条件。这些写在XML里比用注解嵌入SQL要清晰很多,尤其条件多的时候,后期维护难度完全不同。还有一个细节是resultType和resultMap的选择,字段名和列名不一致时我会习惯用resultMap明确映射,避免因为数据库列名下划线转驼峰没开配置导致字段全是null。

4.5 跨域配置的正确姿势

前后端分离开发时跨域是绕不开的问题。开发环境下Vite或Webpack的devServer里配置proxy代理最简单,实际请求还是同源的。但生产环境下如果后端接口和前端静态页面不在同一个端口,就需要后端主动支持跨域。SpringBoot里我写了一个WebMvcConfigurer配置类,重写addCorsMappings方法,允许的源和请求头全部显式配置。

有一点要提的是,允许的源不要直接用*。如果后端同时支持Token认证,使用*和allowCredentials=true会产生冲突,浏览器会直接拒绝请求。我把origins配置成前端实际部署的域名,或者开发环境的localhost端口,这样既安全又能避免这类隐性问题。

5. 前端Vue实现:页面组织与工程化细节

5.1 页面拆分与路由规划

前端页面我按照角色区分为三个独立区域:用户端、商家端、管理端。路由采用嵌套路由的方式,共用布局组件,侧边栏和导航栏根据当前登录用户的角色显示不同菜单。

用户端的核心页面有:商家列表、菜品浏览、购物车抽屉、订单确认页、订单列表、地址管理。商家端核心页面有:菜品管理、订单管理。管理端的页面相对简单,一个用户列表加一个商家审核页就够用。页面数量控制在十个以内时,Vue Router的配置和维护还是很轻松的,但要提前规划好懒加载,不要所有页面一次性打包进主包,否则首屏加载会很慢。

5.2 Axios封装与Token管理

Axios封装是前端工程化的第一步。我先新建了一个request.js文件,创建axios实例,设置baseURL、超时时间。请求拦截器里从localStorage取Token,加到请求头。响应拦截器里统一判断后端的Result结构,如果code是401就跳转登录页并清除本地Token。这样做的效果是,所有页面代码里只要关心业务数据,不用重复处理登录过期这类横切逻辑。

Token存储位置我建议用localStorage,而不是sessionStorage。虽然两者在安全等级上差不多,但localStorage的持久性对用户体验更友好,用户关闭浏览器再打开时不需要重新登录。当然这只是一个相对合理的选择,如果要做得更安全可以配合登录后的滑动过期机制,但对学习项目来说已经足够。

5.3 组件通信与状态管理

购物车是前端状态管理最典型的需求。加入购物车这个行为可能发生在商家列表页、菜品详情页,购物车数据又需要在下单确认页、底部的购物车栏等多个组件里共享。我最初用组件的props和emit逐层传递,页面一多就非常痛苦,后来改成Pinia统一管理购物车状态,从接口拉取的购物车数据存放在store里,任何组件需要时直接引用,修改时统一调用store的action。这才算真正把前端状态理清楚。

需要提醒的是,购物车数据的同步时机要明确。我的方案是登录后拉取一次,每次加购成功后更新本地store,同时调用后端接口同步。这样页面上即时反馈,后端数据也不会丢失。

5.4 前端路由守卫实现权限控制

Vue Router的beforeEach路由守卫是控制页面访问权限的关键点。未登录用户访问需要登录的页面时,直接重定向到登录页并带上redirect参数,登录成功再跳回原页面。已登录用户但角色不匹配时,跳转到自己的首页。这个逻辑不复杂但很实用,而且必须在每个需要鉴权的页面的路由meta里标记需要的角色,否则任何人都能通过URL直接访问商家管理界面。

6. 完整部署教程:从源码到线上可访问

6.1 环境准备清单

部署一套前后端分离项目,需要准备好这些环境:Linux服务器(推荐CentOS 7以上或Ubuntu 20.04)、JDK8或JDK11、Maven 3.6以上、MySQL 5.7或8.0、Node.js 16以上、Nginx。这里有一个实际建议:JDK和Maven用yum或者apt安装有时版本不理想,更推荐用tar包解压配置环境变量,主动权完全在自己手里。

部署前先做好两件事:第一,修改MySQL的root密码并创建项目的专用数据库账号,不要把root密码直接写在项目配置文件里;第二,把前端的接口地址baseURL从开发环境的localhost改成生产环境的后端域名或IP。这一步经常被忽略,导致前端打包后页面能打开,但接口全部请求到本地,看起来就是页面空白和数据加载失败。

6.2 数据库初始化与配置

数据库表结构准备好之后,还有一个非常容易踩坑的地方:时区配置。MySQL连接串里的serverTimezone参数,本地开发可能用的是Asia/Shanghai,服务器上如果配错或者不配,数据库连接会直接报错,即使连上也可能出现插入时间少了8小时的情况。我的建议是统一在JDBC连接串里加上serverTimezone=Asia/Shanghai&useSSL=false&characterEncoding=utf8,省掉很多莫名其妙的时间问题。

初始化表结构时,如果用的MySQL8,注意utf8mb4字符集要全链路保持一致。数据库、表、连接串三个地方都要配合,否则中文极有可能出现乱码。排查乱码问题时,先看表字符集,再看连接串是否带了characterEncoding,最后看前端页面有没有声明charset,按照这个顺序查大概率能定位。

6.3 后端打包部署

后端打包前,确认application.yml里MySQL地址、用户名密码与实际环境一致。密钥之类的敏感配置,生产环境建议通过环境变量覆盖,而不是直接写在配置文件里。打包命令很简单:

cd backend mvn clean package -DskipTests java -jar target/campus-delivery.jar --spring.profiles.active=prod

jar包启动后,先看启动日志有没有报错。SpringBoot启动成功会打印Tomcat started on port的日志。此时可以先在本机用curl测试接口是否通,比如curl http://localhost:8080/api/user/info带上Token测试鉴权,再通过Nginx反向代理对外提供服务。

端口选择上,后端服务不建议直接用80或者443端口,保留给Nginx更合理。后端端口可以随意选一个,比如8080,Nginx监听80端口,把请求转发到8080,这样对外访问不暴露后端实际端口,也方便后续多项目并存。

6.4 前端构建与Nginx配置

前端构建是部署里最容易出问题的一步。执行npm install时经常依赖安装失败,建议用淘宝镜像源。Vue3项目构建:

cd frontend npm install npm run build

构建成功后,dist目录就是所有前端静态文件。把dist目录上传到服务器的某个路径,比如/opt/campus-delivery/dist,然后配置Nginx。

Nginx配置注意两点:第一,root路径指向dist目录,index选index.html;第二,前端路由使用history模式时,必须配置try_files,否则刷新页面会出现404,这是一个极其经典的部署坑。配置如下:

server { listen 80; server_name your.domain.com; root /opt/campus-delivery/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

配置完成后,nginx -t检查语法,service nginx reload重载配置。前端通过/api这个前缀访问后端接口,跨域问题在这里被Nginx的代理直接解决,前端和后端服务器之间完全避免跨域。

7. 常见问题与排查实录

7.1 数据库连接失败与时区报错

这个问题的特征非常明显,启动日志里出现Communications link failure或者The server time zone value开头的报错。前者通常检查MySQL是否启动、账号密码是否正确、端口是否是3306;后者则直接在JDBC连接串里补上serverTimezone=Asia/Shanghai。我排查这类问题养成了一个习惯:先手动在服务器命令行里用mysql客户端连接数据库,确认账号密码和远程访问权限都没问题,再去看SpringBoot的日志,避免把问题归结到代码上,其实只是权限没开。

7.2 Vue打包后刷新404

这是history模式路由的经典问题。前端构建完,用户访问首页正常,但点击路由跳转没问题,一旦手动刷新某个子页面比如商家列表就会出现404。原因就是Nginx找不到这个路径对应的物理文件。解决方案就是我上面提到的,在location /里加try_files逻辑,把所有路径回退到index.html,让前端路由器接管路由解析。

7.3 开发环境跨域配置后依然报错

前端走了代理仍然报跨域,大概率是代理没有生效或者接口baseURL没有走代理前缀。Vite开发环境中,代理配置在vite.config.js的server.proxy里,前端请求的是/api/xxx,代理把/api开头的请求转发到后端。如果页面里直接请求了完整的后端地址,比如http://localhost:8080/api/xxx,那代理就完全不生效,自然就报跨域了。记住一个原则:开发环境所有请求都走相对路径,由代理转发;生产环境所有请求也都走/api前缀,由Nginx转发。这样可以保证前后端的代码里只维护一个环境切换变量。

7.4 JWT Token失效和用户仍然能访问接口

这个问题很隐蔽。JWT令牌过期后,前端应该在收到401响应时跳转登录页。但有些时候后端接口返回的不是401,而是把业务异常正常返回了,比如空指针异常统一包装成500,前端拦截器只判断401,其他错误码全部放行,导致用户其实已经登录过期了,还在正常浏览页面,等到下单时才突然失败。解决思路是:后端拦截器里识别出Token问题必须返回401,前端响应拦截器看到401统一处理登录跳转,中间不要做任何业务兜底。

7.5 购物车数据不同步导致下单金额错误

前端购物车展示数量和后端实际存储不一致,通常是因为前后端各自维护了一份数据,没有以服务端为准。我碰到过一个情况:用户在两个浏览器标签页同时操作,一个标签页加购,另一个标签页下单,最后结算时后端按最新购物车数据计算,前端页面显示的还是旧数据。最终我把提交订单时的金额校验改成了以后端计算为准,前端展示金额只作为参考,提交后返回实际结算金额再让用户确认,彻底解决了这个不一致问题。

7.6 部署后静态资源请求404

前端dist目录里的资源路径是绝对路径,默认打包出来index.html引用的js和css路径是/assets/xxx.js。如果后端接口反向代理配置了静态资源路径的拦截,或者base路径设置不对,就会出现资源404。Vue3的Vite配置里可以通过base参数来调整。一般情况下,只要Nginx的root路径配正确,dist目录完整上传,这个问题基本不会出现。有一次我把dist目录上传到了错误层级,导致root路径指向了上一级目录,页面打开是目录列表而不是应用,这个细节很容易被忽略。

8. 避坑经验与个人体会

整个项目做下来,我最大的感受是:校园外卖系统这种项目,技术含量不在于用了多新潮的框架,而在于能不能把一套业务闭环完整地跑通。从数据库表设计到后端接口,从Vue页面到Nginx部署,每一个环节都有它自己的坑,真正把它们逐个解决掉之后,前后端分离架构的开发模式基本上就能融会贯通了。

再分享一个实用的经验:做这类项目时,不要一头扎进写代码,先花时间把表结构和接口文档定清楚。我用了两周时间写后端,第一周其实只在做表和接口定义,真正写业务代码速度反而不慢。接口返回数据结构的统一,以及订单流程的状态流转需要绘制一张简单的状态表,这些前期规划省下来的时间远超想象。比如订单状态我定义的是:待支付、已支付/待接单、配送中、已完成、已取消,看似简单,但前端页面、后端逻辑和数据库字段全部都要围绕这套状态统一,如果中途随便改,前后端联调时的返工量会非常大。

最后再补一个扩展方向:我现在已经在这套系统基础上加了商家端的销量统计接口,用MyBatis写了一条简单的分组合计SQL,把每天的订单数和销售额统计出来,前端接入一个图标库显示趋势图,工作量很小但效果非常明显。如果你正在做类似的系统,我强烈建议先把核心流程跑通并部署上线,再考虑这些锦上添花的功能,一个能访问的系统永远比一个写了一堆功能但跑不起来的东西更有价值。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 8:06:44

SpringBoot+Vue前后端分离教学管理系统:从架构设计到部署全解析

1. 脱坑指南&#xff1a;这套系统的设计与技术选型思路前后端分离的“大学生计算机基础网络教学系统”&#xff0c;名字听起来就是一套很典型的课程作业型项目&#xff0c;但真拿到手你会发现&#xff0c;它远不止“能跑起来”那么简单。整个系统围绕SpringBoot Vue MyBatis …

作者头像 李华
网站建设 2026/10/20 8:06:27

孤岛式直流微电网IEEE16节点分层控制仿真复现全解析

刚看到这个题目的时候&#xff0c;我第一反应是&#xff1a;又来一个“IEEE 16节点”。说实话&#xff0c;在直流微电网这个圈子里&#xff0c;IEEE 16节点测试系统几乎是论文复现的“标配”了&#xff0c;但真正能把它跑通、跑稳、跑出论文里那种漂亮曲线的&#xff0c;十个里…

作者头像 李华
网站建设 2026/10/20 8:05:58

AI助手账号被限制?六类高频封禁原因与避坑指南

这些天我陆陆续续帮几位朋友排查过某AI助手账号被限制登录的问题&#xff0c;发现绝大多数情况并不是平台“乱封”&#xff0c;而是使用者踩了一些很隐蔽的坑。我自己也被误伤过一次&#xff0c;申诉了三天才解决&#xff0c;所以今天把这6个关键点整理出来&#xff0c;希望能帮…

作者头像 李华
网站建设 2026/10/20 8:05:26

Redis Java客户端选型与连接池实战避坑指南

不废话&#xff0c;直接说正题。很多后端同学看到“Redis的Java客户端”这个标题&#xff0c;第一反应是“不就是Jedis和Lettuce吗&#xff0c;要什么深度”。但真到自己搭框架、调连接池、排查线上诡异报错的时候&#xff0c;才发现客户端选型和配置里的门道&#xff0c;比想象…

作者头像 李华
网站建设 2026/10/20 8:05:08

TransMLA:将多头潜在注意力嫁接进Transformer模型的推理优化实践

嫁接模型能力 TransMLA——初看这个标题&#xff0c;我脑子里蹦出来的不是论文&#xff0c;而是果树嫁接的老经验&#xff1a;接穗选得好&#xff0c;砧木底子硬&#xff0c;成活率才高。放在大模型领域&#xff0c;"嫁接"恰恰是当下最实用的一类工作&#xff0c;把某…

作者头像 李华
网站建设 2026/10/20 8:04:05

Agent Harness标准化实践:接口、状态机与可观测性设计

在LLM应用项目里摸爬滚打一段时间后&#xff0c;我最大的感受是&#xff1a;Agent 本身不难&#xff0c;难的是那个把它“架起来”的壳。这个壳&#xff0c;业内一般叫 Agent Harness&#xff0c;说人话就是给智能体穿的运行外套——消息循环、工具注册、上下文管理、重试策略、…

作者头像 李华