
做手机销售网站这类管理系统在技术社区里一直是很经典的全栈练手项目。市面上的开源仓库很多但真正能“拿下来就能跑、跑起来没一堆坑”的其实不算多绝大多数不是依赖版本对不上就是前后端接口对不齐或者缺初始化脚本。最近我把一套基于 SpringBoot 后端 Vue 前端 MySQL 数据库的手机销售网站信息管理系统源码完整梳理了一遍从本地拉库到前后端联调、从商品上下架到订单发货全部跑通。这篇文章就把这套系统的技术拆解、核心表结构、接口设计思路和实操避坑记录一次性写清楚给正在做同类课程设计、毕业设计或者想拿全栈项目练手的朋友做一个可直接复现的参考。这套项目说白了就是一个标准的电商后台管理系统只是业务围绕“手机”这个品类展开。用户端有商品浏览、搜索、详情、购物车、下单支付模拟、订单跟踪管理员端有商品管理、分类管理、订单处理、用户管理、数据看板。前后端分离后端提供 RESTful API前端通过 Axios 调接口渲染页面数据库用 MySQL 存储业务数据。技术组合比较主流不至于老旧但也不会堆太多花活非常适合用来理解一个真实商城系统的完整闭环。1. 项目整体设计与技术选型逻辑1.1 为什么是 SpringBoot Vue MySQL 这套组合先聊选型。也许有人觉得手机销售网站用 PHP 或者 Python 也能做甚至直接用若依这类脚手架改改更快但 SpringBoot Vue MySQL 的组合在高校项目、个人简历项目里永远是性价比最高的选择。原因不难理解。SpringBoot 把 Spring 繁琐的 XML 配置全部干掉内嵌 Tomcat打一个 jar 包就能跑接口开发效率非常高。而且它遵循 MVC 分层Controller、Service、Mapper 的职责边界清晰答辩或者面试时能讲的东西很多。Vue 则在前端层面解决了 DOM 操作和状态同步的痛点组件化开发让商品列表、购物车、订单详情这类重复性页面可以大量复用。MySQL 更不用说关系型数据库里最普及的面试八股也绕不开索引、事务、隔离级别这些概念项目里有订单表、库存表自然能引出事务和锁的讨论。从部署角度看这套组合也足够轻。后端 java -jar 启动前端打包成静态文件后可以由 Nginx 托管也可以直接放进 SpringBoot 的 resources/static 目录一个进程搞定前后端。很多带“可直接运行”标签的项目都是这么处理的——后端提供一个接口服务前端打包后静态资源由后端统一托管访问 8080 端口直接进页面。这个方案最适合演示和交付不需要单独配 Nginx。1.2 项目模块划分与功能全景这套系统的功能并不复杂但胜在“麻雀虽小五脏俱全”。按角色划分可以分为两个端用户端商品门户首页轮播图、热销手机推荐、新品上架、分类浏览商品搜索按名称关键词搜索按品牌、价格区间筛选商品详情多规格颜色、存储版本、套餐类型、库存显示、销量显示购物车加入购物车、修改数量、删除、批量结算订单中心提交订单、模拟支付、取消订单、确认收货、查看物流状态个人中心注册、登录、个人信息修改、头像上传、我的订单列表管理端后台登录独立管理员账号体系支持记住登录状态数据看板统计用户数、商品数、订单数、销售额并用图表展示最近一周销售趋势商品管理新增商品、编辑、上下架、删除、库存调整、商品图片上传分类管理手机品牌的增删改查如苹果、华为、小米、OPPO、vivo订单管理订单列表筛选、发货、查看订单明细、收货地址管理用户管理用户列表、封禁/解封、重置密码这套功能对应着一个比较完整的电商闭环。很多项目只做“展示层”商品是写死在页面里的订单也没有真正的入库操作那样的项目说服力很弱。这套系统的核心价值在于从前端点击到最后订单表数据的落库整条链路都是真实跑通的。2. 数据库设计与核心表结构解析2.1 数据表总体规划与关系梳理数据库设计是这类项目最先动手的部分也是最能看出一个人基本功的地方。手机销售网站的表设计可以遵循电商系统的标准范式按业务模块拆分。我在导入这套源码时初始化 SQL 脚本里一共包含了 7 张核心业务表user用户表admin管理员表category手机分类表product商品表cart购物车表orders订单主表order_item订单明细表有些项目还会加 banner轮播图表、address收货地址表、comment商品评价表具体看功能边界。下面重点拆解几张关键表的字段设计。2.2 商品表、订单表与购物车表的字段设计要点商品表product是最核心的业务表字段设计上有一个容易踩坑的点价格字段类型。很多初学者习惯用 DECIMAL(10,2)这个是对的。但有些人图省事直接用 DOUBLE结果在做金额计算时遇到精度丢失比如 19.9 存进去变成 19.899999。电商项目金额一律用 DECIMAL禁止用 FLOAT/DOUBLE这是个硬性原则。商品表的关键字段大致如下字段名类型说明idBIGINT主键自增nameVARCHAR(100)商品名称category_idINT关联分类表priceDECIMAL(10,2)商品价格original_priceDECIMAL(10,2)原价用于促销展示stockINT库存数量salesINT销量用于排序展示imageVARCHAR(255)商品主图路径imagesTEXT商品轮播图JSON 数组格式存储detailTEXT商品详情描述statusTINYINT1上架 0下架create_timeDATETIME创建时间这里 images 字段用 TEXT 存 JSON 字符串是很多小型项目的常见做法好处是省一张子表坏处是查询和统计不方便。对于课程设计级别的项目完全可以接受但如果将来要做多规格库存就需要拆成 product_sku 表。订单表orders的设计要比商品表复杂一些涉及的事务点也更多字段名类型说明idBIGINT主键order_noVARCHAR(32)订单编号唯一user_idINT下单用户total_amountDECIMAL(10,2)订单总金额pay_amountDECIMAL(10,2)实付金额pay_typeTINYINT支付方式1余额 2模拟支付statusTINYINT订单状态0待付款 1待发货 2待收货 3已完成 4已取消receiver_nameVARCHAR(50)收货人receiver_phoneVARCHAR(20)收货电话receiver_addressVARCHAR(255)收货地址pay_timeDATETIME支付时间deliver_timeDATETIME发货时间create_timeDATETIME下单时间订单号和状态字段是重点。订单号需要保证唯一性通常用时间戳 随机数 用户id后几位拼接来生成比如20250402153022123456这种。订单状态字段虽然在 Java 代码里可以用枚举定义但落到数据库里就是用 TINYINT 存状态值配合一个状态转换的流程控制可以避免用户跳过支付直接发货这类越权操作。购物车表cart相对简单核心是唯一索引的设计CREATE TABLE cart ( id INT NOT NULL AUTO_INCREMENT, user_id INT DEFAULT NULL, product_id INT DEFAULT NULL, quantity INT DEFAULT 1, checked TINYINT DEFAULT 1, create_time DATETIME DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_user_product (user_id, product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;加唯一索引uk_user_product的目的是防止同一个用户把同一件商品重复加入购物车正常的产品逻辑应该是如果已经在购物车里再次加入时数量累加。这个细节很多源码是没做对的但设计良好的表结构天然解决了业务上的重复数据问题。2.3 设计中的取舍与扩展空间这套表结构放在实际电商系统里肯定不算完善比如没有完整独立的 SKU 表、没有秒杀库存表、没有支付流水表。但对于教学项目来说过度设计反而是负担——表一多联调成本翻倍演示场景反而容易被复杂的字段关系拖累。如果将来要扩展优先级应该是订单表拆出快照字段下单时的商品名称、图片直接冗余在 order_item避免商品改名后历史订单显示错乱、增加商品规格表SKU、增加优惠券表。这些扩展方向在项目文档或答辩中可以作为“后续展望”来谈属于加分项。3. 后端 SpringBoot 实现要点与核心接口剖析3.1 项目分层结构与启动类配置后端代码在结构上遵循经典 SpringBoot 分层架构我用 Maven 把项目拉起来后直接看包结构基本就是这个布局com.phone.shop ├── config // 跨域配置、WebMvc 配置 ├── controller // 接口层接收前端请求 ├── service // 业务逻辑层接口 实现类 ├── mapper // MyBatis Mapper 接口 ├── entity // 实体类对应数据库表 ├── dto // 数据传输对象如登录请求参数 ├── vo // 视图对象如统计数据返回体 ├── utils // 工具类JWT、MD5、订单号生成 ├── exception // 自定义异常与全局异常处理器 └── MobileShopApplication.java这种分层是 Java 后端项目的标配好处是职责边界清晰Controller 只负责参数接收和结果包装Service 做业务逻辑处理Mapper 只做持久层数据交互。每一层都可以独立测试和替换。启动类没有特殊配置标准的SpringBootApplication。配置文件 application.yml 里主要配置三项服务端口、数据源、MyBatis 映射。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/phone_shop?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.phone.shop.entity这里有个很容易踩的坑serverTimezone必须配否则 MySQL 8 连接时会报时区错误The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。另外 jackson 的日期格式化也要配全局的不然前端拿到的时间会是时间戳显示非常不友好。密码这种敏感信息放明文配置在教学项目里可以接受生产环境里应该用环境变量或者配置中心遮蔽。3.2 接口风格与核心方法清单接口设计遵循 RESTful 风格路径按资源命名。后端一共约 30 个接口按模块划分核心接口如下用户与认证模块POST /api/user/register 用户注册POST /api/user/login 用户登录返回 token 和用户信息GET /api/user/profile 获取当前登录用户信息PUT /api/user/profile 修改个人信息POST /api/user/avatar 上传头像POST /api/admin/login 管理员登录商品模块GET /api/product/list 分页查询商品支持关键词、分类id、价格区间GET /api/product/detail/{id} 商品详情POST /api/admin/product/save 新增或更新商品POST /api/admin/product/delete/{id} 删除商品PUT /api/admin/product/status/{id}/{status} 上下架购物车模块GET /api/cart/list 查询购物车列表POST /api/cart/add 加入购物车PUT /api/cart/update 修改数量DELETE /api/cart/delete/{id} 删除购物车记录订单模块POST /api/order/submit 提交订单PUT /api/order/pay/{orderNo} 模拟支付GET /api/order/list 我的订单列表PUT /api/order/cancel/{orderNo} 取消订单PUT /api/order/confirm/{orderNo} 确认收货GET /api/admin/order/list 管理员订单列表PUT /api/admin/order/deliver/{orderNo} 发货统计模块GET /api/admin/dashboard 获取首页统计数据3.3 登录鉴权JWT 拦截器的实现思路登录鉴权是这套后端的一个亮点也是很多初学源码的人第一眼看不懂的地方。实现方案是 JWT 拦截器不依赖 Session。用户登录成功后后端生成一个 JWT tokenpublic String generateToken(User user) { long expire System.currentTimeMillis() 7 * 24 * 60 * 60 * 1000L; return Jwts.builder() .setSubject(String.valueOf(user.getId())) .claim(username, user.getUsername()) .claim(role, user) .setExpiration(new Date(expire)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); }前端拿到 token 后存在 localStorage 里每次请求通过拦截器放到请求头request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[token] token } return config })后端在拦截器中解析 token取出登录用户 id放到 ThreadLocal 或直接放入 request attribute。这套机制好在无状态、跨域友好、前后端分离天然适配。但一定要注意密钥的管理和 token 过期时间。我看到有些项目把 JWT 密钥硬编码在代码里这属于教学演示可以理解但实际开发要避免的写法。3.4 统一返回体与全局异常处理一个让前端开发最省心的后端设计就是统一接口返回格式。这套系统的所有接口都遵循同一个结构{ code: 200, message: 操作成功, data: {} }code 为 200 表示成功400 表示参数错误401 表示未登录或 token 失效500 表示服务器异常。前端在 Axios 响应拦截里只看 code统一处理错误提示业务代码里就不用每个接口都做错误分支。统一返回体对应的 Java 类通常长这样public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }全局异常处理用RestControllerAdvice实现把业务异常、参数校验异常、兜底异常统一拦截转成 Result 格式返回。这样既保证客户端视角的接口一致性又不会把 Java 的堆栈信息直接暴露给前端安全性和体验都能照顾到。这套源码里还有一个细节我认为做得不错业务异常用了自定义异常类比如库存不足、余额不足、商品已下架这些情况在 Service 层直接throw new BizException(库存不足)全局异常处理器捕获后返回 code400。比起到处写 if-else 返回 Result这种写法让核心业务流程清爽很多。4. 前端 Vue 实现要点与页面交互拆解4.1 项目初始化与目录结构规划前端部分在这套源码里用的是 Vue 2 Element UI Vue Router Axios 的组合这也是国内市场占有率最高、学习资料最丰富的一套搭配。虽然 Vue 3 Element Plus 已经是新趋势但很多学校的课程设计、毕业设计还在用 Vue 2兼容性和参考资料都更成熟。前端目录结构phone-shop-web ├── public │ ├── index.html │ └── favicon.ico ├── src │ ├── api // 接口请求模块按业务拆分 │ │ ├── product.js │ │ ├── cart.js │ │ ├── order.js │ │ └── user.js │ ├── assets // 静态资源 │ ├── components // 公共组件 │ │ ├── Header.vue // 顶部导航 │ │ ├── Footer.vue // 底部 │ │ └── ProductCard.vue // 商品卡片 │ ├── router // 路由配置 │ │ └── index.js │ ├── store // Vuex 状态管理 │ │ └── index.js │ ├── views // 页面组件 │ │ ├── home │ │ ├── product │ │ ├── cart │ │ ├── order │ │ ├── user │ │ └── admin │ ├── utils │ │ └── request.js // Axios 封装 │ ├── App.vue │ └── main.js └── package.json这个目录结构是 Vite/Yarn 初始化后二次整理的和 Vue CLI 创建的默认结构一致。模块划分清楚api 目录集中管理接口页面组件按业务域拆分后期维护不用满项目找接口定义。4.2 路由设计用户端与管理端权限隔离路由是前端的一个关键点。这套系统的路由设计采用分模块注册的方式用户端和管理端分开const routes [ { path: /, component: Home, meta: { title: 首页 } }, { path: /product/:id, component: ProductDetail, meta: { title: 商品详情 } }, { path: /login, component: Login, meta: { title: 登录 } }, // 用户中心相关路由 { path: /user, component: UserLayout, meta: { requiresAuth: true }, children: [ { path: cart, component: Cart, meta: { title: 购物车 } }, { path: orders, component: OrderList, meta: { title: 我的订单 } } ] }, // 管理端路由 { path: /admin, component: AdminLayout, meta: { requiresAuth: true, role: admin }, children: [ { path: dashboard, component: Dashboard }, { path: product, component: AdminProduct }, { path: order, component: AdminOrder }, { path: user, component: AdminUser } ] } ]路由守卫负责登录态检测和角色权限校验router.beforeEach((to, from, next) { const token localStorage.getItem(token) const role localStorage.getItem(role) if (to.meta.requiresAuth !token) { next(/login) } else if (to.meta.role to.meta.role ! role) { next(/) } else { next() } })这个权限模型比较直白适合教学。管理员的页面路径和用户端完全分离入口在首页导航栏的“后台管理”链接。真正生产环境里路由权限粒度会更细比如按钮级别的权限用自定义指令 v-permission 控制但项目层面做到这种程度已经合格了。4.3 Axios 二次封装与状态管理实践前端最不能省的一步就是封装 request 工具。直接在每个组件里用 axios.get 写死 baseURL后续维护会非常痛苦。这套源码把 Axios 封装在 utils/request.js 里做了三件事第一设置统一的 baseURL。开发环境用代理转发到 8080生产环境直接使用相对路径。const service axios.create({ baseURL: process.env.VUE_APP_BASE_API || /api, timeout: 10000 })第二请求拦截器自动携带 token。第三响应拦截器统一处理业务状态码和 HTTP 状态码遇到 401 自动跳转登录页。状态管理用的是 Vuex核心管理的状态是购物车数量。因为购物车数量在首页导航栏、用户中心、下单页面都会被读取如果用组件 props 传递会很麻烦放 Vuex 里一个全局 state 加上 getters 计算总数量就完成了。// store/index.js state: { cartCount: 0 }, mutations: { setCartCount(state, count) { state.cartCount count } }, actions: { async fetchCartCount({ commit }) { const res await getCartList() let total 0 res.data.forEach(item total item.quantity) commit(setCartCount, total) } }登录后和每次购物车操作后都触发一次fetchCartCount导航栏的购物车徽标数字就能实时更新体验非常顺滑。4.4 核心页面流程从商品列表到订单支付如果用一句话概括用户端核心链路那就是“浏览商品 → 加入购物车 → 结算下单 → 模拟支付 → 查看订单状态”。这个链条涉及五个页面每个页面的关键交互我都梳理一遍。商品列表页是用户进入系统的第一站也是最容易出体验问题的地方。列表页调用的接口是分页查询参数包含 keyword、categoryId、pageNum、pageSize。前端用 Element UI 的 el-pagination 组件每次切换页码重新请求数据。商品卡片组件 ProductCard 接收一个 product 对象展示图片、名称、价格和销量点击后跳转详情页。这里有个小细节图片路径后端返回的是相对路径如/images/product/1.jpg前端通过 Vite 代理访问到后端静态资源目录才能正常显示。如果图片加载不出来十有八九是代理路径或静态资源映射没配好。商品详情页除了展示基本信息重点在“规格选择和库存校验”。用户选择颜色和版本后前端维护一个 selectedSpec 对象价格和库存从后端返回的 sku 数据里读取。加入购物车时需要同时传递 productId、specId、quantity后端校验库存足够后才会写入购物车表。购物车页面有几个逻辑细节值得注意。修改数量时前端做了节流处理避免用户快速点击加减时连续发送请求把接口打崩。选中状态用 checkBox 表示结算时只选中那些 checked 为 1 的记录。全选的计算通过 Vue 的 computed 属性实现没有一个多余的 watch。订单确认页从购物车读取选中的商品和数量展示合计金额同时需要填写收货人、电话和地址。地址信息如果做过扩展会从 address 表读取默认地址但基础版通常就是手动填写。提交订单调用/api/order/submit后端会做两件事一是根据购物车选中的记录生成订单主表和明细表二是清空对应购物车记录。模拟支付页就简单了页面上展示待支付金额和“立即支付”按钮点击后后端把订单状态从 0待付款改成 1待发货模拟支付成功。整个链路走完后用户能在“我的订单”里看到处于不同状态的订单管理员端则能看到订单进入待发货列表点击发货后状态变 2。这个前后端状态流转的闭环就是整个系统最值钱的部分。5. 项目运行环境准备与完整部署实录5.1 运行环境清单与版本兼容建议拿到源码后第一步不是改代码而是把环境先备齐。这套项目在标准环境下可以跑通我用的是以下配置组件版本说明JDK1.8SpringBoot 2.x 兼容性最好Maven3.6构建工具MySQL5.7 或 8.0注意驱动和时区配置Node.js14.x 或 16.xVue CLI 环境要求npm/yarn任意较新版本包管理器需要特别提醒的是 JDK 和 SpringBoot 版本的搭配。这套源码是基于 SpringBoot 2.x 写的如果用 JDK 17 或更高版本启动大概率会遇到Cannot determine embedded database driver class for database type NONE或反射相关的报错因为 Spring Framework 老版本和 JDK 高版本的模块化权限不兼容。最稳妥的方案是 JDK 8其次 JDK 11 配合 SpringBoot 2.5。MySQL 8 的话注意 urls 里加serverTimezoneAsia/Shanghai同时驱动类用com.mysql.cj.jdbc.Driver。MySQL 5.7 可以用旧驱动com.mysql.jdbc.Driver但统一用新驱动在两种版本下都能跑少踩坑。5.2 数据库初始化与工程导入步骤数据库初始化是整套运行流程的第一步也是最容易出问题的一步。源码目录下通常会有一个sql文件夹里面放着phone_shop.sql初始化脚本。执行过程本机安装并启动 MySQL 服务确保端口 3306 可以连接。用命令行或 Navicat 新建数据库字符集选 utf8mb4。导入 SQL 脚本。命令行方式是mysql -uroot -p phone_shop phone_shop.sql图形界面直接选中数据库后运行 SQL 文件。验证表结构是否完整导入。重点检查 user 表里是否有默认管理员账号通常是 admin/admin123和测试用户账号product 表是否有可展示的商品数据。导入脚本后打开 IDEA 或 Eclipse用 Maven 方式导入后端工程。等待依赖下载完毕后检查 application.yml 中的数据源配置把用户名密码改成自己本机的。这里有个细节MySQL 8 的默认密码加密方式为 caching_sha2_password老版本的 JDBC 驱动可能不支持所以 Maven 依赖里的 mysql-connector-java 版本建议用 8.0.28 以上。后端启动前还可以顺手配置一个编码问题。Windows 环境下如果控制台乱码在 VM options 里加-Dfile.encodingUTF-8。启动成功后控制台输出Tomcat started on port(s): 8080表示后端已经就绪。5.3 前端依赖安装与启动联调前端工程导入到 VS Code 后先执行依赖安装npm install如果 npm install 速度慢或者卡住可以换成淘宝镜像源npm install --registryhttps://registry.npmmirror.com依赖装完后配置开发环境代理在vue.config.js里配module.exports { devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }代理配置的目的是把前端发出的/api请求转发到后端 8080 端口解决跨域问题。理论上后端配置了 CORS 跨域也能直接调但用代理是前后端分离开发中最规范的做法。前端启动命令npm run serve浏览器访问http://localhost:3000。如果这个地址打不开看看是否端口被占用默认端口可能在 8080 和 3000 间自动切换。联调时最直观的检验方法是打开浏览器 F12 开发者工具切到 Network 面板刷新首页看看商品列表接口/api/product/list是否返回 200响应体里有没有商品数据。如果看到 404检查后端是否启动成功如果看到 500优先检查数据库连接配置和 SQL 脚本是否正确导入如果是跨域报错检查代理配置是否生效。5.4 生产环境打包与交付部署开发环境跑通后如果需要把项目部署到服务器上演示用打包方式更轻便。前端执行npm run build生成 dist 目录里面是编译后的静态文件。后端的处理有两个方案。方案一前后端独立部署。把 dist 目录托管到 Nginx配置 location /api 反向代理到后端 8080 服务。方案二直接合成单进程。把 dist 目录里的文件复制到后端工程的src/main/resources/static目录重新打包。因为后端 Controller 的接口路径都带/api前缀静态资源路径不会冲突启动一个 jar 包浏览器访问http://localhost:8080就能看到前端页面。mvn clean package -DskipTests java -jar target/phone-shop-0.0.1-SNAPSHOT.jar方案二的好处就是标题里写的“可直接运行”一个 jar 包包含前端页面和后端接口非常适合课程设计演示。但需注意静态资源一旦打进 jar 内部后续更新前端代码就得重新打包迭代效率不如前后端分离部署。6. 常见问题排查与避坑经验速查6.1 启动阶段经典报错与处理这类 SpringBoot Vue 项目的问题相当集中绝大多数在启动和联调阶段就能遇到。我把高频问题整理成一张速查表按解决方案给你参考。问题现象根本原因解决方案后端启动报时区错误MySQL 连接未指定 serverTimezoneapplication.yml 的 url 加上 serverTimezoneAsia/Shanghai启动报 Access denied for user数据库密码错误或用户权限不足检查 application.yml 密码用 Navicat 实测连接前端 npm install 卡住网络中访问默认源不稳定使用淘宝镜像源或设置 proxy前端页面空白无内容路由模式问题或 JS 报错控制台看报错检查代理配置图片加载不出来静态资源映射路径不对后端加资源映射器或调整图片相对路径请求跨域报 403/405后端未配置 CORS 或代理未生效后端加跨域过滤器前端检查 proxy 配置接口返回 500 和 MyBatis 映射报错Mapper 扫描路径不对检查启动类 MapperScan 包路径中文乱码编码不一致数据库连接加 characterEncodingutf8IDEA 设置 UTF-8登录后跳转回登录页token 存储 key 不一致检查前端登录后 localStorage 存储的 key 与请求头读取的 key部署 jar 包后页面 404前端 dist 未复制到 static重新打包前确认 dist 文件已复制完整6.2 数据库相关的坑字符集、排序与事务数据库层面有两个容易被忽视的坑。第一是字符集。建库的时候如果不指定 utf8mb4默认可能是 latin1存中文直接变成乱码。如果是这样除了重建库还可以用命令修改已有表的字符集ALTER DATABASE phone_shop CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; ALTER TABLE product CONVERT TO CHARACTER SET utf8mb4;第二是事务问题。订单提交接口涉及多个写操作创建订单主表、插入订单明细、扣减库存、清空购物车。如果任何一个步骤失败前面已经写入的数据不能残留否则会出现“订单有了但库存没扣”的数据不一致。后端代码中在 Service 方法上标注Transactional解决Transactional public OrderVO submitOrder(OrderRequest request) { // 1. 校验库存 // 2. 生成订单主表 // 3. 插入订单明细 // 4. 扣减库存 // 5. 清空购物车 }扣减库存时还要注意 SQL 条件防止超卖UPDATE product SET stock stock - #{count} WHERE id #{productId} AND stock #{count}stock #{count}是条件更新MySQL 的行锁会在这一条语句中发挥作用。如果 UPDATE 影响行数为 0说明库存不足业务层抛出异常回滚整个事务。这个写法是电商并发场景下的经典乐观锁思路在面试中可以重点展开。6.3 前后端联调时的定位方法论联调出问题最忌讳一上来就改代码。我的建议是养成“分层定位”的习惯先把问题锁定在哪一层再动手。前端 F12 Network 面板是关键出发点。看一个请求的完整链路请求是否发出、URL 是否正确、请求头有没有带 token、响应状态码是多少、响应体的 code 是多少、message 是什么。如果请求根本没发出问题在前端代码逻辑如果请求发出但 404问题在路由或后端路径不匹配如果 404 之外还有 500问题在后端或数据库。后端查看日志是第二步。SpringBoot 控制台默认会输出 SQL 执行日志如果配置了里面能看到实际执行的 SQL 语句和报错堆栈。比如“字段名不存在”的报错通常是实体类字段和数据库字段映射不一致或者在 XML 里写了错误的列名。这些日志信息比前端看到的“请求失败”四个字详细得多。最后再看数据。数据库里真实的数据状态是最直接的验证。比如用户下单后去查 orders 表有没有新记录stock 有没有被扣减cart 对应商品是否被清除。数据层面验证通过整个链路就是通的剩下只是展示层的问题。结尾最后再分享一个我个人的小经验。拿到任何一套“可直接运行”的全栈项目源码不要急着改功能先把完整的“注册 → 登录 → 浏览 → 加购 → 下单 → 支付 → 后台发货 → 确认收货”这条链路从头到尾走一遍。这个流程走通你才真正理解这套系统的数据结构、接口职责和业务状态流转。之后再根据自己的想法加需求比如加一个商品搜索的热门关键词排行或者把订单状态里加一个“退款/售后”分支改动过程中被迫去读源码、改表结构、调接口那才是真正把这套源码的价值榨干。SpringBoot Vue MySQL 的组合不会过时核心是借助项目建立起全栈思维这也是这类管理系统源码最大的意义。