十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue3图书管理系统:前后端分离项目从零搭建到部署

SpringBoot+Vue3图书管理系统:前后端分离项目从零搭建到部署 这段时间我反复带人把 SpringBootVue3 图书管理系统从零到能跑、能演示、能答辩的状态走了一遍。先说判断这个项目的魅力不在“图书业务”本身而在于它把后端接口、前端页面、数据库设计、前后端联调、部署发布这一整条链路压缩到了一个可以直接复制的模板里。对准备做毕业设计、课程设计或者简历上缺一个“能讲清楚的项目”的人来说它是性价比很高的起点。你真正要关注的并不是“怎么管理图书”而是搞清楚一个前后端分离的项目从启动到增删改查完整跑通中间到底有哪些坑、哪些参数、哪些判断标准。下面我按实际落地顺序拆一遍。环境以 Java Spring Boot 后端、Vue3 Vite 前端、MySQL 数据库为例按常规配置说明。如果你下载到的源码带 SQL 脚本、前端 dist 包或部署文档也可以拿这份思路去核对项目是否完整。1. 核心价值判断这个项目的难点不在图书业务而在分离架构1.1 前后端分离到底“分离”了什么很多人第一次接触图书管理系统以为难点是“书怎么分类、库存怎么扣、逾期怎么算”。但这类教学项目的重点其实不在业务算法而在前后端分离的架构方式。在单体时代Java 页面里会直接写 JSP前端和后端混在一起部署。它简单但问题也很明显前端改样式要重启后端后端改接口要和前端页面绑在一起多人协作时互相牵制。前后端分离后后端只负责写接口、处理数据、返回 JSON前端只负责渲染页面、调用接口、展示数据。两者通过 HTTP 接口通信开发时各起各的进程部署时也可以分开部署。图书管理系统正好适合做这个演示。它业务简单不需要复杂的权限模型也不涉及高并发但增删改查五个基础操作是全的。把这五个操作做成规范接口再让 Vue3 页面调用这些接口一条完整链路就出来了。我经常提醒新手一句话不要急着写代码先把“后端在哪里、前端在哪里、数据库在哪里、它们之间怎么通信”这四个问题想明白。这个项目能跑通核心标志就是后端返回 JSON前端拿到 JSON 并把结果渲染到表格里。1.2 一个 CRUD 项目能覆盖哪些面试考点很多准备实习、找工作的同学会把图书管理系统写进简历。面试官通常不会只问“你做了什么功能”而会往细节里问。这个项目能带的考点很集中Spring Boot自动配置原理、启动流程、依赖注入、Spring MVC 请求处理流程。MyBatis 或 MyBatis-PlusMapper 接口如何映射 SQL、分页插件怎么配置、数据库表和实体类字段如何对应。Vue3组合式 API、ref 和 reactive 的区别、组件通信、生命周期、路由跳转。Axios 与跨域前端请求代理、响应拦截器、统一错误处理。项目部署后端 jar 包怎么打、前端静态文件怎么托管、Nginx 如何反向代理。所以这个项目适合当“骨架项目”而不是“最终目标”。你可以在图书管理基础上继续加用户登录、管理员权限、借阅记录、归还提醒、数据统计、Excel 导入导出等模块。每一个扩展模块都是简历上的一个可讲点。1.3 标题里说的“半小时搭建”是什么意思半小时不是一个绝对承诺而是一个节奏参考。如果你已经准备好环境数据库建好表源码结构完整从头创建后端工程、初始化前端项目、跑通接口确实可以在半小时内完成一次“从空目录到浏览器看到列表页面”的流程。但如果你是第一次接触我不建议为了追求半小时而跳过理解。更好的方式是这样分配时间前 10 分钟看数据库脚本理解有哪些表、哪些字段。中间 10 分钟启动后端用接口测试工具确认分页查询能返回 JSON。后 10 分钟启动前端通过页面完成一次新增和删除。这个顺序能让你在最短时间内验证项目是否完整也方便你稍后向别人讲解。2. 开发前准备清单版本匹配比安装本身更关键2.1 环境版本怎么选环境准备是这类项目里最容易出问题的地方。很多报错并不是代码问题而是 JDK、Node.js、MySQL 版本之间互相不兼容。我按常见稳定组合给出建议具体版本以你实际下载的源码为准。组件建议版本说明JDK8、11 或 17Spring Boot 2.x 常见用 JDK 8/11Spring Boot 3.x 要求 JDK 17Maven3.6 以上用来拉依赖和打包MySQL5.7 或 8.0差别不大关键看数据库连接驱动Node.js16 以上Vue3 配合 Vite 需要较新版本包管理器npm 或 pnpm安装依赖时用建议 npm如果你的项目是 Spring Boot 2.7 JDK 8那就不要手动升级到 Spring Boot 3.x否则很多依赖坐标和配置方式都会变。如果前端是 Vue3 ViteNode 版本过老会导致 Vite 无法启动。这里最稳妥的做法是先看项目里pom.xml和package.json标明的版本范围再决定本地环境用什么。2.2 后端工程初始化后端工程可以通过 Spring Initializr 创建也可以直接复用已有源码。如果是手工创建核心依赖一般包括 Spring Web、MySQL 驱动、MyBatis-Plus、Lombok。不同 Spring Boot 版本下MyBatis-Plus 的 starter 坐标写法会有差异安装时注意。一个典型依赖块长这样dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency注意MyBatis-Plus 的版本号需要和 Spring Boot 版本兼容。如果启动时报Invalid value type for attribute factoryBeanObjectType基本就是 MyBatis-Plus 版本与 Spring Boot 3.x 不匹配或者依赖没有被正确引入。2.3 数据库设计先建表再动手图书管理系统的核心表一般就是图书表。常见的字段包括CREATE TABLE book ( id bigint NOT NULL AUTO_INCREMENT, isbn varchar(32) DEFAULT NULL COMMENT ISBN编号, book_name varchar(100) NOT NULL COMMENT 书名, author varchar(100) DEFAULT NULL COMMENT 作者, publisher varchar(100) DEFAULT NULL COMMENT 出版社, category varchar(50) DEFAULT NULL COMMENT 分类, price decimal(10,2) DEFAULT NULL COMMENT 价格, stock int DEFAULT 0 COMMENT 库存, publish_date date DEFAULT NULL COMMENT 出版日期, status tinyint DEFAULT 1 COMMENT 状态1上架 0下架, cover_url varchar(255) DEFAULT NULL COMMENT 封面图地址, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书信息表;这个表结构基本覆盖了常见字段类型整数、字符串、小数、日期、时间。比单纯id name price三字段的表更接近真实项目。为什么用utf8mb4因为它能存储 emoji 表情和其他特殊字符。如果项目里涉及书名、作者名中的冷僻字这个设置能减少一部分乱码问题。建表时还有个建议不要过度设计。有些源码会在每张表里都加create_time、update_time、deleted字段这种是通用做法没问题。但如果业务上根本没用逻辑删除则不需要为每个接口硬编码deleted0查询条件否则反而会降低可读性。3. 后端 Spring Boot 实现把增删改查做成标准接口3.1 配置文件与统一返回结果后端启动的第一步是确保能连上数据库。看application.yml里的配置时重点检查四个位置数据库地址用户名密码时区一个常见的配置server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/library_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0这里map-underscore-to-camel-case: true能让数据库字段book_name自动映射为实体类属性bookName。如果不开这个配置要么手动写 ResultMap要么在查询语句里给字段加别名。前端和后端分离之后后端返回的数据格式最好固定。一般会封装一个ResultT对象包含状态码、提示信息和数据。这样前端可以统一判断请求是否成功。public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMsg(success); result.setData(data); return result; } public static T ResultT error(String msg) { ResultT result new Result(); result.setCode(500); result.setMsg(msg); return result; } }3.2 实体类、Mapper 接口和 Service 层后端代码的分层一般是这样Controller 接收请求Service 处理业务逻辑Mapper 访问数据库。对应图书模块就是 BookController、BookService、BookMapper。使用了 MyBatis-Plus 后实体类可以这样写TableName(book) public class Book { TableId(type IdType.AUTO) private Long id; private String isbn; private String bookName; private String author; private String publisher; private String category; private BigDecimal price; private Integer stock; private LocalDate publishDate; private Integer status; private String coverUrl; private LocalDateTime createTime; private LocalDateTime updateTime; }Mapper 接口可以非常简单public interface BookMapper extends BaseMapperBook { }为什么这里可以缩这么短因为 MyBatis-Plus 在启动时会自动为BaseMapper提供常用的单表方法例如selectById、insert、updateById、deleteById、selectPage。它们对图书管理系统这种单表 CRUD 来说已经足够。Service 层可以继续组合这些方法。如果项目追求分层清晰可以写接口和实现类如果只是为了毕设演示直接写一个BookServiceImpl也完全够用。我更建议分清接口和实现因为面试时这是一个可以说的点。3.3 Controller 接口设计与参数说明图书管理系统的接口一般包含五个功能请求方式路径参数分页查询GET/api/book/pagecurrent、size、keyword查询详情GET/api/book/{id}路径参数 id新增图书POST/api/bookJSON 数据修改图书PUT/api/bookJSON 数据删除图书DELETE/api/book/{id}路径参数 id一个简化版 Controller 示例RestController RequestMapping(/api/book) public class BookController { Resource private BookService bookService; GetMapping(/page) public ResultPageBook page(RequestParam(defaultValue 1) Integer current, RequestParam(defaultValue 10) Integer size, RequestParam(required false) String keyword) { PageBook page bookService.pageBooks(current, size, keyword); return Result.success(page); } PostMapping public ResultBoolean save(RequestBody Validated Book book) { return Result.success(bookService.saveBook(book)); } PutMapping public ResultBoolean update(RequestBody Validated Book book) { return Result.success(bookService.updateBook(book)); } DeleteMapping(/{id}) public ResultBoolean delete(PathVariable Long id) { return Result.success(bookService.deleteBook(id)); } }这里有几个点可以展开理解。第一为什么分页用 GET因为查询不修改数据参数可以放在 URL 上也方便在浏览器里直接验证。第二为什么新增和修改用 POST 和 PUT 区分这是 REST 风格约定。如果项目里团队约定不严格也可以用 POST 做新增、PUT 做修改。重要的是前后端保持一致不要前端写 POST后端接口实际是 GET。第三为什么参数要用RequestBody因为前端提交的是 JSON 字符串Spring Boot 需要把 JSON 反序列化为 Book 对象。如果不用这个注解参数可能取不到值。3.4 条件查询不要用拼接自己骗自己分页接口里的 keyword 常用来做模糊搜索。常规思路是在 Service 里构造一个 LambdaQueryWrapper。public PageBook pageBooks(Integer current, Integer size, String keyword) { PageBook page new Page(current, size); LambdaQueryWrapperBook wrapper new LambdaQueryWrapper(); if (StringUtils.hasText(keyword)) { wrapper.like(Book::getBookName, keyword) .or() .like(Book::getAuthor, keyword); } return bookMapper.selectPage(page, wrapper); }这个写法简单面试时也能讲清楚。注意不要写成一堆 if 嵌套去改 SQL 字符串。用 LambdaQueryWrapper 的好处是类型安全字段名在编译期就能被检查比手写字符串列名更不容易出错。如果项目需要更复杂的 SQL比如多表联查、统计报表再考虑写 XML 里的自定义 SQL。图书管理系统到这一步通常不需要。4. 前端 Vue3 工程从初始化到页面可交互4.1 用 Vite 初始化项目Vue3 现在主流的脚手架是 Vite创建命令在不同 npm 版本下略有差异。一般可以这样操作npm create vitelatest book-manager-ui -- --template vue cd book-manager-ui npm install这里要提醒一个容易踩的坑npm create vitelatest过程中可能会提示你选择框架和变体。如果命令行交互不顺利可以直接选择 Vue 和 JavaScript 模板不要选 TypeScript除非你熟悉 TS。图书管理系统用 JS 足够。安装完成后再安装额外依赖npm install axios vue-router4 element-plus4.2 页面结构与路由规划建议把页面拆成几个模块不要把所有内容塞在一个组件里。常见的结构是src/ api/ -- 后端接口封装 router/ -- 前端路由 views/ -- 页面组件 components/ -- 公共组件 layout/ -- 布局组件路由可以这样配置import { createRouter, createWebHistory } from vue-router const router createRouter({ history: createWebHistory(), routes: [ { path: /, component: () import(/layout/Layout.vue), children: [ { path: books, component: () import(/views/BookList.vue) } ] } ] }) export default router4.3 Axios 统一封装Axios 封装的核心目的是让每个页面不用重复写baseURL、headers、错误处理逻辑。一个常用的最小封装是import axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.response.use( (response) { const res response.data if (res.code ! 200) { ElMessage.error(res.msg || 请求失败) return Promise.reject(new Error(res.msg)) } return res }, (error) { ElMessage.error(error.message || 网络异常) return Promise.reject(error) } ) export default request这里把baseURL设为/api看起来像是请求当前前端的同源路径。实际上在开发环境要靠 Vite 的代理转发到后端这样可以避免跨域问题。4.4 图书列表页面表格、分页、弹窗表单图书列表页面是这个项目的核心界面。我一般会把页面拆成三层顶层是搜索表单包含书名关键字和分类选择。中间是数据表格展示图书列表。底部是分页器。新增和编辑共用一个弹窗表单。表格列通常包括列字段书名bookName作者authorISBNisbn价格price库存stock状态status操作编辑、删除在 Vue3 里关键状态可以这样管理import { ref, reactive, onMounted } from vue import { getBookPage, createBook, updateBook, deleteBook } from /api/book const loading ref(false) const bookList ref([]) const total ref(0) const queryParams reactive({ current: 1, size: 10, keyword: })这里ref和reactive的区别是新手容易迷惑的地方。简单理解ref适合包裹基本类型或整个对象访问时要用.valuereactive适合包对象模板里访问时不需要.value。两种写法都能用团队里保持统一就好。5. 联调阶段端口、代理和增删改查完整验证5.1 前端代理配置开发时后端默认跑在8080前端 Vite 默认跑在5173。前端页面直接请求http://localhost:8080/api/book/page会遇到跨域问题。一种常见的解决方式是在 Vite 配置文件里加代理server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端请求/api/book/page时Vite 开发服务器会把请求转发到http://localhost:8080/api/book/page。浏览器里看到的请求域名还是当前前端域名所以不会触发跨域拦截。为什么我更推荐用代理而不是直接在后端开启全局 CORS因为开发环境和生产环境可以共用同一种前端路径部署后只要调整代理规则前端代码不需要改接口地址。这也是很多实际项目的做法。5.2 完整测试顺序联调阶段不要一上来就用页面测所有功能。我建议按这个顺序验证。第一单独测后端接口。用浏览器直接访问后端分页接口地址看能不能返回 JSON。不能返回就说明后端起不来或数据库配置有问题。第二单独测前端页面。启动 Vite访问前端页面看页面是否渲染。此时列表可能是空的这没关系。第三测列表联动。打开前端页面按 F12 看 Network 面板。如果请求成功表格里应该看到图书数据如果请求失败就看后端日志和前端 Console。第四测新增。在弹窗里填数据提交后看列表是否刷新。刷新逻辑有两种提交后调用列表接口重新查第一页或者重新加载当前页。我用后者这样更贴近真实操作习惯。第五测修改。修改一条数据后确认表格里的值已经变化同时数据库里对应字段有更新。第六测删除。删除一条数据后确认总条数减一再次刷新页面时数据不会“复活”。5.3 成功结果长什么样这里的成功结果不是“页面没白屏”这么简单。我给你一个更明确的判断标准后端日志出现 SQL 查询记录。前端 Network 面板请求状态 200。返回 JSON 的 code 是 200。表格显示的数据和数据库记录一致。新增、修改、删除后刷新页面数据仍然正确。只要以上五点全部满足前后端分离链路就算真的通了。如果只满足页面显示但新增后刷新数据丢失那不叫跑通叫“看着能跑”。6. 从单条管理到批量场景扩展、部署和性能边界6.1 批量导入和批量删除能不能做图书管理系统做到一定程度自然会有批量需求。比如管理员想把一批书一次性导进去或者在列表里勾选多条记录批量下架。批量操作最容易踩的坑是前端循环发请求。比如你有 100 条数据不应该在 for 循环里调 100 次POST /api/book。更合理的做法是后端提供一个批量接口一次接收一个数组然后循环插入。这样数据库连接、事务控制和失败回滚都更好处理。后端批量接口可以用类似下面的结构PostMapping(/batch) public ResultBoolean batchSave(RequestBody ListBook books) { return Result.success(bookService.saveBatch(books)); }前端调用时把数组整体传给后端const res await request.post(/api/book/batch, bookList)批量操作前一定要做两件事校验数据格式给用户明确的进度反馈。否则批量导入 100 条第 50 条数据格式错误整个请求失败用户完全不知道前面 49 条到底有没有入库。6.2 分页、搜索和排序的参数边界分页和搜索看起来简单参数边界却容易出问题。current和size不要允许前端无限传大。比如一个恶意请求把 size 传成 100000后端就要一次性查询十万条数据没加限制时容易拖垮数据库。实际项目里可以给 size 加最大值校验或在配置层限制。模糊搜索字段也要控制范围。书名、作者可以做 like 查询但不要对create_time这种时间字段单独做 like。时间字段更适合范围查询比如传开始时间和结束时间。如果要做排序建议在排序字段和排序方向上做白名单校验不要直接把前端传的排序字段拼接进 SQL。否则可能出现字段不存在或排序方向异常的问题。6.3 打包部署前端 dist 和后端 jar本地能跑通后部署是另一个话题。后端一般用 Maven 打包成可执行 jarmvn clean package生成的target目录下会有一个xxx.jar在服务器上执行java -jar xxx.jar前端需要先构建静态文件npm run build生成dist目录。部署方式有两种常见选择。第一种用 Nginx 托管前端静态文件并把/api请求反向代理到后端服务。这是前后端分离项目比较标准的做法。server { listen 80; server_name your-domain; root /opt/book-manager/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; } }第二种把前端dist目录拷贝到 Spring Boot 的src/main/resources/static下然后重新打包成一个 jar。这种方式适合学习演示但不符合真正的前后端分离部署习惯。如果面试时被问到“你怎么部署”建议讲第一种更接近实际项目。7. 常见报错和排查链路不要一上来就改代码7.1 排查顺序这类项目报错时我的第一反应不是看代码而是按下述顺序排查。先说输入先复现一次记录报错信息和操作步骤。很多问题并不是每次都出现比如只有特定搜索关键字才报错只有删除某一条数据才报错。再看网络打开浏览器 F12切到 Network 面板看请求是否发出、请求地址是否正确、响应状态码是多少。这一步能快速区分问题在前端还是后端。再看后端日志日志往往是最直接的线索。表不存在、字段不存在、SQL 语句绑定失败这些在日志里都很明显。再看配置确认数据库连接、端口、前端代理、文件路径配置是否正确。最后才去查代码逻辑如果前四步都没问题再怀疑接口参数传递、逻辑分支和状态处理。7.2 高频问题清单现象高频原因优先排查点前端请求 404代理路径和后端接口路径不一致Network 面板实际请求地址后端报 Unknown column实体字段与数据库列名映射失败驼峰映射配置、ResultMap数据库连接失败MySQL 未启动、密码错误、时区问题application.yml 里连接配置跨域报错前端直连后端域名/端口不同Vite proxy、CORS 配置npm run dev 失败Node 版本过老、依赖没装完node -v删除 node_modules 重装jar 包无法启动端口被占用、依赖冲突检查java -jar报错日志页面白屏路由模式或资源路径问题控制台报错检查 history 模式和 base 路径7.3 如果只是学习默认配置够用最后说一句边界判断。图书管理系统作为练习项目默认配置、单机部署、少量数据都够用。你不必为了炫技引入复杂的微服务架构、分布式缓存、消息队列。那些技术不是这个项目该承载的内容。如果是为了长期维护或生产使用就要补上用户登录、权限校验、操作日志、数据备份、接口限流这些安全与运维层面的东西。但那是另一个“生产化改造”的话题和“让项目跑起来”是两回事。我更建议先把单条增删改查跑稳再把批量操作加上再考虑部署。这三个阶段每完成一个你都能在简历和面试里多讲一层。
返回列表