ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SpringBoot2+Vue3+MyBatis-Plus全栈电商项目“衣依”实战解析

SpringBoot2+Vue3+MyBatis-Plus全栈电商项目“衣依”实战解析

“衣依”这个项目名一出来,懂行的小伙伴应该就看明白了:这是一个标准的前后端分离的全栈Java Web项目。技术栈直接写在标题里——SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0,外加一份文档。别小看这套组合,它几乎是目前国内做毕业设计、课程设计和Java Web入门实战的首选配方。我前前后后接触过好几个类似的服装、电商类系统,自己也完整地跟过一版从零搭建到部署上线的过程,今天就把这个项目的核心逻辑、实操过程和我踩过的坑一次性说清楚。

先说这个系统能干什么。面向服装销售场景,“衣依”平台拆开来看就是两个端:买家端和管理员端。买家端负责注册登录、逛商品、加购物车、下单支付(通常是模拟支付或沙箱支付),管理员端处理商品上架、分类管理、订单发货、用户管理。底层的核心流程就是一条电商闭环——商品从后台录入,到前台展示,用户选购后生成订单,管理员发货后订单状态流转结束。整套流程麻雀虽小,五脏俱全,刚好把SpringBoot的接口开发、MyBatis-Plus的数据操作、Vue3的前端交互和MySQL的数据存储全部串起来。

这套技术栈的搭配非常成熟,我的建议是,如果你正在找Java Web项目练手,或者毕业论文需要一个能跑通、能演示、能讲清楚原理的系统,“衣依”这个方向很值得参考。接下来我会从项目设计拆解、核心细节、实操过程、踩坑经验四个维度,把这个项目从源码到跑通的每一个关键点都写出来。

1. 项目整体设计与技术选型思路

1.1 业务功能模块拆解

先泼一盆冷水:很多人拿到一套源码,第一件事就是启动起来看效果,结果一脸懵——“这么多表、这么多接口都是干嘛的?”我建议大家先不看代码,先从业务上把系统的模块画一遍。

“衣依”系统的业务模型并不复杂,核心实体就四类:用户、商品、购物车、订单。但围绕这四类实体,功能会展开得很广。

用户端的功能列表大致是:注册登录、个人信息维护、收货地址管理、商品按分类浏览、关键词搜索、商品详情页、购物车增删改查与选中结算、订单提交、订单列表查询、订单状态跟踪、取消订单。更完整一点的还会加上商品收藏、浏览历史、支付回调处理这几个能力,但这些属于加分项,不是核心。

管理端的功能列表大致是:管理员登录、仪表盘统计(今日订单数、销售额)、商品管理(新增、编辑、上下架、库存调整)、分类管理(一级/二级分类的增删改查)、订单管理(订单列表、订单详情、发货操作)、用户管理(查看用户列表、禁用账号)。

这两端加起来,大概就是30到40张表以内的规模,接口数量一般在60到100个之间。对应到数据库设计上,一定会有几张重量级表:用户表、商品表、商品分类表、购物车表、订单主表、订单明细表、收货地址表、轮播图表。商品表通常还会有规格字段,比如“颜色/尺码”这类属性,是直接设计成JSON字符串存,还是拆一张SKU表,后面的章节我会重点展开。

1.2 技术选型背后的取舍逻辑

这套组合之所以流行,不是因为某一项技术特别炫,而是每一层都选在了“最舒服”的位置上。

SpringBoot 2.x现在是绝对的主流。虽然SpringBoot 3已经出了,但3.x强制JDK17起步,很多学校的服务器和教材还停留在JDK8/11,而且很多老教程、老解决方案都是基于SpringBoot2写的,跟着排查问题要好找得多。SpringBoot2自带Tomcat和全套自动配置,起步快,项目里不用写一堆XML配置,这点在课程设计这个场景尤其重要,因为你要把精力放在业务逻辑上,而不是配置地狱里。

MyBatis-Plus是个好东西。它本质是MyBatis的增强包,封装了单表的CRUD操作,你连SQL都不用写,直接继承BaseMapper就能用。对于“衣依”这种电商项目,90%的查询都是单表查询,剩下的联表场景写少量注解或XML就足够了,完全没有必要引入JPA或者复杂的MyBatis手写XML。更重要的是它自带分页插件,一行代码就能解决列表页的分页问题,这在写商品列表和管理后台列表时能省下大量时间。

Vue3配合组合式API,是当下前端的主流方向。选Vue3而不是Vue2的原因不用多说,Vite的启动速度、组合式函数(composables)的复用逻辑、Pinia的状态管理,都会让代码的整体结构更清晰。而Vue2的Options API在组件一多、逻辑一复杂之后就会变得臃肿,组合式API把同一个功能的变量和方法聚合在一起,维护成本低得多。

MySQL8.0的选型则更多是“顺势而为”。MySQL5.7仍在大量生产环境运行,但8.0的默认字符集utf8mb4、JSON类型、窗口函数、更好的索引优化器,对开发阶段的体验来说更好。你新装数据库,没理由再选一个即将进入EOL的老版本。需要注意的是,MySQL8.0的驱动包名多了一个cj(com.mysql.cj.jdbc.Driver),时区配置也更严格,这两个细节在联调阶段很容易坑人。

2. 核心细节解析与实操要点

2.1 后端分层与项目结构

拿到源码之后别急着跑,先看目录结构。规范的SpringBoot项目一定是按职责分层的,这点很重要。

一个典型的包结构是这样的:

com.yiyi ├── common // 统一返回结果、异常处理、常量、工具类 ├── config // 配置类,如MyBatis-Plus分页插件、跨域配置、静态资源映射 ├── controller // 接口层,接收前端请求、参数校验、返回结果 ├── service // 业务逻辑层,事务管理在这里 │ └── impl ├── mapper // MyBatis-Plus的Mapper接口 ├── entity // 数据库实体类 ├── dto/vo // 数据传输对象和视图对象 └── interceptor // 拦截器,处理登录鉴权

很多同学觉得“接口直接写在Controller里不就行了,搞这么多层干嘛”。我见过不少烂代码,Controller里面直接查数据库、直接写复杂逻辑,前期看起来很方便,等到订单模块要复用商品查询逻辑,就只能在两个Controller里各写一遍。分层的核心价值不是好看,而是让每一层只做一件事:Controller管输入输出,Service管业务规则,Mapper管数据访问。这样你的代码才能被复用、被测试、被维护。

还有一个很多人容易忽略的细节:统一返回结果类。建议用类似Result 的包装,里面固定放code、message、data三个字段。前端axios封装就靠这个code来判断请求是成功还是失败。这个习惯如果从一开始就保持,后面联调阶段会非常省心。

2.2 JWT登录鉴权与权限模型

电商项目的所有操作几乎都依赖“当前登录用户是谁”,所以登录鉴权是整个系统的地基。地基不稳,后面写购物车、订单全是空中楼阁。

“衣依”这类项目最常用的方案是JWT + 拦截器。用户登录成功后,服务端生成一个签名后的Token返回给前端,前端存在localStorage里,之后每次请求在请求头带上Authorization: Bearer ,后端拦截器解析Token,把用户信息塞进请求上下文,接口里就随时能拿当前用户了。

JWT生成的核心依赖是jjwt库,生成的Token里可以放claims(载荷),我会放userId和role两个字段。签名密钥要放在配置文件中,过期时间一般设置24小时,过期后前端收到401状态码,就跳转到登录页。这里有个关键点:拦截器只拦截需要登录的接口,比如购物车、订单、用户中心,而商品浏览、首页轮播这些接口要放行,否则游客就没法逛商城了。

对应到权限模型,用户表加一个role字段就够了,1代表买家,2代表管理员。管理员接口在拦截器里多校验一下:从Token里取出role,如果不是管理员直接返回403。很多同学喜欢用Spring Security或者Shiro,但说句实话,这类中小型项目用Spring Security的学习成本远大于收益,JWT配合拦截器已经足够干净和清晰。

我贴一下核心的拦截器逻辑,让大家感受一下实现复杂度很低,但功能很完整:

public class AuthInterceptor implements HandlerInterceptor { private final JwtUtil jwtUtil; public AuthInterceptor(JwtUtil jwtUtil) { this.jwtUtil = jwtUtil; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 如果不是映射到方法,直接放行 if (!(handler instanceof HandlerMethod)) { return true; } // 从请求头获取Token String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); } if (StringUtils.hasText(token) && jwtUtil.validateToken(token)) { Long userId = jwtUtil.getUserId(token); Integer role = jwtUtil.getRole(token); request.setAttribute("userId", userId); request.setAttribute("role", role); return true; } // 未登录,返回401 response.setStatus(401); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":401,\"message\":\"未登录或登录已过期\",\"data\":null}"); return false; } }

这里面有一个容易被忽略的坑:request.setAttribute设置的参数只能在当前请求内读取,所以Controller里直接@RequestAttribute("userId") Long userId就能拿到。这个机制不涉及Session,服务端不需要存储任何状态,所以JWT方案天然支持水平扩展——多部署几个实例都不用担心Session同步问题。

2.3 商品SKU设计与MySQL8.0的优势

现在聊聊商品表怎么设计。服装类目和一般商品不同的地方在于,它天然有规格属性:颜色、尺码。一个商品“白色L码”和“黑色M码”可能库存不同、价格相同(或不同),这个在电商领域叫SKU(Stock Keeping Unit,最小库存单位)。

很多初学的项目为了省事,会在商品表里放一个specification字段,用JSON字符串存“[{"color":"白色","size":"L","stock":100},...]”。这么做最简单,查询的时候直接拿整个JSON解析,但是有两个问题:下单时要扣减对应规格的库存,你得锁定这行记录然后改写整个JSON;要知道某个SKU是否售罄,也没法走索引查询。换成一张SKU子表,主键关联商品ID,每个SKU一行数据,库存就是单纯的int字段,扣减就是一个UPDATE语句,清晰又高效。

用MySQL8.0的话,有两个能力值得用起来。一个是JSON字段类型本身,既然定义了JSON列,MySQL8.0原生支持JSON表达式索引和JSON函数查询;另一个是窗口函数,在“销售额统计”这类报表时特别好用。比如你要统计每个分类的销售额排行榜,一个ROW_NUMBER() OVER(PARTITION BY category_id ORDER BY total_amount DESC)就能搞定,这在旧版本MySQL里只能靠写复杂的子查询。

不过我要提醒一句:能不用JSON就不用JSON,这是数据库设计的一个长期原则。JSON字段很难做约束校验,数据一致性全靠代码保证。业务字段尽量拆成明确的列,JSON只用于“未来可能扩展、现阶段不想建表”的场景,比如商品主图的附件配置、第三方返回的原始数据等。

2.4 订单状态机与事务边界

订单模块是整个系统的核心难点。难点不在CRUD,而在状态流转的一致性。

订单的典型状态我建议这样定义:0待付款 → 1待发货 → 2待收货 → 3已完成 → 4已取消,外加一个5已退款(如果做了退款功能)。状态流转的路径要画清楚:待付款可以取消;待发货管理员可以发货;发货后用户确认收货;超时未支付则自动取消(这个一般用定时任务)。

最关键的业务规则是:下单时同时要扣库存、写订单、清购物车,这三步必须在一个事务里。否则就会出现“订单生成了,库存却没扣”这种事故。在Spring里,最直接的方式是Service方法加@Transactional注解,让异常发生时整体回滚。

还有一个很容易踩的坑:事务只在Service方法中生效,而且要保证方法从外部被调用才行。如果同一个类里一个方法调另一个方法,事务是不生效的,因为经过Spring代理的时候,内部调用不会经过代理。我在订单模块里就踩过这个坑,后来写了一个独立的OrderService,把创建订单的所有逻辑都收敛到一个public方法里,事务就正常了。

另外,订单号生成是一个值得写一笔的细节。用户下单要生成一个业务单号,不能直接用自增ID,否则容易被猜到总量。常见的做法是用时间戳拼接随机数,或者用雪花算法。这个项目里我用的是yyyyMMddHHmmss + 用户ID后四位 + 随机数,长度适中,可读性好,也基本不会撞号。

3. 实操过程:从空目录到前后端联调跑通

3.1 环境准备与项目初始化

拿到源码之后,第一步是准备运行环境。这套项目需要的软件如下:

  • JDK:1.8或11均可,推荐1.8,兼容性最好
  • Maven:3.6以上,配置阿里云镜像加速依赖下载
  • Node.js:16以上,实测18.12.0没任何问题
  • MySQL:8.0,注意安装时字符集选utf8mb4
  • 开发工具:IDEA(后端) + VSCode(前端)

这里有个非常实用的建议:项目根目录往往是前端和后端分开的两个文件夹,比如backend和frontend。用IDEA打开时,直接把根目录当作一个项目来打开,然后右击backend/pom.xml,选择“Add as Maven Project”,这样一个IDEA里能同时管理两边的代码,省去来回切换的麻烦。

数据库初始化也建议用现成的SQL脚本。先创建一个数据库:

CREATE DATABASE `yiyi_shop` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

然后导入项目里带的yiyi_shop.sql。导入后打开数据库,你会看到所有表已经建好,部分表还带上了测试数据。这时候先不要折腾数据,先把系统跑起来看效果,等理解了表结构,再清数据重造也没问题。

3.2 后端配置与启动细节

后端启动前要改的配置文件是application.yml,里面重点看三块:数据源、端口、JWT密钥。

数据源长这样:

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/yiyi_shop?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true username: root password: 你的密码

这里面有三处必须注意。第一,driver-class-name是com.mysql.cj.jdbc.Driver,不是老版的com.mysql.jdbc.Driver,MySQL8.0的驱动包只认这个。第二,serverTimezone=Asia/Shanghai必须设置,否则时间字段会差8小时。第三,allowPublicKeyRetrieval=true建议加上,否则连接时会因为MySQL8新版加密规则报错。

MyBatis-Plus的配置也在这份文件里,主要是逻辑删除和id类型。

mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 id-type: auto

配置完成后,启动SpringBoot主类,如果控制台出现了Tomcat started on port(s): 8080,那就说明后端已经跑起来了。这时候可以直接用浏览器访问http://localhost:8080/api/goods/list,看到JSON数据就说明接口没问题。

3.3 前端初始化和代理配置

前端的启动稍微复杂一点,因为你还要处理跨域的问题。好在Vite开发服务器自带代理能力,我们把前端地址代理到后端8080端口,就能让浏览器以为请求是发到同一个域名的,绕开跨域限制。

vite.config.js里这样写:

export default defineConfig({ plugins: [vue()], server: { port: 3000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })

配置好代理后,前端所有以/api开头的请求,都会被Vite转发到后端的8080。这样就保证了前端和后端在开发阶段互相不干扰:前端跑在3000端口,后端跑在8080端口,前端这边只管页面,后端那边只管接口。

启动命令也顺手记一下:

npm install npm run dev

看到Local: http://localhost:3000就说明前端跑起来了。输入默认管理员账号(一般SQL里会带一个admin账号,密码通常也是admin或者123456)登录后台,就能看到管理界面了。

有一点要提前说明:前端项目如果用npm安装依赖总是失败,多半是网络问题,建议用淘宝镜像源。在项目根目录建一个.npmrc文件,内容写registry=https://registry.npm.taobao.org(新的镜像地址应该用npmmirror.com),然后重新npm install。

3.4 核心业务链路实操:购物车到订单

项目跑通后,建议选一条完整的业务链路亲手走一遍,这样你对整套系统的理解会比单纯看代码深刻得多。我以“用户选商品 → 加购物车 → 结算生成订单 → 管理员发货”这条主线来拆解每一步要做什么。

第一步:注册登录

用浏览器打开前端首页,点击右上角注册,填完手机号、密码、昵称提交。后端会调用注册接口,密码在注册接口里要进行BCrypt加密存储,而不是明文。登录接口校验通过后返回Token,前端存到localStorage里。这一步走完,刷新页面看右上角是否显示了用户名,如果显示了,说明Token已经被请求拦截器带上去了。

第二步:商品列表与详情

首页会展示轮播图和分类推荐位。点击一个分类,前端请求GET /api/goods/list?categoryId=xx&page=1&size=10,后端用MyBatis-Plus的Page对象分页查询。这个接口用了LambdaQueryWrapper构造条件,不要用字符串拼条件,容易出SQL注入。详情页拿到商品ID,请求详情接口,返回商品信息、图片列表和SKU列表。页面上选一个颜色和尺码,下面会实时显示该SKU的库存和价格,前端这部分逻辑其实是在一个计算属性里完成的。

第三步:加购物车

点击“加入购物车”,前端把goodsId、skuId、quantity三个参数POST到后端。后端先查购物车表有没有这条记录:有就增加数量,没有就新增。注意这里要加一个约束:不能超出库存。添加成功后,前端右上角的购物车角标数量会+1,这个数字由Pinia里的cartCount状态管理维护,只要调一下fetchCartCount方法就能重新拉取。

第四步:确认订单与提交

购物车页勾选要买的商品,点击结算,跳转到确认订单页。页面上要选择收货地址,默认取最近使用的那条。确认无误后点击提交订单,前端调用POST /api/order/create,参数是一个数组:每个商品的下单数量、选择到的SKU、总金额。后端拿到这个请求后,开始执行事务:先查出商品和SKU的最新库存,校验库存是否充足,然后扣减库存、生成订单主表和订单明细表、清空购物车已选中的条目、返回订单ID和订单号。这一步是重点,强烈建议在Service方法断点走一遍,看看事务是怎么把多个写操作串起来的。

第五步:支付模拟与管理端发货

这个系统一般不会接真实支付,因为个人项目没有支付资质,所以支付环节通常是模拟。最简单的做法是订单创建后默认状态为“待付款”,提供一个“模拟支付”按钮,点击后调一个接口把订单状态改成“待发货”。管理端在订单管理页面看到这笔订单后,点击“发货”,填入物流单号,订单状态变成“待收货”。用户端刷新订单列表,看到状态变化,点击“确认收货”,订单状态变成“已完成”,整个闭环就结束了。

我真心建议你把这条链路在代码里跟一边,尤其是订单提交Service方法,把里面每一步的数据库操作都标上注释,你会对事务、状态流转、数据一致性有非常直观的认识,这是任何视频教程都给不了的体验。

4. 常见问题与排查技巧实录

4.1 MySQL连接失败和时区异常

这是最经典的问题,几乎每个第一次跑这个项目的人都会撞上。错误提示一般有两种:“Unable to connect to MySQL server”和“The server time zone value '�й���ʱ��' is unrecognized”。

第一个问题先检查MySQL服务有没有启动,Windows下可以net start mysql80,Mac/Linux下用brew services list或systemctl status mysql。确认服务启动后,再看两张表的配置:一是application.yml里的password是不是本机MySQL的密码,二是3306端口有没有被占用。本地开发最稳妥的办法是MySQL安装时把端口设为3306,不要弄个3307还得改配置。

第二个时区问题的解决方法很简单:URL后面加上serverTimezone=Asia/Shanghai。如果你发现加上后还报时区错,那多半是连接的数据库驱动版本太旧了,去pom.xml确认一下mysql-connector-java的版本是8.0.x,旧版本的驱动对时区支持有问题。

4.2 MyBatis-Plus分页不生效

分页查询是商品列表和管理员列表的核心功能。很多人写完后发现查询出来的结果不分页,返回的total永远是0,或者所有数据一次性全查出来了,原因只有一个:分页插件没有注册。

MyBatis-Plus在3.4.0之后的分页插件配置方式有变化:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

老教程里写的是PaginationInterceptor,3.4.0之后已经废弃了,如果从旧项目复制过来,启动会报NoSuchBeanDefinitionException,或者分页完全失效。另外一个细节是分页查询的Service方法上,调用page(new Page<>(pageNum, pageSize))时要确保pageNum从1开始,如果你从0开始,第一页的数据会查不出来。

还有一个“伪分页”的坑:很多同学为了省事,把全表数据查出来然后在Java代码里subList分页。这个在小数据量下感觉不到问题,等商品到几百上千条,接口响应会明显变慢,数据库的压力也会变大。我说的就是MyBatis-Plus自带的分页插件,它在物理层直接生成LIMIT语句,查询性能最优。

4.3 JWT登录失效与Token续期问题

后端开发完成、前端联调时经常遇到一个诡异场景:用户登录后一切正常,但一段时间后再点某个按钮就跳到登录页了。这个问题的根源是Token过期,前端没有做自动处理。

Token设24小时过期,这个时长对演示和课程设计来说足够了,但建议前端在响应拦截器里做一个“静默续期”的机制:当接口返回401时,先不清除登录态,而是跳到一个提示页,让用户重新登录,或者用刷新Token重新换取新的访问Token。方案并不复杂,但做好了这个体验会明显提升。

另外一个相关问题是退出登录后,Token其实还有效(因为JWT是无状态的,服务端无法主动失效),所以前端一定要在退出时把localStorage里的Token清除干净。有些项目会做一个Token黑名单,但那是中大型系统的做法,这里完全不需要。

4.4 前端联调时的跨域问题

前端访问后端接口时,控制台报错信息是“Access to XMLHttpRequest at 'http://localhost:8080/api/...' from origin 'http://localhost:3000' has been blocked by CORS policy”。

这个报错看着吓人,但原因通常只有两种。第一种:前端没有配置Vite代理,请求直接跨域了。解决方法是按上文的proxy配置来写,所有请求走/api前缀。第二种:后端没有配置跨域过滤器。虽然开发阶段用了代理,但如果哪天部署到线上用Nginx转发,或者有人直接用Postman测试,后端接口仍会被Postman拒绝,因为Postman不带Origin头,浏览器才会带。

推荐在后端全局加一个CORS过滤器,允许前端来源:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

注意SpringBoot2.4之后,allowedOrigins("*")和allowCredentials(true)不能同时使用,否则启动报错,解决办法就是改用allowedOriginPatterns("*")。

4.5 图片上传和静态资源访问

商品肯定要有图片,这个项目的图片一般是用本地存储的方式解决,管理员把商品图片上传到服务器某个目录下,前端通过URL访问。这里有个常见问题:图片路径写的是本地磁盘绝对路径,换成别的机器跑就找不到图片了。

解决办法是配置一个虚拟路径映射,把本地的目录映射到URL路径上,让图片可以通过/images/xxx.jpg来访问:

@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/images/**") .addResourceLocations("file:" + uploadPath + "/"); }

注意addResourceLocations最后的路径结尾必须是/,否则映射不生效。这个细节我当年调了半个小时才定位到。

还有一个容易被忽视的点:前端页面里拼接图片地址时,要用后端返回的完整URL,不要自己臆造一个路径。商品列表接口返回的字段如果叫mainImage,那它的值应该已经是/images/goods/xxx.jpg这种可以直接拼到域名后面的形式,而不是数据库里的一个文件名。

4.6 部署到Linux服务器时的常见坑

本地跑通只是第一步,很多课程设计最后还要提交一个在线演示地址,所以部署也是一个必考项目。部署前推荐用Maven把后端打成jar包,前端构建成静态文件放到Nginx下面。

后端在服务器上跑,最常见的问题是端口被占用、数据库密码不对、防火墙没放行。先用java -jar yiyi-backend.jar直接运行,看控制台报什么错。注意Linux下的MySQL8.0默认只监听127.0.0.1,如果你的后端应用和数据库在同一台机器上,用localhost连接没问题;如果分开部署,就需要修改MySQL配置里的bind-address。

前端部署到Nginx时,有两个坑要特别注意。一个是静态资源路径,用npm run build生成的dist目录里,assets资源如果是绝对路径/assets/...,部署到子目录就会404,解决办法是在vite.config.js里配置base: './'(相对路径),或者在Nginx里设置对应别名。另一个是页面刷新404问题。因为Vue Router默认使用history模式,前端路由刷新时需要后端配合重定向:

location / { root /var/www/yiyi; index index.html; try_files $uri $uri/ /index.html; }

这个配置的意思很直白:如果请求的文件不存在,就返回index.html,让前端路由自己去匹配。很多人在这一步栽跟头,部署完打开首页没问题,一点刷新就报404,其实就是少了这条配置。

5. 项目扩展与值得投入的升级方向

如果你不满足于“跑通源码”,想做点差异化改造,我建议从下面几个方向里挑一两个来扩展,性价比最高。

引入Redis做热点缓存

商品详情页和首页是访问最频繁的接口,每次查询都走数据库,高并发下压力很大。给首页轮播图、分类和热门商品加一层Redis缓存,设置10到30分钟的过期时间,数据库压力会明显缓解。实现逻辑很简单:查询时先查Redis,没有再从数据库查,然后写回Redis。这个点如果在答辩时讲出来,老师会觉得你有真实的性能意识。

增加搜索功能

现在的商品搜索通常是一个简单的LIKE '%关键词%'模糊查询,数据量一大就不行了。可以基于MySQL的全文索引,或者接入ElasticSearch做商品搜索。这个扩展对服装平台来说非常自然,因为用户买衣服确实要靠搜关键词。如果只改数据库,可以给商品表的goods_name字段加全文索引,用MATCH ... AGAINST替代LIKE,效果立竿见影。

定时任务处理订单超时

订单超时未支付自动取消,这个是电商平台标配功能。SpringBoot自带@Scheduled注解就能实现:写一个方法,每60秒扫一次数据库,把创建时间超过30分钟且状态为“待付款”的订单状态改成“已取消”,同时恢复库存。这个功能逻辑简单,代码量不到50行,但是对系统的完整度提升非常明显,而且涉及到“扫描”和“批量更新”两个很常见的后端开发技能点。

对接微信支付沙箱

如果你想让项目“看起来更真实”,可以考虑对接微信支付或支付宝的沙箱环境。注意沙箱环境是模拟的,不需要真实商户资质,但需要注册一个测试应用,拿到测试密钥。支付回调处理是这一块的核心难点,回调里要校验签名、更新订单状态、处理幂等(防止重复回调),做完这个模块,你对支付系统中的安全性和幂等性会有真正的理解。

写在最后的实操体验

这个项目我是完整跟过一遍的,从零开始踩坑到全部跑通,前后花了两三天。最大的体会是,源码不是拿来看的,而是拿来“拆”的。每看到一个接口,先想想前端哪里会调它;每看到一张表,再想想它在哪个业务环节里承担什么角色。把“数据在哪里流动”这条线理清楚,比死记代码重要得多。

另外一个小建议:开发这类系统时,尽量不要用生产级别的高可用方案去“秀肌肉”。课程设计和毕设答辩的核心评估标准是“逻辑是否完整、功能是否闭环、能否说清实现原理”。SpringBoot2 + Vue3 + MyBatis-Plus这套技术,正好处在一个“功能完整但复杂度可控”的黄金位置。你只要能把这套链路讲明白,把订单状态流转和事务边界说清楚,就已经是一部很有说服力的作品了。

如果你正在跑这个项目,遇到什么疑难问题,多看看日志,多打断点,多查数据库里的数据变化。线上的报错信息永远是第一线索,解决一个报错,你对系统的理解就加深一层。项目源码只是一个起点,把它变成你自己的作品,才是这套代码真正的价值所在。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进