SpringBoot+Vue3+MyBatis商城项目剖析:从架构设计到生产实践

发布时间:2026/10/13 16:22:21
SpringBoot+Vue3+MyBatis商城项目剖析:从架构设计到生产实践
1. 项目概述与核心需求解析1.1 这个商城项目到底在做什么米家商城名字听着像小米生态链的产品但实际它是一个基于Java SpringBoot搭建的典型电商系统。我接触这个项目的时候第一反应是这不就是一套标准的“前后端分离商城”教学级项目吗但仔细看完源码之后发现它和我之前拆过的很多毕业设计、课程设计类项目有一个明显区别整个工程划分非常干净业务逻辑没有堆在Controller里Service层的职责边界也比较清晰甚至对支付回调、库存扣减这种容易写成一坨的地方都单独做了处理。这个项目本质上解决了一个很现实的问题很多新手学完SpringBoot、Vue3、MyBatis这几个技术栈之后最大的困惑是不知道怎么把三套东西串起来。数据库设计好了不知道怎么映射到Java实体后端接口写好了不知道前端怎么调用前端页面写完了一刷新数据没加载出来也不知道去哪查问题。米家商城就是一条完整的链路MySQL存数据、MyBatis管持久化、SpringBoot提供接口、Vue3渲染页面每一层都有对应的代码可以对照着学。1.2 技术选型背后的逻辑SpringBoot作为后端框架目前几乎是Java领域的事实标准。它内嵌Tomcat不用额外部署容器起步成本低自动配置机制省掉了大量XML配置。Vue3作为前端框架组合式API的引入让组件逻辑复用变得比Vue2时代舒服太多配合Vite构建工具开发时的热更新速度快到体感拉满。MyBatis则是持久的SQL映射框架它最大的好处是SQL完全由开发者自己控制不像JPA那样在复杂查询时容易被自动生成的SQL拖累性能。MySQL是这个项目里唯一的重型依赖整个商城的商品、用户、订单、购物车数据全存在里面。选MySQL而不是PostgreSQL或者Oracle核心原因是它在国内的技术生态太成熟了出了问题搜一下基本都是现成的答案对学习型项目来说这一点比某个数据库本身的性能上限更重要。注意这类项目在学习阶段完全够用但如果真要上线商用有几个点必须替换或加固。比如密码的加密方式、支付回调的验签逻辑、商品库存的超卖控制这些在源码里虽然有实现但离生产级别的标准还有差距。1.3 适合谁来参考我大概捋了一下这个项目最适合三类人。第一类是准备找Java后端开发实习或校招的学生用这个项目作为简历上的亮点面试时能讲清楚用户从登录到下单结算的全过程第二类是想快速搭一套商城系统做私活或者自己练手的开发者直接基于这套代码改改就能交付第三类是想从前端转全栈的开发者通过前后端分离的代码对照理解接口联调到底是怎么回事。不过我也得泼一盆冷水如果你已经工作了三年以上做过后台管理系统或者电商类业务那这个项目的技术深度对你来说可能偏基础。它更像一块跳板你可以基于它去扩展分布式会话、消息队列削峰、Redis缓存商品详情这些进阶能力。2. 系统设计思路与架构拆解2.1 前后端分离的整体拓扑整个项目跑起来的架构其实很常规但正因为常规反而适合当作范本去理解。前端是Vue3工程开发时通过Vite启动在本地端口浏览器加载的是SPA单页面应用后端是SpringBoot工程内嵌Tomcat监听8080端口两者之间通过HTTP接口通信前端通过Axios封装请求后端返回JSON数据。这里有一个容易被新手忽略的点开发环境下前端和后端分别跑在不同端口会触发浏览器的跨域限制。这个项目里解决跨域的做法是在SpringBoot写了一个WebMvcConfigurer的配置类直接实现了CORS跨域请求的映射。很多网上的资料喜欢用注解CrossOrigin解决但那个方式需要加到每个Controller类上太啰嗦全局配置类明显更优雅。生产环境下的部署则是另一种玩法用Nginx作为静态资源服务器托管前端打包后的dist目录同时把/api开头的请求反向代理到后端的8080端口。这样浏览器访问的是同一个域名不存在跨域问题而且Nginx处理静态文件的性能比Tomcat好很多。这套方案在项目里没有写出来但如果你真要部署上线这是最标准的做法。2.2 后端模块划分源码中的后端分包是按业务功能划分的不是按技术层次一刀切这点值得单独说。常见的分包方式是controller、service、mapper三个包各管一摊简单项目这么搞没问题但业务多起来之后会很乱。这个项目是按user、product、order、cart这样的业务模块分包每个包下再放各自的Controller、Service、Mapper这种结构的可维护性好很多尤其是多人协作的时候每个人负责一个模块代码冲突的概率会大幅下降。拦截器这块也做得比较用心。JWT令牌校验通过一个自定义的HandlerInterceptor实现注册拦截器时通过addPathPatterns和excludePathPatterns配置了哪些接口需要登录才能访问哪些接口放行。商品列表、商品详情这些页面不需要登录所以放行购物车、订单结算、提交订单这些必须登录所以拦截。代码里对登录接口、注册接口、首页轮播图接口都做了排除这个细节如果你自己写很容易漏掉。2.3 前端页面与状态管理Vue3前端这边页面不多但覆盖了电商核心链路首页商品展示、商品分类筛选、商品详情、购物车、确认订单、支付页、我的订单、个人中心。用Vue Router做前端路由配合路由守卫实现未登录跳转逻辑——当你访问购物车但本地没有token时自动弹回登录页。状态管理用的是Pinia这已经是Vue3时代的默认选择了。项目里维护了用户信息、购物车数量这些全局状态。购物车数量是个典型的全局共享状态因为导航栏和购物车页面都要显示不用状态管理的话就得通过事件总线来回传非常痛苦。Pinia的store在刷新页面后数据会丢失项目里通过持久化插件把token和用户信息同步到了localStorage这个小细节很多初学者想不到。3. 数据库设计与MyBatis核心实现3.1 MySQL表结构设计思路整个商城数据库的表设计是典型的电商五张核心表用户表、商品表、商品分类表、购物车表、订单表。订单表和订单项表是分开的订单表存收货人、订单金额、支付状态这种订单级信息订单项表存每个商品购买了多少件、当时的价格快照。这种设计在电商领域属于基本功如果只建一张订单表把所有商品信息塞进去后续做订单拆分、退款、数据分析都会很难受。商品表的字段设计也体现了对业务的理解。除了商品名称、描述、价格这些常规字段之外还有主图地址、轮播图地址这两个字段存的是图片的URL路径而不是把图片本身存进数据库。很多新手容易犯的错误是想着把图片转成Base64存字段里结果数据库迅速膨胀查询越来越慢。项目里图片是上传到本地服务器或者对象存储数据库只存路径这是标准做法。关键表字段速查表名核心字段设计要点t_userid, username, password, nickname, avatar密码存的是MD5加密值不存明文t_productid, name, description, price, stock, main_image库存字段设计成int下单时做扣减t_categoryid, name, parent_id通过parent_id支持二级分类t_cartid, user_id, product_id, quantity加了user_id和product_id的唯一索引t_orderid, order_no, user_id, total_price, status订单号用时间戳加密生成不用自增id3.2 MyBatis映射与动态SQL实战这个项目用MyBatis的方式对新手非常友好实体类字段命名用驼峰数据库表字段用下划线命名然后通过MyBatis全局配置的mapUnderscoreToCamelCase参数自动完成映射。这样省掉了大量的resultMap配置代码看起来干净不少。商品分页查询用了MyBatis的动态SQL通过 标签判断查询条件是否为空再配合 标签实现分类筛选、关键字搜索。说实话现在很多人已经习惯用MyBatis-Plus的LambdaQueryWrapper了手写动态SQL确实显得有点传统。但从学习的角度看理解动态SQL的执行原理反而更重要因为MyBatis-Plus底层也是把这些条件拼接成SQL只是帮你封装好了。还有一个值得注意的点是批量插入。购物车选中多个商品生成订单的时候需要一次性插入多条订单项数据。项目里没有用for循环一条一条插而是通过MyBatis的 标签拼了一条批量INSERT语句。别小看这个细节循环插入100条数据要往返数据库100次批量插入只消耗一次网络开销性能差距是数量级的。3.3 数据库索引与性能考虑MySQL这块虽然是个偏学习的项目但索引的使用没有瞎胡闹。商品表的分类字段加了索引因为商品列表页经常按分类过滤订单表的user_id加了索引因为“我的订单”页面要按用户查询订单列表购物车表的user_id和product_id建了联合唯一索引这个设计可以防止用户在添加购物车时重复生成同一条记录后端代码里配合这个索引做了加购去重逻辑。但有一点项目里没优化商品表的名称字段没有加全文索引搜索结果只能靠LIKE模糊匹配。数据量小的时候无所谓但如果商品量超过几万条LIKE %关键词%在MySQL里会全表扫描性能会直线下降。要真做商城商品的搜索建议上Elasticsearch或者至少在MySQL里用全文索引先顶一阵。4. 核心业务逻辑与实操过程实录4.1 用户登录认证与JWT令牌登录这块走的是目前最主流的JWT方案。用户提交用户名密码后端校验通过后用JJWT库生成一个包含用户id和用户名的token字符串返回给前端。前端拿到token存到localStorage里之后每次请求后端接口时都在HTTP请求头加上Authorization字段携带这个token后端通过拦截器统一解析验证。密码存储用的是MD5加盐说白了就是先对密码做一次MD5再拼上一个固定的盐值字符串再做一次MD5。这个强度在现在肯定不算高暴力破解的成本很低。我实测下来发现如果你想把这个项目改得更安全PasswordEncoder换成BCrypt会更好——SpringSecurity里的BCrypt每次加密都会生成随机盐同样的明文每次生成的密文都不同防彩虹表的能力强很多而且代码改起来只涉及注册和登录两处。拦截器校验token的逻辑里有一个常见的坑就是解析token失败时要区分两种情况token过期和token非法。项目里只用了一个try-catch全部拦下返回“登录状态已失效”虽然能用但区分开会对前端给用户更精确的提示。过期返回401让前端跳登录页非法直接返回400提示参数错误体验会更好。4.2 商品列表的分页与条件查询商品列表页是电商系统里最需要兼顾体验和性能的接口。这个项目的实现逻辑是前端每次切换分类或者搜索时把categoryId和keyword传给后端后端通过MyBatis的PageHelper插件做物理分页返回当前页码的商品数据和总记录数。PageHelper的用法很简单分页查询之前调用PageHelper.startPage(pageNum, pageSize)紧接着执行的那条Mapper查询就会被自动拦截并加上LIMIT分页再把结果包装成PageInfo对象返回。但这个插件有个非常经典的坑startPage方法必须在紧挨着Mapper查询的上一行调用中间不能有任何其他数据库操作否则分页会加到错误的SQL上。我在实际项目里见过因为这个坑导致全表数据被误删的误操作虽然严格来说主要责任不在插件但理解原理能避免很多低级事故。商品详情接口则是通过商品的id直接查询这里加了一个文章浏览量的逻辑每次请求详情时对浏览量字段做自增。自增操作在SQL里用UPDATE t_product SET views views 1是一个原子操作在高并发下也不会出现覆盖更新丢失计数的问题。这种写法比查出来再加一然后更新回去安全得多。4.3 购物车增加、删除、数量变更购物车模块的逻辑相对单纯核心就三个接口加入购物车、修改商品数量、删除购物车项。加入购物车时先根据user_id和product_id查这个商品是否已经在购物车里如果存在则做数量相加如果不存在则insert一条新记录。这种“先查再插”的逻辑在单用户场景下没问题但如果同一用户同时发起两个相同的加购请求会出现同样的商品被插入两条购物车记录。项目里在数据库层面加了唯一索引兜底保证即使代码出现并发问题数据库也会拒绝第二条插入。修改商品数量接口传入cartId和新的数量前端在购物车页面点击加减号时发出请求后端直接按主键更新quantity字段。这里没有做库存数量校验如果商品库存只有5件但你通过接口把购物车数量改到100提交订单时也没报错这算是一个业务逻辑漏洞。要补的话修改数量时应该顺便查一下商品库存前端数量超过库存就强制置为库存上限并提示用户。删除购物车项就一句话的事后端根据cartId删除记录。在真实项目中通常做的是软删除加一个deleted字段标记是否已删除这样后续做数据分析时还能捞到历史加购行为。不过这个项目从代码简洁性考虑用硬删除也说得过去。4.4 订单生成与库存扣减流程订单生成是整个项目中最值得反复琢磨的业务点。流程是这样的前端把购物车中被选中的商品id列表传给后端后端一次性查询选中的商品信息计算总价生成唯一的订单号然后做两件事——插入订单表和订单项表同时把购物车中对应的商品记录删掉。这里重点说订单号的生成方式。项目里用的是System.currentTimeMillis()拼上随机数再经过一个简单的混淆算法得到最终字符串。这种生成方式在单机部署、低并发场景下没问题但要真正做商城一般会引入雪花算法或者干脆用Redis的INCR生成自增序列。雪花算法的好处是生成的ID全局唯一且趋势递增对数据库索引友好MySQL的B树在插入数据时不容易发生页分裂。各位如果基于这个项目二次开发我强烈建议把订单号生成改成雪花算法。库存扣减的逻辑是下单前先查询商品库存如果库存大于购买数量则扣减否则提示库存不足。这段代码有一个经典的并发超卖问题两个请求同时查到库存为1然后都认为自己能扣减最后库存变成负数。解决超卖有几种方案最简单的是在SQL层面扣减时加上条件UPDATE t_product SET stock stock - #{count} WHERE id #{id} AND stock #{count}这样即使并发请求进来数据库的行锁会保证只有一个请求更新成功受影响行数为0的那个请求就判定为抢购失败。4.5 前端Vue3页面交互与联调细节前端的商品列表页做了下拉加载更多和上拉刷新的功能这个在H5商城页面里非常常见。下拉刷新通过Betterscroll库实现上拉加载触发下一页请求数据追加到列表尾部同时用一个isLoading变量做防重防止用户疯狂上拉时重复请求同一页数据。这个防重标记是前后端联调时最容易出bug的点很多初学者会忽略结果每次加载都会多出重复的商品数据。路由守卫是前端控制访问权限的关键。项目里通过router.beforeEach钩子函数检查当前路由的meta是否需要登录如果需要且本地没有token则跳转到登录页并携带redirect参数登录成功后再跳回原来的页面。这个交互细节很多后台管理系统都有但新手自己写的时候往往忘了用户刚准备提交订单就被踢到登录页再登录完又回到首页体验很差。Axios封装这块项目里用了统一的拦截器请求发出前在config.headers中塞入token响应返回后统一解包response.data并对HTTP状态码做了判断。这里我有一次踩坑记录分享一下如果后端返回的JSON里code字段是自定义的那前端不能只依赖HTTP状态码判断业务成功必须兼容处理比如token过期时后端返回200但code是401前端就跳登录页。这个项目的封装逻辑是有的但后端有一两个接口没遵循同一返回格式导致前端联调时需要单独处理这个如果重新梳理一遍接口规范会更舒服。5. 环境搭建与源码本地运行全流程5.1 前置环境准备与版本选择这个项目的运行环境不算复杂但有三个版本坑必须提前避掉。JDK用1.8肯定没问题如果你本机装了JDK17甚至JDK21启动时大概率会遇到反射访问警告甚至直接报错尤其是SpringBoot内嵌Tomcat与JDK高版本的模块强隔离机制有兼容性问题。我一直建议这类教学项目用JDK8这不是保守是省时间。MySQL版本建议5.7或者8.0。如果你用的是MySQL8要注意JDBC驱动要换成com.mysql.cj.jdbc.Driver而且连接URL里必须带上serverTimezoneAsia/Shanghai参数否则会报时区错误。这个时区问题几乎每个人都会遇到第一次跑通源码的新手百分之百要在这个地方卡一下。Vue前端部分要求Node版本不能太低我实测用Node 16没问题Node 18也能跑但如果你用的是Node 20以上的版本个别依赖安装时可能会编译失败。这时候用npm install加--legacy-peer-deps参数或者在项目根目录配上.npmrc文件指定镜像源和legacy-peer-deps配置能避掉大部分坑。5.2 数据库初始化与配置修改源码里通常会带一个sql目录里面有建库建表的脚本。直接用Navicat新建一个数据库然后把sql文件导入就行。唯一要注意的是导入之前先打开sql文件看一眼确认数据库名字是不是叫mijia_mall之类的如果不是要么改sql文件里的库名要么在application.yml里的JDBC连接地址改成对应的库名。application.yml是后端启动前必改的配置文件。默认配置是本地root账号空密码如果你本机MySQL设置了密码把url和username、password都改掉。还有一个配置项是server.port默认8080如果你的8080端口被其他服务占用了改成一个不冲突的端口注意前端调用接口的baseURL也要跟着改。5.3 前后端启动步骤与联调验证后端启动最简单用IDEA打开项目等Maven把依赖全部下载完成直接运行启动类。如果启动过程中看到Tomcat started on port(s): 8080这句话说明后端已经跑起来了。这时候可以用Postman之类的工具先测一个接口比如GET请求http://localhost:8080/api/product/list如果返回JSON数据说明后端和数据库连通。前端启动需要先在项目目录下执行npm install这一步非常考验耐心尤其是国内网络环境装Electron、node-sass这类依赖时经常失败。建议直接在项目里配置使用国内npm镜像源能省至少一半时间。装完之后执行npm run devVite会启动一个本地开发服务器并自动打开浏览器。第一次打开页面后如果数据区域是空的优先去浏览器F12控制台看有没有报跨域错误看到CORS字样就去检查后端配置类看到404就检查前端请求的baseURL是否与后端端口一致。联调通过的标准是随便点一个商品进入详情页能正常展示数据并且点击“加入购物车”后购物车角标数字变化说明登录、接口鉴权、购物车业务整条链路都是通的。6. 常见问题排查与避坑指南6.1 启动期的高频报错汇总报错现象根本原因解决方案启动时报Failed to configure a DataSourceapplication.yml数据库连接配置不对检查url、账号、密码确认MySQL服务已启动SQL异常Unknown column xxx in field list数据库表结构与实体类字段映射不一致核对sql脚本是否全部执行字段名是否匹配驼峰映射规则登录接口返回500或Authentication failed密码加密算法不匹配确认配置文件里的加密方式与数据库存储的密文一致前端启动时提示digital envelope routines::unsupportedNode版本过高与旧版Webpack冲突换Node 16或设置环境变量NODE_OPTIONS--openssl-legacy-provider前端页面能打开但接口全部403后端拦截器放行路径未配置完整检查登录注册接口是否在excludePathPatterns中排除接口返回JSON中文乱码数据库连接URL缺少characterEncoding参数连接串加上characterEncodingutf86.2 前后端联调阶段的高频问题前后端联调出问题有一个排查思路我一直推荐先确认后端接口本身有没有返回数据再确认前端有没有正确渲染。操作方法是在浏览器直接访问后端接口地址如果返回了JSON说明后端正常问题在前端请求或渲染逻辑如果访问后端接口都报错那问题在后端和数据库这一侧。这个二分法能省下大把对着一堆代码发呆的时间。另一个高频问题就是token丢失。前端登录成功后把token写进了localStorage但页面刷新后请求又变成了401。原因通常是Axios请求拦截器读取token时用的key和登录时存储的key不一致比如一个写的是token一个读的是user_token。这种问题一眼看去毫无头绪但定位思路非常明确登录成功后的存储代码和请求拦截器里的读取代码对照一眼就知道。6.3 我个人实测遇到的坑和心得第一次跑这个项目的时候我在商品列表分页上卡了很久。现象是数据库里明明有20条商品但接口只返回10条我以为是查询条件写错了排查到最后发现是PageHelper没有正确分页因为分页之前有一次无关的数据库查询导致分页拦截器把SQL拦截到了错误的位置上。从那以后我在任何项目里都会注意保证startPage紧跟Mapper查询中间不插入任何其他数据库操作。还有一个算经验之谈的建议就是第一次看这种全栈项目时不要逐行读代码效率太低。正确顺序是先启动起来跑通整个用户行为链路注册、登录、浏览商品、加购物车、下单、查看订单然后再针对自己关心的模块去精读代码。有了整体认知后再看细节记住的效果完全不同。我见过太多人刚开始就扎进某个Controller的几十行代码里看了两小时也没搞懂项目整体干嘛的最后直接弃坑。7. 从教学项目到生产系统的扩展思考这个米家商城项目说实话技术栈选型和代码结构都适合作为快速起步的模板但离生产级商城还有一段路要走。如果你打算拿它作为毕业设计或者简历项目有几个扩展方向值得投入精力面试时也能讲出东西来。第一个方向是缓存引入。商品详情、首页轮播图这些读多写少的数据非常适合加Redis缓存。用Spring Cache注解或者手动操作RedisTemplate把热门前三页的商品列表和详情页放进缓存设置合理的过期时间给面试官讲清楚“缓存穿透、缓存击穿、缓存雪崩”这三个概念在项目里的应对方案含金量直接上一个大台阶。第二个方向是消息异步化。下单成功后发送通知、支付成功后回调更新订单状态这类逻辑占用主链路时间又不要求实时返回完全可以丢进消息队列。RabbitMQ或者RocketMQ选一个把订单创建的消息发出去另一个消费者异步处理后续逻辑这也比在Service里同步写一堆代码优雅得多。第三个方向是部署容器化。试着给前后端写Dockerfile用Docker Compose把MySQL、后端、前端三个容器编排起来一条命令把整个应用跑起来。这会逼着你把配置文件外置、日志采集、环境变量管理这些工程化问题都过一遍这些经验在求职面试中很加分。我自己在实际操作中的一个体会是这类教学商城项目最大的价值不是它本身多厉害而是它提供了一个你可以在上面自由折腾的起点。当你把Redis缓存、消息队列、分布式事务这些概念一个个移植进去并跑通之后你对整个系统中“每个组件为什么存在”的理解会比只看教程深刻得多。希望你拿到这套源码之后别急着关掉页面先顺着链路把一个订单跑通再挑一个薄弱环节去改造你学到的东西会远超代码本身。