资讯动态

SpringBoot+Vue+MyBatis+MySQL前后端分离商城实战与排坑

发布时间:2026/10/17 10:19:23 来源:尧图企业网站定制
前后端分离网上商城系统听起来是个已经烂大街的课题但真正动手去拉一套 SpringBootVueMyBatisMySQL 的源码下来再从头到尾把它跑通、改明白就能发现里面藏着一大堆课堂里不会讲的细节。这个项目之所以值得单独拿出来说是因为它几乎覆盖了国内中小团队做业务系统时最常用的一整条技术链路后端用 SpringBoot 撑接口MyBatis 管数据访问MySQL 做存储前端用 Vue 写页面再通过 HTTP 接口做前后端完全解耦。对于正在学框架整合、准备找工作、或者想快速搭一套电商 Demo 做毕业设计的人来说这都是一份可以直接“抄作业”的实战材料。我自己在接手这套源码之前也踩过不少类似的坑。第一次跑这种前后端分离项目时光是跨域、Token 失效、数据库时区这几个问题就折腾了一个周末。所以这篇文章不只是把项目结构罗列一遍我会顺着“为什么这样设计”“实际部署怎么操作”“遇到问题怎么查”这条线把整套系统的关键环节拆开讲清楚。前端怎么配置代理、后端怎么拦截鉴权、数据库表为什么这么建、打包之后怎么放到 Nginx 上每一块我都会给出可复现的操作方式。哪怕你之前完全没碰过这类项目跟着走一遍也能把整套系统的运行逻辑串起来。1. 项目整体设计与思路拆解1.1 这个商城系统到底做了什么从功能面上看这套系统具备一个标准网上商城的基本骨架用户注册登录、商品浏览、购物车管理、下单结算、订单列表、后台管理。用户端负责“逛”和“买”管理端负责“上架”和“处理订单”。所有功能都围绕商品、订单、用户这三条核心数据线展开再加上购物车这个典型的过渡态实体。比较关键的一点是前后端完全分离。前端只关心页面渲染和用户交互后端只负责业务逻辑和数据读写。两者通过 JSON 格式的 RESTful 接口通信前端通过 HTTP 请求拿到数据再渲染成页面后端不参与任何页面跳转。这种模式下前端和后端的开发可以并行进行只要提前把接口约定好。实际项目中前端把接口文档里的字段定义好后端对着文档写实现两边互不阻塞这是团队协作效率明显高于单体 JSP 项目的地方。1.2 为什么选择前后端分离方案很多初学者会问直接用 SpringBoot 写模板渲染不好吗为什么非要拆成两个工程我自己的感受是前后端分离带来的好处不是“炫技”而是实实在在的开发效率和安全边界。第一职责边界清晰。后端工程师不用再关心页面上按钮的颜色、弹窗的样式前端工程师也不需要懂得 Java 代码怎么处理事务。一旦项目变大这种边界能避免大量互相等待和代码冲突。第二部署方式灵活。前端打包成静态文件后扔给 Nginx 托管后端打成一个 Jar 包独立跑在应用服务器上两者可以分别扩容。遇到性能瓶颈时前端用 CDN 加速后端做多实例负载均衡互不影响。第三接口复用性高。同样的后端接口可以同时服务 Web 端、管理后台甚至未来的小程序端和移动 App。前后端分离之后后端接口就是一套独立资产不用为每个端重新写一套业务逻辑。当然这种方式也有代价跨域处理、Token 鉴权、接口文档维护都变成了必须正面解决的问题。后面我会专门讲这些坑这里先明白“为什么要分”更重要。1.3 技术选型背后的理由技术栈选 SpringBootVueMyBatisMySQL对国内开发者来说几乎是最顺手的组合但它不是随便凑的。SpringBoot 负责解决“配置地狱”的问题。早期用 Spring 开发XML 配置一大堆光搭环境就能劝退不少人。SpringBoot 通过自动配置和 starter 机制把常用的配置都沉淀成规范开发者只需要关注业务代码本身。在这个项目里SpringBoot 承担了接口发布、依赖注入、事务管理、参数校验等核心职责。MyBatis 则处于数据访问层。相比 JPA 那种高度自动化的 ORMMyBatis 给了开发者直接编写 SQL 的空间。商城这类业务里商品列表的分页查询、订单明细的多表关联、销售统计的聚合计算都是 SQL 强项手写 SQL 反而更直观、更容易优化。而且 MyBatis 的 XML 文件把 SQL 和 Java 代码分开后期换 DBA 来调优 SQL 时不需要碰 Java 代码。Vue 的选型理由更倾向于前端生态的成熟度。Vue 上手曲线比 React 平缓模板语法直观中文资料丰富配合 Element UI 这类组件库能很快搭建出一套风格统一的管理后台。商城这种列表页、表单页、详情页居多的场景Vue 的组件化开发方式特别顺手。MySQL 就不用多说了中小规模业务场景下最稳妥的开源关系型数据库配合 InnoDB 引擎支持事务商品扣库存、订单生成这些操作能得到可靠的保障。整套选型没有特别炫技的地方讲究的就是稳定、好招人、资料多、容易排查问题。2. 数据库设计与后端核心实现2.1 核心表结构与关系设计这套系统数据库设计遵循了典型的电商范式核心表可以归成四组。用户部分最少需要一张用户表字段包括用户名、密码、手机号、邮箱、头像地址、状态、注册时间。密码字段务必存加密后的结果不能明文存。这里我用的是 BCrypt 加密比 MD5 加盐更安全验证方式也简单。商品部分包含商品表、商品分类表和商品图片表。商品表需要价格、库存、销量、上下架状态、描述等字段商品与分类是多对一关系为了方便图片管理图片单独建表通过商品 ID 关联。订单部分是整个系统数据关系里最复杂的。订单主表记录订单号、用户 ID、订单总金额、支付状态、物流单号、收货地址快照订单明细表记录每件商品的快照信息包括下单时的单价、数量、商品名称。这里一定要做“快照”不能直接引用商品表的当前价格否则商品改价之后用户的历史订单里价格也会跟着变这在电商系统里属于事故。购物车则是一个典型的用户与商品之间的中间状态表包含用户 ID、商品 ID、数量、选中状态。购物车数据其实随时可以被清空不需要太强的业务约束所以表结构可以做得轻量一些。这些表之间的关联关系用外键也可以但实际项目中我见到更多做法是不用物理外键只保留逻辑关联通过代码保证数据一致性。原因很简单物理外键在高并发写入和分库分表场景下会成为瓶颈而且电商系统的数据一致性更多靠事务和补偿机制保障不去依赖数据库自身的级联约束。2.2 SpringBoot 工程结构与配置后端工程的包结构是按照 controller、service、mapper、entity、common 这几层来组织的。Controller 只做参数接收和结果封装不写业务逻辑。Service 层处理业务规则比如下单时先查库存、再生成订单、再扣减库存这个操作必须放在同一个事务方法里。Mapper 层通过 MyBatis 接口访问数据库Entity 与数据库表字段对应。配置文件里最重要的几个地方分别是数据源、MyBatis 映射路径和端口。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/mall?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:/mapper/*.xml type-aliases-package: com.example.mall.entity configuration: map-underscore-to-camel-case: true数据源 URL 里的characterEncodingutf8和serverTimezoneAsia/Shanghai是必填项。不设置时区数据库连接会报时区错误不设置 UTF-8后面插入中文很容易乱码。MyBatis 的map-underscore-to-camel-case打开后数据库的user_name字段可以自动映射到 Java 属性的userName省掉一大片resultMap手写映射。很多人第一次跑源码失败问题就出在配置文件上。端口被占用、数据库密码不对、MySQL 版本与驱动不匹配这些都会让项目启动报错。后面部署部分我会再展开讲。2.3 基于 MyBatis 的数据访问层MyBatis 的使用方式在这里非常典型。Mapper 接口只定义方法签名对应的 SQL 写在 XML 文件里。以用户查询为例接口定义如下public interface UserMapper { User findByUsername(String username); int insert(User user); }对应 XML 里写 SQLselect idfindByUsername resultTypecom.example.mall.entity.User SELECT id, username, password, nickname, phone, email FROM user WHERE username #{username} /select这里要注意的是#{username}和${username}的区别。前者是预编译参数占位符会生成?占位能有效防止 SQL 注入后者是字符串拼接直接把值拼进 SQL如果要用来做动态表名或排序字段时才能用。商城系统所有用户输入的可疑参数都应该用#{}。动态 SQL 也是 MyBatis 的价值所在。商品列表查询通常有多个可选条件比如按分类筛选、按关键词模糊搜索、按价格区间过滤这种场景直接用where和if标签动态拼条件。select idsearchProducts resultTypeProduct SELECT * FROM product where if testcategoryId ! null AND category_id #{categoryId} /if if testkeyword ! null and keyword ! AND name LIKE CONCAT(%, #{keyword}, %) /if if testminPrice ! null AND price gt; #{minPrice} /if /where /select这个写法的好处是查询条件是可选的代码里不用写一堆 if 拼接 SQL 的重复逻辑。但也要注意动态 SQL 写多了之后SQL 的可读性会下降建议把复杂查询拆分成多个独立方法而不是一个方法塞进全部条件。2.4 认证与授权设计前后端分离下Session 方案不再好使因为前端可能部署在另一个域名下而且移动端也没有 Cookie 概念。这套系统用的是基于 Token 的无状态认证具体采用 JWT 方案。用户登录成功后后端生成一个包含用户 ID、用户名和过期时间的签名 Token返回给前端。前端把 Token 存在 localStorage 中之后每次请求都通过请求头Authorization: Bearer token带给后端。后端通过拦截器统一校验 Token校验通过后把用户信息放入请求上下文Controller 里再取出来用。拦截器实现里最核心的部分如下public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); try { Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); return true; } catch (Exception e) { response.setStatus(401); response.getWriter().write({\code\:401,\message\:\登录已过期\}); return false; } } response.setStatus(401); response.getWriter().write({\code\:401,\message\:\未登录\}); return false; } }需要区分的是并非所有接口都需要登录。商品列表、商品详情、首页轮播图这些公开接口应该放行而加购物车、下单、查看个人订单这些接口必须鉴权。做法很简单在拦截器里维护一个白名单路径列表配置/api/product、/api/category下的公开接口其他接口一律拦截。这个设计直接决定了前端访问时会不会莫名其妙跳出 401务必提前规划好。Token 过期时间是另一个容易被忽略的点。设置太短用户逛着逛着就被踢下线设置太长安全性下降。我一般给商城项目设置 2 小时过期配合前端在请求拦截器里检测 401 状态码统一跳回登录页并清理本地凭证体验比较合理。3. 前端实现与接口联调要点3.1 前端工程结构与路由划分前端部分使用 Vue 单页应用的方式搭建工程内部按视图划分目录。views目录下分成home、product、cart、order、user、admin等模块每个模块对应一组相关页面。公共组件放到components目录网络请求统一放到api目录工具函数放到utils目录。路由设计上用户端和管理端是两套独立布局。用户端包含首页、商品列表、商品详情、购物车、结算页、个人中心管理端则包含商品管理、订单管理、用户管理、数据统计。为了区分我这里用嵌套路由实现父路由/mall加载用户端布局子路由渲染具体页面管理端父路由/admin加载侧边栏布局。const routes [ { path: /mall, component: UserLayout, children: [ { path: home, component: Home }, { path: product/:id, component: ProductDetail }, { path: cart, component: Cart, meta: { requiresAuth: true } }, { path: checkout, component: Checkout, meta: { requiresAuth: true } } ] }, { path: /admin, component: AdminLayout, meta: { requiresAuth: true, requiresAdmin: true }, children: [ { path: products, component: ProductManage }, { path: orders, component: OrderManage } ] } ]路由守卫也是前后端分离项目必须处理的部分。全局前置守卫里检查目标路由的meta.requiresAuth如果为真且本地没有登录凭证就跳转到登录页。管理端路由还要额外检查用户角色防止普通用户直接输入管理端地址访问页面。这只是前端层面的拦截真正安全的校验仍以后端接口鉴权为准前端守卫只是用户体验上的第一道闸门。3.2 状态管理与接口请求封装商城系统的状态不像复杂后台系统那样全局共享很多数据大多数状态是页面级的所以这里不必把路由保活和大量数据都塞进全局 Store。但购物车数量和用户信息这两类数据会被多个页面读取放进全局状态里确实方便。我习惯用简单的 reactive 对象维护这两个模块不额外引入太重量的状态库避免新手一上来就被复杂的 Store 概念绕晕。接口请求层是前端工程质量的关键。直接用axios裸调很容易出现回调嵌套和错误处理代码重复的问题。我在项目里做了一层统一封装把 BaseURL、超时时间、请求头注入、响应拦截器全部收敛到http.js一个文件里。import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) request.interceptors.response.use( response { return response.data }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) location.href /login } return Promise.reject(error) } ) export default request这里有一点很关键开发环境里baseURL直接写成/api配合 Vite 或 Webpack 的代理规则转发到后端这样可以绕开开发环境的跨域问题。生产环境则靠 Nginx 把/api路径反向代理到后端服务同一份代码在不同环境都能正常工作。如果在前端写死http://localhost:8080部署到线上就还得改代码维护成本高。接口方法按模块拆到不同的文件里比如api/product.js里放商品相关的请求函数api/order.js里放下单和查询订单函数。页面里不再直接调用 axios而是调用这些语义清晰的函数。这样做的好处是后端接口地址变了只需改一个文件页面层完全不受影响。3.3 核心页面与组件拆解商城首页结构并不复杂顶部导航、搜索框、分类菜单、商品瀑布流。这个页面拆成NavBar、CategoryMenu、ProductCard三个组件。商品卡片组件接收一个商品对象作为 prop渲染图片、标题、价格点击后通过路由跳转到详情页组件内部不直接发起数据请求数据统一由页面容器通过接口获取后分发。商品详情页是信息密度比较高的页面。上半部分是商品大图、标题、价格、库存、数量选择器和“加入购物车”按钮下半部分是商品详情描述 Tabs。加购操作的核心逻辑是调用后端接口传入商品 ID 和数量同时更新全局购物车数量。这里比较容易犯的错是在商品状态变化时没有重置数量选择器导致用户切换到另一个规格时数量还停留在上一次操作的值我在源码里已经处理了这个细节。购物车页面则是列表加多选加结算的组合。每一行展示商品信息、单价、数量加减组件、小计。底部固定结算栏显示选中商品的总价。这里需要前端实时计算选中商品总价而不是依赖后端返回。后端只在真正提交订单时再做一次总价校验防止用户篡改价格。管理端页面可以充分展示 Vue 加 Element UI 的开发效率。商品管理页就是一个典型的数据表格加搜索表单加弹窗表单组合表格列里放商品图、名称、价格、库存、上下架 Switch 开关操作列里放编辑和删除按钮。这些页面看起来高大上实际上是组件库组合出来的并不需要多少原创 CSS 能力。3.4 联调阶段容易踩的接口问题前后端联调是最容易出问题也是最容易暴露设计缺陷的阶段。我遇到过的典型情况有这么几类。接口返回格式不统一。有人把成功结果直接返回数组错误时返回字符串有人正常返回 JSON异常时返回 HTML 错误页。前端响应拦截器拿到数据后无法统一处理。这套项目里我和后端同学约定所有接口统一返回{ code: 200, data: ..., message: ok }结构code 非 200 就按业务异常处理。有了这个约定前端才能在拦截器里做到逻辑统一。参数类型不一致。后端接口定义的参数是Integer前端传的是字符串形式或根本没传Spring MVC 的类型转换会直接报错。前端联调时遇到 400 错误优先检查请求参数的类型和字段名。列表数据字段名不一致。后端驼峰命名前端用的是下划线渲染出来就是页面空白。在前后端分离模式下接口文档里必须对每个字段名达成一致。如果一个项目里没有接口文档这个问题的排查成本会非常高建议即使不做完整文档也至少用接口测试工具把关键接口维护一份方便两边对齐。4. 从源码到上线完整部署实战4.1 环境准备与基础软件安装把源码跑起来的第一步是准备环境。整个项目需要 JDK 1.8 或 11、Maven 3.6、Node.js 14、MySQL 5.7 或 8.0、Nginx。这里我逐个说避免哪个环节漏掉导致启动失败。JDK 安装后要配好JAVA_HOME环境变量命令行里输入java -version能输出版本号才算成功。Maven 需要配置阿里云镜像仓库否则从中央仓库下载依赖的速度会让人等得崩溃。在settings.xml里的mirror节点配置方式比较简单mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirrorNode.js 安装后建议把 npm 镜像源也切到国内源命令是npm config set registry https://registry.npmmirror.com。我见过太多人在启动前端项目时卡在依赖下载阶段这几个镜像配置能极大提升成功率。MySQL 安装后需要手动创建一个数据库并把项目提供的 SQL 脚本导入。命令行操作是mysql -u root -p create database mall default character set utf8mb4; use mall; source /path/to/mall.sql;数据库名称要和后端配置文件里的库名一致。如果项目里有两个 SQL 文件比如一个建库一个示例数据两个都要按顺序导入。示例数据里通常会有测试账号、演示商品和分类信息导入后商城页面才能看到内容。4.2 后端打包与启动后端是一个 Maven 工程打包命令非常简单cd backend mvn clean package -DskipTests打包成功后target目录下会生成一个可执行 Jar 包。启动前先确认application.yml里的数据库账号密码和本地一致然后用命令启动java -jar target/mall-backend-1.0.0.jar如果看到Started Application in x.x seconds的日志说明后端起成功了默认监听 8080 端口。此时可以用接口测试工具访问一下公开接口比如http://localhost:8080/api/product/list返回 JSON 数据就说明接口链路通了。这里有个常见的坑如果 SQL 脚本里的数据没有导入或者导入时选择了错误的字符集后端能启动但接口查不到数据页面就会一直空白。遇到这种情况先检查数据库里有几张表、每张表是否有数据而不是去改前端代码。4.3 前端构建与 Nginx 托管前端构建前需要确认环境文件配置。Vite 项目一般是.env.production文件内容里指定接口前缀VITE_API_BASE_URL/api然后执行构建命令cd frontend npm install npm run build构建成功后dist目录下就是纯静态文件。把dist目录部署到 Nginx 的html目录下再改一份 Nginx 配置核心内容有两块一是静态文件访问规则二是/api路径的反向代理。server { listen 80; server_name your-domain.com; root /data/mall/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; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这个配置里最容易被忽略的是try_files $uri $uri/ /index.html;这一行。Vue 是单页应用路由切换由前端控制当用户直接访问/product/1这类地址时Nginx 找不到对应文件如果不做 try_files 回退到 index.html刷新页面就会 404。这算是前后端分离项目部署时的经典问题网上能搜到大量提问大半都是缺少这行配置导致的。location /api/里的proxy_pass http://127.0.0.1:8080/;要注意结尾斜杠。带斜杠表示把/api前缀替换成根路径再转发比如前端请求/api/auth/login后端收到的实际路径是/auth/login。如果不带斜杠后端收到的还是/api/auth/login是否匹配取决于后端接口前缀设计。我建议统一采用前端接口带/api前缀、后端接口不带前缀的方式Nginx 这里带斜杠转发这样结构最清爽。4.4 外网环境部署的额外注意点如果只是本机跑通上面操作已经足够了。但部署到云服务器时还有几个容易漏掉的细节。首先要检查安全组和防火墙是否放开了 80、443 端口。很多初学者在云服务器上部署完成后本地访问不了排查半天发现是安全组没放行端口。其次MySQL 不要向公网开放只监听 127.0.0.1后端通过内网访问数据库。最后使用 HTTPS 时还要在 Nginx 配置 SSL 证书并把 80 端口流量重定向到 443。证书申请现在已经非常方便安全配置不能省。数据库连接池配置也应该在正式部署后调整。开发环境的连接数设置很小到了线上并发一大立刻不够用。建议把 SpringBoot 数据源配置里的最大连接数调到 50 左右并设置合理的连接超时和空闲回收策略。5. 排查实录几个绕不开的坑5.1 跨域问题到底怎么解前后端分离项目第一次把页面和接口连起来时最常撞见的就是跨域。浏览器的同源策略限制页面脚本只能访问同源的资源前端跑在http://localhost:5173后端跑在http://localhost:8080端口不同跨域就会拦截。开发环境最省事的方案是前端代理。Vite 的vite.config.js里配置server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样浏览器访问的还是localhost:5173/api/xxxVite 在开发服务器层面把请求转发给了后端浏览器层面没有任何跨域问题。生产环境用 Nginx 反向代理同理。我建议优先用代理方案而不是后端开 CORS因为代理方案和环境部署方式保持一致而且不用在后端增加一堆允许跨域的配置暴露不必要的网络策略。但如果你非要后端开 CORS 也不是不行只是要小心。后端的 CORS 配置如果写成allowedOrigins(*)等于允许任何网站调用你的接口安全隐患很大。有些浏览器对带凭证的请求还有额外限制。综合起来代理方案是更合理的默认选择。5.2 登录状态异常的问题排查用户登录后请求接口一直 401或刷新页面后需要重新登录这种问题在联调阶段几乎必然出现。常见的根因有几类。第一Token 没有正确传递。前端在请求拦截器里把 Token 放到请求头但拦截器配置顺序出错或前端取 Token 的 key 跟存的时候不一致都会导致后端收不到。可以先用浏览器开发者工具看 Network 面板确认请求头里有没有Authorization字段。第二后端拦截器放行规则没配好。有时候登录接口自身也被拦截器保护了用户提交登录请求时根本没有 Token后端直接返回 401登录页自己都进不去。遇到这种问题检查一下拦截器的白名单里有没有登录和注册接口。第三刷新页面丢失用户信息。JWT 是无状态的用户信息只存在于前端存储里刷新后从存储中重新读取即可。如果刷新后路由跳转依赖后端返回的用户信息而这个信息没有持久化就会出现“登录成功但刷新就白屏”的怪异现象。正确的做法是前端把用户基本信息也存起来刷新时先展示存储的值再通过接口校验有效性。5.3 商品并发库存扣减问题商城系统最容易出现数据一致性问题的场景就是下单扣库存。如果代码写成Product product productMapper.selectById(productId); if (product.getStock() quantity) { product.setStock(product.getStock() - quantity); productMapper.updateById(product); }在并发量稍高的时刻两个请求同时读到剩余库存为 10同时判断可以购买同时减成 9最终库存变成 9而实际可能已经卖出两单出现超卖。高并发电商系统的库存方案是个深水区但中小项目里至少应该用 SQL 层的原子扣减来做基础防护UPDATE product SET stock stock - #{quantity} WHERE id #{id} AND stock #{quantity}这条 SQL 通过数据库行锁保证扣减动作原子化同时用stock #{quantity}条件防止扣成负数。如果影响行数为 0说明库存不足直接抛出友好提示。注意要先扣库存再插入订单数据两条操作必须放在同一个数据库事务里。这套方案应对中小商城场景完全够用也是理解分布式锁、消息队列等高级方案的跳板。5.4 中文乱码与编码规范中文乱码在前后端分离项目中贯穿了数据库、后端、前端三个环节。数据库表和字段要设置utf8mb4字符集MySQL 版本较老时还需要设置默认字符集。JDBC 连接 URL 里要带characterEncodingutf8。后端接口返回的 Content-Type 要带charsetUTF-8。前端页面本身用 UTF-8 编码。一个经典场景是数据库表是 utf8但建表时指定了DEFAULT CHARSETutf8mb4连接 URL 少写了characterEncodingutf8结果插入中文后读出来全是问号。排查这种问题时可以分三步验证先用 SQL 工具直接看表里存的中文是否正常如果是乱码说明问题出在数据写入环节如果数据库里正常但页面显示乱码说明问题出在接口返回或前端解析环节如果接口返回正确但页面上乱码则要检查 HTML 的 meta 声明和 JS 文件编码。把这三个环节的编码策略统一成 UTF-8通常能一次性解决大多数乱码问题。我在源码里已经把所有涉及编码的地方统一处理了如果你从零搭项目建议一开始就按这个标准做。5.5 端口占用与启动失败速查这个问题的出现频率其实很高而且报错信息常常误导人。后端启动时如果提示Port already in use: 8080用命令查一下是谁占用了端口。Linux 下命令是netstat -tunlp | grep 8080Windows 下是netstat -ano | findstr 8080查出来之后强杀进程或者直接改后端端口。前端启动报端口占用同理Vite 默认端口是 5173如果被占用可以在配置里改成其他端口。还有一类启动失败是依赖版本冲突SpringBoot 版本和 MySQL 驱动版本不兼容启动时直接抛类加载错误。建议严格使用源码里指定的版本不要一上来就升级到最新版等全部跑通后再考虑版本升级的事。6. 基于这套源码的二次开发建议6.1 拿到源码后先做什么不管你是下载了完整源码包还是从代码仓库克隆的项目我第一次拿到源码后的顺序都是固定的先看 README再看 SQL 脚本再配环境最后启动。这一步能帮你搞清楚项目需要哪些环境依赖、数据库初始数据长什么样、默认端口是多少避免盲猜。如果源码里缺少说明文档那就先打开后端配置文件了解数据库连接信息再打开前端的环境配置文件了解接口地址和代理规则。把这两个文件读懂了项目的基本运行方式也就清楚了。很多新手直接跳过这些环节上来就npm run dev报错后又不知所措其实多数报错都能在前面两步里找到答案。6.2 从哪几个方向扩展这套系统跑通之后扩展方向很清晰。商品模块可以加多规格属性比如颜色、尺寸、版本组合出多个 SKU每个 SKU 携带独立的库存和图片这对真实电商项目是刚需。订单模块可以接支付模拟流程增加支付回调接口配合订单状态机把待支付、已支付、已发货、已完成整条生命周期串起来。管理端可以补数据统计报表用 ECharts 展示每日订单量和销售额曲线后端写对应的聚合查询 SQL。这些扩展都是在现有骨架上的增补不会破坏原有架构。只要按照分层原则新增 Controller、Service、Mapper前端新增对应的路由和组件就能平滑演进成更完整的系统。6.3 学习这套代码的正确姿势如果是为了学习不建议一开始就想着把所有代码都读完。我的建议是先把整个系统跑起来点几个页面让数据流动起来然后顺着一条业务链路去读代码。比如从首页点击商品进详情再点加购再到购物车提交订单这条链路会经过前端路由、接口函数、后端 Controller、Service、Mapper、SQL、数据库表的完整调用链。把这条链路读通前后端分离系统的核心运行机理就掌握得差不多了。读代码的过程中重点去理解前端请求如何到达后端、后端如何处理参数并返回数据、数据表之间的外键关系和事务边界在哪里这三个问题远比记住某个具体方法名更有价值。写在最后我个人在实际操作中的体会是这套前后端分离商城系统的难点其实不在某一个框架本身而在框架之间的衔接处前端构建产物怎么交给 Nginx、反向代理怎么配置才能把/api请求转给后端、Token 怎么在前端存储并带上、后端拦截器怎么排除公开接口、数据库连接和字符集怎么对齐。这些衔接点就像水管接头每一段管道本身都结实接头没拧紧就到处漏水。最后再分享一个小技巧以后不管是拿到什么 SpringBootVue 的源码第一件事永远是“让它先跑起来”不要边读代码边猜测问题。跑不起来的项目读再多源码也只是纸上谈兵跑起来之后再结合断点调试和接口测试工具去理解具体逻辑效率会翻好几倍。希望这篇内容能帮你在商城项目这条路上少浪费几个周末把精力花在真正有价值的业务实现和性能优化上。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑