ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue+MyBatis实战:构建前后端分离的宠物用品商城

SpringBoot+Vue+MyBatis实战:构建前后端分离的宠物用品商城 这几年接了不少外包项目凡是涉及电商类的十个里有七八个都会点名要“前后端分离”的架构。原因很简单前端要快后端要稳两边能独立部署、独立升级团队协作也不互相扯皮。这套在线宠物用品交易网站用的就是经典的SpringBootVueMyBatisMySQL组合刚好覆盖了Java服务端、前端工程化、ORM框架、关系型数据库这几个核心技能点。对于想系统走一遍完整项目实战的开发者来说是一个很合适的参考样本。这篇文章会从技术选型的思考、数据库设计、前后端联调、核心模块实现一路讲到最后的打包部署和避坑经验。不是教科书式的罗列都是我实际开发中踩过坑、优化过的方案希望能给正在做类似毕设、练手项目或者想转型全栈的朋友一点参考。1. 项目整体设计与技术选型思路1.1 为什么选SpringBootVueMyBatis这套组合先说后端。SpringBoot在Java生态里已经是事实上的标准它把Spring的配置简化到了极致内嵌Tomcat让项目直接以jar包运行不需要额外装Web服务器。再加上它有一套非常成熟的自动配置机制大部分基础组件都能做到“引入依赖即用”。对这个项目来说SpringBoot能省下大量繁琐的XML配置把精力花在业务代码上。前端方面Vue在国内的普及度相当高尤其Vue 3组合式API出来之后代码组织更清晰配合Vite开发时热更新非常快。Vue的组件化开发方式天然适合拆分成商品列表、购物车、订单管理这类独立模块每个组件只管自己的数据和视图维护起来很舒服。MyBatis作为半自动ORM框架它的优势在于SQL由开发者完全掌控。电商业务里经常要写复杂的多表关联查询比如查订单的同时要带出商品快照、用户信息、收货地址这种场景用MyBatis的XML映射可以把每一条SQL都调优到位。对比JPA那种全自动框架遇到复杂查询或者需要做SQL优化的场景MyBatis反而更好把控。MySQL在中小规模项目里属于首选数据库开源、免费、社区资料多。前后端分离项目里MySQL 8.0以上的版本在JSON字段、窗口函数等方面也提供了不少新能力像宠物商品这种需要存多个图片地址的场景用JSON字段或单独建表都行。1.2 前后端分离的核心价值与模块划分前后端分离本质上就是把“用户界面渲染”和“业务逻辑处理”这两个职责彻底拆开。前端跑在浏览器里通过HTTP接口与后端交互返回的JSON数据负责驱动页面渲染。这样做的直接好处有三点第一并行开发效率高。前端开发人员在Vue脚手架里写好页面用Mock数据联调后端人员同时开发Controller和Service。只要接口约定明确两边互不阻塞。第二部署灵活。前端构建出的静态文件扔给Nginx或直接放进SpringBoot的static目录后端单独起服务。出问题时能快速定位是渲染异常还是接口异常。第三多端复用。接口做一次PC端、移动端H5、小程序都能共用。做宠物用品商城后续很可能要扩展小程序商城接口层是现成的。项目模块划分上我按业务边界分成了用户模块、商品模块、购物车模块、订单模块和分类模块。每个模块内部遵守标准的Controller-Service-Mapper三层结构模块之间通过统一的返回体Result对象通信。这样做的好处是代码结构清晰改动一个模块不影响其他模块后续做功能扩展也很轻松。2. 数据库设计从业务闭环到表结构的落地2.1 核心业务闭环的表设计思路宠物用品交易网站虽然比不上海鲜市场那种超大吞吐量但该有的业务闭环一个不少。用户逛商城、浏览商品、加购物车、下单结算、支付、管理收货地址最后查看订单状态和物流信息这个流程在设计表结构的时候要从头到尾理顺。用户表user是最基础的字段包括用户名、加密密码、手机号、邮箱、头像地址、注册时间和状态标记。密码存储强烈建议用BCrypt加密不要用MD5那种已经被证实效率极高的碰撞方案。商品表product需要多花点心思。宠物用品有几个特点SKU可能包含规格比如猫粮的重量1.5kg、5kg、需要展示多张轮播图、部分商品要做折扣价。所以产品表拆成product和product_sku两张表会更合理。product存主信息比如名称、主图、详情描述、所属分类、销量、上下架状态product_sku存具体的规格和价格库存。购物车表cart不能简单地存一个商品ID列表因为用户加入购物车时的价格是当时的实时价格下单时应该取最新的商品价格来结算而不是取加购时的价格。购物车表存用户ID、商品ID、SKU ID、数量、勾选状态、加入时间即可。订单表orders设计上有一个关键点要存商品快照。用户下单之后商品可能改名、调价、下架但订单里的商品名称和成交价必须保持不变否则会引发纠纷。所以订单表里我存了order_no、user_id、总金额、优惠金额、实付金额、收货人信息、订单状态这些字段另建一张order_item表存商品快照信息包括商品名称、主图、单价、数量。地址表user_address就是常规的省市区详情、收货人、联系电话、默认标记不做太复杂的结构。2.2 多表关联、索引与常用SQL优化细节设计完表之后还要考虑查询效率。宠物用品商城的商品列表页和搜索页是访问量最大的接口SQL写法直接决定了页面响应速度。商品列表查询通常要关联分类表、SKU表。SKU表查询最低价格的时候如果用GROUP BY子查询数据量大了会很慢。我当时的做法是product表里冗余一个price字段保存该商品SKU的最小价格列表页直接查product单表详情页再展开查SKU列表。牺牲一点存储换查询速度在这种业务场景下很划算。订单查询也一样orders表和order_item表是一对多关系。查询订单列表时如果用“先查订单再循环查商品明细”的方式会产生N1问题。正确做法是一次JOIN查出来所有订单及其商品项在Java内存里按orderId分组组装。这种思路在MyBatis里可以用collection标签实现嵌套结果映射一篇博文讲不完后面在代码部分详细说。索引方面的经验是order表必须对user_id建索引因为个人订单列表是最常见的查询order_no要建唯一索引因为它对外暴露且要支持按订单号检索product表的category_id和status要建联合索引支撑分类筛选和上下架过滤cart表的user_id和product_id要建联合索引防止同一用户重复插入同一商品。3. 前后端联调与核心功能实现3.1 用户认证JWT方案的设计与拦截器实现前后端分离下Session共享是个麻烦问题。如果前端部署在一套域名后端在另一套域名跨域请求携带Cookie的配置非常繁琐所以这个项目我直接用了JWTJSON Web Token做登录态管理。用户登录成功后后端生成一个包含用户ID、用户名、过期时间等信息的JWT令牌返回给前端。前端把令牌存在localStorage里之后每次请求在请求头加一个Authorization字段值为“Bearer空格令牌”。后端定义一个拦截器拦截所有需要登录的接口从请求头取令牌并校验校验通过后把用户信息放入ThreadLocal供后续业务使用。JWT的几个配置要点密钥要放在application.yml里用环境变量或配置中心管理绝对不要硬编码在代码里提交到仓库。过期时间我设置的是24小时客户端在收到401状态码时自动跳回登录页。令牌签发和解析用jjwt库代码非常简洁几行就能完成。拦截器的注册在WebMvcConfigurer里实现需要排除登录接口、注册接口、商品浏览接口和图片资源路径。这里有个细节前端页面骨架加载时可能会并发请求多个接口导致短时间内多次校验令牌。可以在令牌验证通过后把它放进一个短效缓存减少无谓的解密开销在高并发场景下实测有明显的性能提升。3.2 商品模块与图片上传的实现细节商品模块算是整个项目的门面。首页的轮播图、商品分类导航、商品列表、搜索、详情页都依赖这个模块。图片上传实现时我采用了本地磁盘存储数据库存相对路径的方式。上传接口接收MultipartFile按照日期创建目录文件名用UUID重命名避免中文名和重复名导致的路径问题。存储路径在配置文件中设定比如/usr/local/pet-shop/images/并且在WebMvcConfigurer里把该路径映射为静态资源路径这样前端的img标签可以直接访问到图片。商品列表接口分页用了PageHelper插件一个PageHelper.startPage(pageNum, pageSize)就能完成物理分页。需要注意PageHelper只有在紧跟着的第一条查询语句起作用所以不要在startPage调用之间插入其他数据库操作否则分页会失效。这种细节问题网上随便一搜就是一堆人问实际开发中很容易踩。商品详情页的数据组装要稍微留心product基本信息、SKU列表、详情图文、销量和评价的简单统计尽量用一个聚合接口返回前端一次拿到所有数据少一次请求就少一次白屏等待。3.3 购物车与订单流程的完整业务链购物车模块在页面上体现的是“加购、加减数量、勾选、删除、计算总价”这些交互但真正的难点在于下单时的数据一致性处理。加购接口要做几个判断商品是否存在且上架、SKU是否有库存、加购数量是否超出限制。如果发现商品ID和SKU ID组合在购物车里已存在做的是数量累加而不是重复插入。结算时前端会提交购物车中勾选的那些商品条目后端拿到这些条目ID后锁定对应的SKU行重新校验价格和库存。从数据库查最新的SKU价格来计算订单金额而不是直接信任前端传过来的价格。后端必须始终以数据库为准这是电商系统的铁律。下单过程我放在一个事务方法里执行步骤包括根据购物车条目ID查出商品明细。批量锁定SKU并校验库存。扣减库存库存不足则回滚并抛出提示。计算总金额、优惠金额、实付金额。生成订单号和订单记录插入order_item商品快照。删除购物车中已结算的商品条目。订单状态我用数字字典管理0待支付、1已支付待发货、2已发货、3已完成、4已取消。支付模块对接的沙箱环境真实项目中换成微信或支付宝只需要替换支付服务实现类尽量避免在业务代码里写死支付逻辑。4. 部署落地从本地联调环境到云服务器4.1 本地开发环境的协调管理前后端分离项目在本地联调时最烦的问题就是跨域。前端工程运行在Vite提供的开发服务器上默认端口可能是5173后端SpringBoot运行在8080端口两边端口不同必然产生跨域请求。解决方式有两种一种是后端配置全局CORS。写一个WebMvcConfigurer的配置类允许所有来源、所有请求头、所有方法前后端联调阶段这样最简单。另一种是前端Vite配置代理在vite.config.js里设置server.proxy把以/api开头的请求转发到http://localhost:8080。这样浏览器看到的请求是同源的绕过了跨域限制而且配置代理的方式在打包后由Nginx做反向代理时思路完全一致。实际项目里我更推荐用Vite代理的方式因为它在后续切换到生产环境Nginx部署时只需要把代理规则迁移到Nginx配置里前后端代码完全不用改。数据库连接方面本地项目我推荐直接装MySQL 8.0注意时区和连接参数。驱动的URL要带上serverTimezoneAsia/Shanghai和useSSLfalse不然会报时区异常。MySQL 8.0的驱动类名也要用com.mysql.cj.jdbc.Driver而不是老项目里常见的com.mysql.jdbc.Driver。4.2 Vue项目打包放进SpringBoot的完整流程很多人在部署前后端分离项目时会犯迷糊前端打包出来的dist目录怎么处理方案其实很多扔到Nginx、扔到OSS、扔到服务器某个目录。如果是一个单体应用包也可以直接把dist文件复制到SpringBoot项目的src/main/resources/static目录下重新打包成jar之后后端同时提供API和页面服务。具体操作步骤是先在前端项目根目录执行npm run build生成dist目录。然后把dist目录下的文件全部复制到后端项目的src/main/resources/static目录中。再次打包后端的mvn package产出的jar包启动时直接访问服务器IP加端口就能看到商城首页。这个方案的优点是部署简单不需要再单独安装Nginx缺点是前端更新必须跟着后端一起重新打包部署。对小型项目、课程设计、个人练手项目来说够用。要注意一个细节Vue项目路由用的是history模式时前端刷新页面或者直接访问某个子路由地址后端找不到对应的Controller会返回404。解决方法是让后端把非API路径的请求全部转发到index.html。在SpringBoot里可以通过实现ErrorPageRegistrar把404错误页面映射到index.html或者直接用Nginx的try_files配置更省事try_files $uri $uri/ /index.html。4.3 云服务器部署与MySQL环境搭建正式部署时我建议还是把后端jar包和前端静态文件分开。用Nginx代理静态资源用反向代理转发API请求到SpringBoot应用这样做的好处是企业级项目都是这套组合学会了可以无障碍迁移到工作中。服务器上的MySQL安装完要注意几个生产环境必须做的优化默认端口3306尽量不要对外开放只允许内网或者本机访问。后端应用如果和MySQL在同一台机器连接地址直接用127.0.0.1。MySQL 8.0的默认认证插件是caching_sha2_password如果后端驱动版本不够新会握手失败。确保MySQL驱动版本在8.0以上即可。建表时统一使用utf8mb4字符集因为宠物用品评价里可能会出现emoji表情utf8mb4能完整支持四字节UTF-8字符。部署时的环境变量配置也很重要。SpringBoot的application.yml里数据库密码、JWT密钥这些敏感信息不要写在配置文件里提交到代码仓库。启动jar包时用--spring.datasource.password参数传递或者用环境变量占位符${DB_PASSWORD}的方式这样就算代码仓库泄露真实密码也不会跟着暴露。5. 关于项目源码与进一步扩展的几点经验5.1 源码的阅读建议很多人拿到一套完整源码就直接跑起来点一遍觉得能跑通就完事了这样其实错过了学习的最佳方式。我建议的阅读路径是先启动项目把功能点全都点一遍对项目有一个整体认知。然后从数据库脚本开始看搞明白每一张表是干什么用的、表之间的关联关系组织得是否合理。再然后看后端入口和配置类理解自动装配加载了什么组件。接着按一条业务链路去追踪比如“用户登录→获取令牌→浏览商品→加购→下单→查看订单”这条链路把涉及到的Controller、Service、Mapper实现逐个读一遍。最后看前端的API封装和路由守卫理解前端是怎么配合JWT完成权限控制的。如果把前端每个接口的调用方式和后端每个接口的请求路径对照着看一遍前后端分离的整个数据流就非常清晰了。这就是自己从零写一遍代码最大的收获。5.2 当前项目的扩展空间宠物用品交易网站虽然是一个标准的商城项目但在真实业务里还远不算完整。以下几点是可以继续扩展的方向按优先级排序第一接入管理员后台。目前项目如果有管理端那么核心业务才算闭环。商品上下架、SKU管理、订单发货、数据统计都需要后台支撑。第二引入Redis缓存。商品详情、首页轮播这类读多写少的数据非常适合用Redis做缓存。登录令牌也可以考虑放进Redis做主动失效控制。第三引入消息队列。下单后需要发送短信通知、订单超时自动取消用消息队列或延迟队列实现会更优雅。RabbitMQ的延迟插件可以直接应对超时关单场景。第四搜索引擎。商品搜索目前如果是数据库LIKE查询数据量大了以后体验会越来越差。引入Elasticsearch或者直接用MySQL全文索引能明显提升搜索体验。5.3 学习前后端分离项目的最佳姿势最后再分享一个我个人的学习习惯。拿到这类完整项目不要只想着“把源码跑起来交差”。你可以主动给自己设置几个改造目标比如改成宠物周边商城换成咖啡豆商城或者把设计稿换一套皮肤。改造的过程中必然要动数据库、动接口、动页面组件这个过程中积累的问题和解决方案比项目源码本身珍贵得多。遇到报错不要急着找答案。先看控制台完整堆栈定位是前端网络层问题还是后端业务层问题然后顺着调用链反向追代码。我见过太多人看到一个报错截图就直接去百度其实解决这个报错只需要往前看一行代码。拆解问题的过程才是真正长本事的过程。
返回列表