
1. 项目整体设计与技术选型思路1.1 为什么我选择前后端分离架构来做图书管理系统先说说这个项目的起因。图书管理系统算是后端开发里最经典的练手项目了但很多人做出来的版本都停留在 JSP Servlet 或者 Thymeleaf 模板渲染的老套路上。我这次特地用 SpringBoot Vue MyBatis MySQL 重新做了一套核心目的就是想给自己留一个“前后端完全分离、接口可复用、部署不折腾”的完整模板。前后端分离意味着前端只负责页面渲染和用户交互后端只负责提供 JSON 接口两者通过 HTTP 协议通信开发时各跑各的端口部署时前端打包成静态文件交给 Nginx后端打成 jar 包单独跑互不干扰。这套结构解决的最大痛点就是“解耦”。以前用模板引擎前端改个按钮样式都得动整个 Java 类后端加个字段还得小心翼翼别把页面弄崩。分离之后前端团队和后端团队可以并行开发前端用 Mock 数据调接口后端用 Postman 测试接口只要约定好接口文档两边几乎不用互相等。对于个人开发者来说虽然不存在团队协作问题但分离结构让代码的可维护性和可扩展性肉眼可见地提升以后想加个移动端小程序直接复用后端接口就行前端重写一套即可。这个项目适合谁呢我的判断是有一定 Java 基础、想搞懂前后端如何联动的同学或者准备做毕业设计、需要一套能跑通且答辩能讲清楚的项目的朋友。当然就算你只是想把 SpringBoot 和 Vue 的集成流程跑一遍这套项目也能给你完整的参考。我不打算只丢源码我会把每个关键步骤为什么这么做、踩了什么坑都写出来你照着做就能复现。1.2 技术栈选型背后的“为什么”SpringBoot、Vue、MyBatis、MySQL技术栈的选择不是越新越好而是越顺手越好。SpringBoot 之所以被捧为 Java 后端开发的事实标准是因为它把 Spring 那套繁琐的 XML 配置彻底简化成了“自动配置 starter 依赖”。以前写一个 SpringMVC 项目要配一堆 xml现在加一个 spring-boot-starter-web 依赖写个 main 方法就能跑起来。SpringBoot 内置 Tomcat打包成 jar 直接 java -jar 执行部署成本极低对于这种中小型管理系统来说再合适不过。前端选 Vue 而不是 React 或者 Angular主要原因在于 Vue 的上手曲线平缓模板语法直观特别适合快速开发后台管理这类以表单和表格为主的应用。Vue 2 目前依然有大量存量项目但新项目建议直接上 Vue 3 Vite组合式 API 写起来更清爽。我这次用的就是 Vue 3 组合式 API 风格配合 Element Plus 组件库图书新增、编辑、列表展示这些界面基本就是拼积木能把精力集中在业务逻辑上。MyBatis 的作用是半自动化的 SQL 映射框架。相比 JPA/Hibernate 那种“自动生成 SQL”的魔法MyBatis 让 SQL 掌握在程序员自己手里复杂查询、多表关联、动态条件都能精准控制。很多老工程师偏爱 MyBatis 的原因就在于“SQL 透明”出了问题可以直接看出是哪条语句写错了。配合 MyBatis-Plus 还能少写大量单表 CRUD 的样板代码。MySQL 作为关系型数据库稳定且开源免费图书管理这种数据量级完全能够胜任而且社区资料丰富任何报错基本都能搜到解决办法。这套组合用一句大白话概括就是SpringBoot 管后端启动和依赖MyBatis 管数据库操作Vue 管页面展示MySQL 管数据存储。它们各司其职组合起来刚好覆盖了一个业务系统的所有环节。选型的最终标准是“能快速开发、能稳定运行、能让人看懂”。2. 系统核心功能与数据库设计2.1 图书管理系统的核心功能模块拆解一个合格的图书管理系统最少得跑通“图书信息维护”和“借阅归还”这两个主流程。我这次做的版本把功能拆成了五个核心模块这也是后端接口设计的基础。第一个是图书管理。包含图书的新增、删除、修改、查询查询支持按书名模糊搜索、按分类过滤、按 ISBN 精确匹配。图书字段我设计了书名、作者、出版社、ISBN、分类、库存数量、馆藏位置、封面图片、简介等。删书的时候要注意如果该书还有未归还的借阅记录应该先禁止删除或者做逻辑删除否则会导致借阅记录关联不到书。第二个是分类管理。图书分类用独立的表维护树形结构其实更好扩展但为了简化我用了单层分类每个分类有名称和描述。前端下拉框加载分类列表新增图书时选择所属分类。分类被图书引用时不能直接删需要提示“该分类下存在图书”。第三个是借阅管理。这里的核心是借阅记录表包含图书ID、读者ID、借出时间、应还时间、实际归还时间、状态。状态可以设计为“待借出、已借出、已归还、已逾期”。借书操作要扣减图书库存还书操作要增加库存两个操作必须放在一个事务里避免出现库存对不上的问题。逾期判断可以在查询列表时通过比较当前时间和应还时间实时算出来也可以每天跑个定时任务去更新状态。我这个项目里选了查询时实时计算省去定时任务配置数据更动态。第四个是读者管理。读者信息包括姓名、手机号、借书证号、注册时间等。读者表支撑借阅模块因为借阅记录必须关联到具体读者。为了让演示更方便我内置了一个 root 用户作为管理员同时支持通过后台手动维护读者数据。实际生产系统一般会和用户认证体系打通但作为毕设或练习项目简单的 CRUD 就够用了。第五个是统计面板。首页展示图书总量、读者总量、当前借出数量、逾期未还数量再用 ECharts 画个简单的分类占比饼图和借阅趋势折线图。统计功能不需要太复杂几个 COUNT 查询加上一个简单的图表组件就能实现但这个模块特别提气能让整个系统看起来完整度高很多。2.2 数据库表设计从表结构看业务关系数据库设计是后端开发的根基表结构没设计好后面写代码全是坑。我这次设计了五张表book图书表、category分类表、reader读者表、borrow_record借阅记录表、admin管理员表。为了演示方便我用了逻辑外键而不是数据库物理外键也就是说表之间没有真正建立 FOREIGN KEY 约束而是通过业务代码维护关联。这么做的原因很简单物理外键在高并发插入和删除时性能差而且会给后续数据迁移带来麻烦实际企业开发中大部分项目也会刻意避免物理外键靠应用层保证数据完整性。book 表的建表语句关键点在于 ISBN 建议加唯一索引分类ID加普通索引。borrow_record 表的 book_id、reader_id 必须加索引因为借阅记录会随着时间膨胀查询“某个读者当前借了哪些书”或者“某本书是否被借走”都会走这两个字段。status 字段也建议加索引因为统计逾期和借出数量时需要按状态过滤。下面是简化后的表结构对照你可以直接参考表名核心字段用途bookid, category_id, name, isbn, publisher, stock图书信息categoryid, name, description图书分类readerid, name, phone, card_no读者信息borrow_recordid, book_id, reader_id, borrow_time, due_time, return_time, status借阅流水adminid, username, password_hash管理员账号为了演示方便我专门写了一个 schema.sql 脚本里面包含了建库、建表、初始化几条测试数据。你导入 MySQL 后系统就能直接跑起来不需要手动在命令行敲 DDL。这个脚本放在项目根目录的 db 文件夹下部署的时候直接 source 执行即可。还有一点MySQL 的字符集一定要设成 utf8mb4否则插入中文书名或者“作者”字段里的特殊字符比如 emoji会报乱码。2.3 接口设计规范前后端约定的关键前后端分离的项目接口设计决定了前后端联调的效率。我这次遵循了 RESTful 风格同时做了一些统一约定。统一接口返回格式是关键中的关键。前端的 axios 拦截器需要统一处理成功和失败的情况如果你接口里一半返回 { code: 0, data: xxx }另一半直接返回裸数据前端永远在纠结 data 到底在哪。所以我的后端做了一个 BaseResult 类里面固定包含 code、message、data 三个字段。code 为 0 表示成功非 0 表示业务失败message 是给前端提示的错误信息data 是业务数据。这个设计虽然看起来多包了一层但在实际开发中省心太多了。你还可以在里面加一个 timestamp 字段用于排查接口耗时。接口路径上我做了这么一组约定GET /api/book/list 分页查询图书POST /api/book/add 新增图书PUT /api/book/update 修改图书DELETE /api/book/delete/{id} 删除图书GET /api/borrow/records 查询借阅记录POST /api/borrow/borrow 借书操作POST /api/borrow/returnBook 还书操作GET /api/statistics/overview 首页统计所有业务接口统一挂在 /api 前缀下。这么做一是方便在 Nginx 里配置反向代理把所有 /api 请求转发到后端 SpringBoot 服务二是前端开发时可以通过 Vite 代理把 /api 代理到 localhost:8080避免跨域问题。另外对于查询类的 GET 接口我统一用参数对象接收分页参数固定为 pageNum 和 pageSize排序参数固定为 sortField 和 sortOrder这样前端写起来不会乱。为了方便演示登录功能我加了一个简单的 JWT 鉴权。登录成功后后端返回一个 token前端存进 localStorage每次请求在 axios 拦截器里把 token 放到 Authorization 头里。后端通过拦截器校验 token遇到没有 token 的请求直接返回 401。这个逻辑虽然不复杂但能让整个项目看起来更接近真实生产环境。3. 从零搭建与核心代码实现3.1 环境准备与项目初始化十分钟搞定骨架先说环境要求。JDK 建议用 1.8 或者 11SpringBoot 版本我用的是 2.7.x这个版本和 MyBatis、MySQL 驱动兼容性最稳。Maven 用 3.6Node 用 16。如果你本机没有这些环境可以去各自官网下载安装基本都是解压或者点下一步的事。安装完之后在命令行执行 java -version、mvn -v、node -v都能输出版本号就说明环境没问题。后端项目我是直接用 Spring Initializr 生成的。你可以在 IDEA 里新建 Spring Initializr 项目也可以去 start.spring.io 网站生成后解压导入。关键依赖要选上 Spring Web、MyBatis Framework、MySQL Driver。如果你打算用 MyBatis-Plus 少写点 CRUD额外加一个 com.baomidou:mybatis-plus-boot-starter 的依赖版本要注意和 SpringBoot 2.7 匹配我用的是 3.5.3.1实测没问题。前端我用 Vite 从零创建的 Vue 3 项目。在命令行执行 npm create vitelatest book-web -- --template vue然后 cd book-web npm install。之后安装 Vue Router 和 axiosElement Plus 也一并装上。有了 Element Plus表格、表单、弹窗这些组件直接引入能省掉大量自己写 CSS 的时间。Vite 的开发服务器默认跑在 5173 端口需要配代理访问后端的 8080。在 vite.config.js 里加一段 server.proxy把所有 /api 的请求转发到 http://localhost:8080。这里不配代理的话浏览器直接请求后端会报跨域错误。3.2 后端核心代码SpringBoot MyBatis 如何写才能不拖泥带水后端的包结构我建议按功能拆分而不是按层拆分。也就是说把实体类、Mapper、Service、Controller 都放到同一个业务包下比如 com.example.book.controller、com.example.book.service、com.example.book.mapper这样文件归档更自然。真正写代码的时候我比较依赖 MyBatis-Plus 的标准 CRUD因为它自带 selectById、insert、updateById、deleteById 这些方法不用自己写 XML。复杂查询再在 Mapper 接口里定义方法配上 XML 文件。举一个图书分页查询的例子。MyBatis-Plus 提供了一个 Page 对象配合分页插件可以很方便地做分页。你需要先配置一个 PaginationInnerInterceptor然后在 Service 里调用 page 方法。代码大致是这样RestController RequestMapping(/api/book) public class BookController { Autowired private BookService bookService; GetMapping(/list) public BaseResult list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, String keyword, Integer categoryId) { PageBook page bookService.queryBookPage(pageNum, pageSize, keyword, categoryId); return BaseResult.success(page); } }这里的关键是 Service 层怎么把条件查询拼出来。我用 MyBatis-Plus 的 LambdaQueryWrapper 来构造查询条件比手写 XML 更简洁public PageBook queryBookPage(Integer pageNum, Integer pageSize, String keyword, Integer categoryId) { PageBook page new Page(pageNum, pageSize); LambdaQueryWrapperBook wrapper new LambdaQueryWrapper(); if (StringUtils.hasText(keyword)) { wrapper.like(Book::getName, keyword) .or().like(Book::getAuthor, keyword) .or().like(Book::getIsbn, keyword); } if (categoryId ! null) { wrapper.eq(Book::getCategoryId, categoryId); } wrapper.orderByDesc(Book::getId); return bookMapper.selectPage(page, wrapper); }这种写法不需要写 XML也不需要拼接 SQL 字符串安全性也有保障因为用的是预编译的占位符。对于借阅记录的关联查询我额外写了一条 XML SQL用 left join 把 book 名称和 reader 名称带出来。MyBatis 的关联查询映射是 resultMap把数据库列和 Java 属性对应好。为了少写一些 getter/setter实体类我用了 Lombok 的 Data 注解一行代码就能生成所有访问器清爽不少。事务处理是借书还书功能的重中之重。我在 Service 方法上加 Transactional 注解SpringBoot 会自动管理事务。借书的流程是先查图书库存库存大于 0 就执行 update 语句把库存减 1再 insert 一条借阅记录状态为“已借出”。还书流程是更新借阅记录状态为“已归还”更新实际归还时间再把对应图书的库存加 1。注意这两个操作顺序一定不能乱更新库存和插入记录必须在一个事务里否则出现异常时数据就回不去了。3.3 前端核心代码Vue 3 axiox 怎么调用后端接口前端最重要的就是页面与接口的绑定。我会在 src/api 目录下建一个 request.js 和若干模块文件。request.js 里封装一个 axios 实例设置 baseURL 为 /api然后统一添加请求拦截器和响应拦截器。请求拦截器负责从 localStorage 里拿 token 加到请求头响应拦截器负责判断 code 是否为 0如果不是则弹出错误提示同时处理 401 跳转登录页。响应拦截器还会把 res.data.data 直接返回这样页面里不用每次写两层 res.data。接口模块书写的典型方式是这样的import request from /utils/request export function getBookList(params) { return request({ url: /book/list, method: get, params }) } export function addBook(data) { return request({ url: /book/add, method: post, data }) }页面组件的逻辑就变得非常干净。以图书列表页为例在 setup 里定义一个分页对象和表格数据数组调用 getBookList 方法把返回的数据赋值给表格的 dataSource。Element Plus 的 el-table 只需要传 columns 配置和 data 属性就能渲染出表格。编辑操作就是弹出一个对话框表单绑定数据提交时调用 addBook 或 updateBook。路由方面我用 Vue Router 的懒加载配置按需加载页面组件。由于系统有登录页和主布局页两种结构我在路由里配置了嵌套路由主布局组件包含侧边菜单和顶栏子路由分别指向图书管理、分类管理、读者管理、借阅管理、统计面板等页面。菜单的选中状态根据当前路由的 path 自动高亮用 router-link 跳转。还有一点Vue 3 的组合式 API 和 Element Plus 配合时表单校验别忘记。Element Plus 的 form 组件提供 rules 参数可以在提交时自动校验“必填项”和格式比如 ISBN 长度、手机号格式。我在新增图书表单里就加了 isbn 的正则校验避免脏数据进数据库。3.4 前后端联调与跨域处理常见的三个坑位前后端分离最让人头疼的就是跨域。开发环境下前端跑在 5173后端跑在 8080协议和端口不同浏览器会拦截前后端之间的请求。我在前端 Vite 配置了代理把所有 /api 开头的请求转发到 8080这样浏览器看到的是同源的永不出现跨域报错。这个方法最推荐因为它不需要后端配置任何跨域许可生产环境也一样把 /api 交给 Nginx 转发即可。如果后端也配置了 CrossOrigin 或者全局 CORS 过滤器其实也没问题但要注意别把 allowedOrigin 设置成 *否则携带 token 的请求会被浏览器判定为不安全。我这里的做法是开发环境只靠 Vite 代理生产环境只靠 Nginx 反向代理后端完全不做 CORS 放开。这样逻辑最干净也不会出现重复开放权限的安全隐患。联调时遇到最多的报错是“404 Not Found”。这通常是前端请求的路径和后端 Controller 的 RequestMapping 路径不一致。我通常会在后端的 Controller 层打印日志打开 SpringBoot 的日志输出或者用 Postman 直接测接口。只要后端接口用 Postman 能通前端却访问不到那就是代理或路径问题。另一个常见报错是前端的 params 传了 undefinedaxios 会把 undefined 参数忽略掉后端如果要求必填就会报参数缺失。处理办法是在调用接口前做默认值设置如 pageNum: page.value.pageNum || 1。4. 部署上线完整流程4.1 本地一键启动脚本化部署的骚操作这个项目我在本地跑的流程是先把 MySQL 启动导入 db/schema.sql然后启动后端 SpringBoot 应用默认端口 8080再进入前端目录执行 npm run build生成 dist 目录这个 dist 目录里就是打包好的静态文件。为了模拟生产环境我直接用 Nginx 把 dist 目录作为网站根目录同时把 /api 反向代理到 8080。这样就能通过在浏览器输入 localhost 访问系统而不是通过 5173 端口体验和部署在服务器上一模一样。如果你只开发调试不模拟生产其实可以同时开两个终端一个跑 mvn spring-boot:run一个跑 npm run dev前端 Vite 代理就能正常工作。但这样只能自己在本地玩别人没法通过同一个地址访问。我给项目根目录写了一个 deploy.sh 脚本可以一键完成“打包前端、拷贝 dist、重启后端”整个流程。脚本的核心逻辑很简单#!/bin/bash cd frontend npm run build cd .. rm -rf /opt/book-api/static cp -r frontend/dist /opt/book-api/static cd backend mvn clean package -DskipTests java -jar target/book-api.jar这里要注意Nginx 里的 root 指向的是 /opt/book-api/staticlocation /api/ 再代理到 http://127.0.0.1:8080/api/保证前后端在同一个域下没有跨域问题。我把这个流程跑通之后后面的重复部署就只用一条命令解决省了不少事。4.2 服务器部署从 jar 包到 Nginx 全流程如果要把系统部署到一台云服务器步骤也不复杂。前提是你得有一台能联网的 Linux 服务器并且已经装好了 JDK 和 MySQL。首先用 Maven 把后端项目打成 jar 包执行 mvn clean package -DskipTests然后在服务器上使用 nohup java -jar book-api.jar log.out 21 启动。这里的关键是后端配置文件里的数据库地址不能写 localhost 了要改成云服务器的内网地址或实际地址用户名和密码也要用服务器的。前端部署我用的典型 Nginx 配置你可以在 /etc/nginx/conf.d/ 下面新建一个 book.confserver { listen 80; server_name your_domain_or_ip; root /opt/book-api/static; index 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; } location / { try_files $uri $uri/ /index.html; } }try_files 那一行很关键因为 Vue Router 如果是 history 模式刷新页面时 Nginx 会去找真实的文件路径找不到就返回 index.html这样可以避免部署后一刷新就 404。如果你用 hash 模式就不需要这一步。我建议新项目直接用 history 模式配合这个配置URL 更干净。还有一个部署时容易忽略的点前端 dist 里的 js/css 文件名会带 hash只需要首次部署时上传整个 dist 目录之后每次重新构建覆盖即可。如果只想更新后端可以先停掉旧进程再重新跑新的 jar 包前端不用动。这种“前后端独立发布”的便利性正是前后端分离架构带来的最大红利。4.3 配置文件多环境切换开发、测试、生产不打架实战里不能把所有环境都写在一个 application.yml 里。我用了 SpringBoot 的多环境 profile 机制配置三个文件application-dev.yml、application-prod.yml、application.yml。其中主配置只写公共项环境相关的用 spring.profiles.active 来指定。比如启动时可以 java -jar book-api.jar --spring.profiles.activeprod默认不写就加载 dev。这样本地开发用 dev 连本机 MySQL服务器上用 prod 连线上数据库两套配置互不干扰。MySQL 连接串用 env 占位符也能做到不用硬编码。我通常在配置里写成spring: datasource: url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:book_system}?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/Shanghai username: ${DB_USER:root} password: ${DB_PASSWORD:123456}这样在本地不设置环境变量就采用默认值在服务器上用 export DB_PASSWORDxxx 覆盖就行。这个习惯一旦养成换环境、换数据库都不需要改代码改环境变量即可。部署时也建议把密码这类敏感信息放在环境变量或 Docker 容器配置里而不是写死在配置文件中提交到 git。5. 常见问题与排查技巧实录5.1 启动报错汇总SpringBoot 和 Vue 最常见的坑先列一个排查表都是我在实际运行中遇到且解决过的高频问题现象可能原因解决方案后端启动报 Access denied for userMySQL 用户名密码错误或用的是 root 但没授权核对 application.yml或在 MySQL 里 GRANT 权限后端启动报 Unknown database数据库还没建库执行 schema.sql 先建库建表后端启动报 Failed to configure a DataSource依赖里有数据库驱动但连接配置缺失检查 yml 中 datasource 配置前端 npm run dev 报 Cannot find module依赖没装全删除 node_modules 后重新 npm install前端页面空白但控制台报错路由未匹配或组件导入路径错误检查 router 配置和 import 路径大小写请求接口报 404代理没生效或后端路径不对用 Postman 测后端接口确认代理配置请求接口报 401token 缺失或过期重新登录检查 axios 拦截器是否添加 token表中第一行的问题最坑。本地 MySQL 如果密码带特殊字符比如 、#、%在 yml 里一定要用单引号包裹或者直接放到环境变量中。另一个高频问题是数据库驱动版本和 MySQL 版本不匹配我的方案是锁定 mysql-connector-java 8.0.33兼容 5.7 和 8.0。5.2 数据库连接与中文乱码的彻底解决中文乱码问题几乎每个项目都会遇到。首先确认数据库本身字符集是 utf8mb4然后确认连接字符串加上 characterEncodingutf8mb4再次确认数据库表结构也是 utf8mb4。如果前面都对了最后再看前端的传输编码。axios 默认是 application/json;charsetUTF-8SpringBoot 的 Jackson 默认也是 UTF-8一般不会乱。但是我遇到过数据库表用 utf8 而字段里存了 emoji 的情况那就会导致插入失败换成 utf8mb4 就好了。还有连接池超时的问题。就是系统运行一段时间后第一次操作报 Communications link failure。这是因为 MySQL 的 wait_timeout 默认是 8 小时连接空闲超时后断掉。解决办法是在 jdbc 连接串里加 autoReconnecttrue或者配置 HikariCP 的 max-lifetime 小于 MySQL 的 wait_timeout我一般设成 1800000 毫秒30 分钟彻底避开这个坑。5.3 前端部署后刷新 404 与接口报错的排查思路前端部署到 Nginx 后如果刷新某个子路由页面出现 404按我们前面的配置加一条 try_files 即可。如果你确实用了 hash 模式那不会出现这个问题但 URL 会多一个 # 号美观度差些。我用 history 模式所以配置里必须得有那行 try_files。接口报错一般先从浏览器开发者工具的 Network 面板看请求状态码。如果是 502通常是后端进程挂了或者后端端口不对去服务器 tail -f nohup.out 看日志。如果是 504多半是接口执行太久比如统计大表数据没加索引考虑优化 SQL。如果状态码是 200 但提示业务错误那就看响应体的 code 和 message定位到具体业务逻辑。排查问题的核心思路是“先前端后后端、先网络后代码、先日志后重启”不要盲目猜。6. 实操心得与后续扩展建议6.1 我自己做过多次之后总结出的三个改进点第一图书管理模块最好增加批量导入导出功能用 EasyExcel 实现 Excel 的批量导入。图书数量一多逐个新增效率太低生产环境中通常都有这个需求。第二借阅记录查询必须支持按时间段和借阅状态组合筛选这是管理员最常用的查询维度。第三做一个邮箱或短信提醒逾期功能但是为了项目简洁我用一个简单的定时任务每天扫一遍借阅记录发送通知到管理员邮箱作为演示是可以的。这些点都能让你的系统在答辩或展示时更有亮点。6.2 如果要扩展成完整的微服务或前后端分离方案这个项目虽然是单体但它的接口设计已经为扩展打下了基础。后面如果并发上来了可以按模块拆服务比如把借阅服务独立出来用 Redis 做库存缓存用 RabbitMQ 做借阅消息队列数据库做读写分离。前端也可以从 Vue 3 扩展到 uni-app 或 React Native接口不动客户端直接复用。图书管理系统虽然简单但它是理解分布式系统演进的绝佳起点因为业务边界清晰数据关系不复杂非常适合做架构演进实验。6.3 最后分享一个部署时的小技巧部署前先别急着启动执行一下 mvn clean package 和 npm run build把构建过程彻底跑通再考虑服务器操作。我遇到过多次“本地跑得欢服务器一跑就崩”的情况最常见的差异就是路径大小写问题和端口占用问题。Windows 下文件名不区分大小写Linux 区分所以前端 import 组件时路径大小写一定要和文件名保持一致。端口检查用 netstat -lntp 确认没有占用。把这些问题在部署前排查清楚你的上线过程才会顺滑。这套图书管理系统从开发到部署的完整流程我前前后后花了两天整理大部分时间都耗在调试 Nginx 代理和数据库字符集上。但正因为踩过这些坑我才敢把每个细节都拿出来分享。如果你正好需要一套前后端分离的项目练手或者应付毕业设计照着这套思路去构建遇到问题可以对照常见问题表排查。代码虽然不复杂但整个开发流程做下来你对 SpringBoot、Vue、MyBatis、MySQL 这四者的协作关系一定会有更深刻的理解。