☰
SpringBoot+Vue3+MyBatis图书管理系统:前后端分离全栈实战详解
2026/10/5 13:27:53 网站建设 项目流程

前后端分离的SpringBoot+Vue3+MyBatis图书管理系统,配上一套MySQL数据库,这个组合这几年几乎成了Java全栈学习路径里绕不开的经典项目。不管你是准备毕业设计,还是想给自己简历上增加一个有完整链路的全栈项目,这套技术栈都值得认真敲一遍。图书管理系统看起来很简单,但它把登录、权限、分页查询、关联业务、事务控制、跨域联调这些后端日常高频场景全串起来了,前端部分又能把Vue3的组合式API、路由守卫、状态管理、Axios封装这些实战技能练到位。

这篇文章我从业务拆解讲起,一直讲到后端接口实现、前端页面联调、MySQL环境搭建、部署打包,最后把我在实际开发和调试中踩过的坑一并整理出来。文中涉及的代码都是可以直接复用的核心片段,不是那种只有结构没有内容的空架子。

1. 项目怎么拆:搞懂图书管理系统的边界与选型逻辑

1.1 先把业务逻辑谈清楚:书、人、借还三条主线

图书管理系统的核心业务并不复杂,归根到底是三条线:书的管理、人的管理、书和人的关系管理。书的管理就是图书的增删改查、库存维护、分类管理;人的管理就是用户的注册、登录、角色区分;书和人的关系管理就是借书、还书、借阅记录、逾期判断。把这三条线理清楚了,整个系统的功能模块也就自然浮现出来了。

我习惯用一张表来整理模块边界,开发的时候照着这个表去建页面、写接口,基本不会乱:

业务模块核心数据关键操作常见页面/接口
图书管理图书编号、书名、作者、出版社、分类、库存新增、编辑、删除、分页查询图书列表、图书表单
读者管理用户名、姓名、角色、状态注册、登录、禁用/启用登录页、读者列表
借阅管理借阅记录、借出时间、应还时间、归还时间借书、还书、续借、记录查询借书页、借阅记录列表
统计报表借阅次数、热门图书、库存余量统计查询首页看板、排行榜

之所以把“借阅管理”放在核心位置,是因为它是这个系统里唯一涉及“多表关联 + 事务 + 状态流转”的模块。借一本书,要同时更新借阅记录表和图书库存表,这个操作如果不用事务包起来,就会出现“借阅记录已经生成、但库存没减”这种脏数据。对于学习项目来说,这个场景比单纯写CRUD有价值得多。

1.2 为什么非要用前后端分离,而不是传统的单体页面

很多初学者会问,一个图书管理系统,用SpringBoot模板引擎直接渲染页面不行吗?当然可以,但“前后端分离”本身就是这个项目要重点练的能力。分离架构的核心收益在于:前端和后端可以并行开发,后端把接口定义清楚,前端用Mock数据先行联调;部署的时候前端静态文件可以扔到Nginx,后端只负责提供API,两边互不干扰。

如果是在校学生做毕业设计,分离架构还有一个现实的好处:论文和工作汇报里能多写一章“前后端联调与部署方案”,而且面试时关于跨域、接口设计、打包部署的问题全都覆盖到了。当然代价也很实在——多一套工程,多一次跨域配置,打包部署的链路变长。所以我的建议是:如果你只是想快速跑通一个页面,那就别分离;如果你想通过这个项目把全栈能力练扎实,分离是必须的。

1.3 技术选型的理由:SpringBoot、MyBatis、Vue3、MySQL8各自解决什么问题

SpringBoot选它是因为“约定大于配置”这套理念太适合中小型项目了。内嵌Tomcat、自动装配、起步依赖,省掉了大量XML配置和Web容器搭建的琐碎工作。相比早期的SSH架构,SpringBoot让开发者能把精力集中在业务代码上,这也是为什么现在绝大部分Java岗位要求里都写着SpringBoot。

MyBatis和JPA之争每个项目群都能吵半天。我的观点是:图书管理系统这种业务,SQL逻辑比较直观,用MyBatis更合适。MyBatis把SQL控制权完全交给开发者,复杂查询、多表联查、SQL调优时可操作空间大;而JPA在简单CRUD上虽然快,但一旦涉及动态查询和复杂关联,反而要花大量精力去处理派生查询方法和Entity关系映射。简单说,MyBatis是“你能看见SQL、能控制SQL”,这对学习阶段特别重要。

Vue3选它是因为组合式API带来的工程化优势。图书管理系统的图书列表、借阅记录、统计看板这些页面,内部逻辑都包含“数据请求、筛选条件、分页状态、加载状态”几块内容,用Options API写会散落在data、methods、computed三个区域,而组合式API能把这些逻辑聚合在同一个setup作用域里。加上Vite开发服务器的热更新速度确实快,开发体验比Webpack时代舒服太多。

MySQL8.0就不多说了,稳定、免费、资料多,在本地和服务器上都能跑。需要注意的点是安装时选对字符集utf8mb4,后面中文乱码能省掉很多麻烦。

2. 后端实现:SpringBoot + MyBatis的关键细节

2.1 工程结构怎么组织,接口应该放在哪一层

后端工程我建议按“实体层—映射层—服务层—控制层”四层来组织,不要图省事把业务逻辑塞Controller里。这个习惯越早养成越好,因为后面做权限校验、事务控制、单元测试的时候,分层的优势会非常明显。一个标准的包结构大致是这样:

com.example.library ├── controller # 接收请求、参数校验、返回结果 ├── service # 业务逻辑、事务控制 ├── mapper # MyBatis数据访问接口 ├── entity # 数据库表对应的实体类 ├── vo # 给前端返回的视图对象 ├── common # 统一结果封装、异常处理、工具类 └── config # 跨域配置、拦截器配置

Controller只做三件事:接收参数、调用Service、返回Result对象。Service负责业务规则,比如借书时检查库存、还书时更新状态。Mapper只写SQL和映射,不掺业务判断。这样的结构虽然每个文件都很薄,但整体逻辑链路清晰,出了问题能快速定位是哪个环节。

2.2 数据库表设计:图书表、用户表、借阅记录表

数据库设计是整个项目的基石,表字段没定好,后面写接口会处处难受。图书管理系统最低限度需要三张核心表:图书表(book)、用户表(user)、借阅记录表(borrow_record)。

图书表的核心字段是:

字段名类型说明
idbigint主键,自增
isbnvarchar(32)ISBN编号,可加唯一索引
titlevarchar(128)书名
authorvarchar(64)作者
publishervarchar(128)出版社
categoryvarchar(64)分类
stockint当前可借库存
total_stockint总库存
create_timedatetime上架时间

这里的stock和total_stock是两个概念,total_stock表示图书的总量,stock表示当前还能借的数量。每借出一本,stock减1,归还时再加回来。用一个冗余字段而不是每次动态计算,查询效率更高,这也是实际开发里常见的空间换时间思路。

借阅记录表是整个系统里关联关系最核心的表:

字段名类型说明
idbigint主键
user_idbigint借阅人ID
book_idbigint图书ID
borrow_timedatetime借出时间
due_timedatetime应还时间
return_timedatetime实际归还时间
statustinyint0-借出中 1-已归还 2-逾期

status字段是状态流转的关键。每次还书操作都会先查当前记录的状态,只有“借出中”的记录才能执行归还,避免重复还书造成库存虚增。user_id和book_id上各建一个普通索引就够了,这个数据量级别的系统不需要过度优化,复合索引在这个场景下收益不大。

2.3 核心接口实现:分页查询和借还书的事务控制

图书列表的分页查询是最常见的接口,我用MyBatis手写limit分页。为什么不用PageHelper插件?因为手写一次能让你彻底搞明白分页的原理,用插件反而像黑盒。分页查询的Mapper XML核心部分如下:

<select id="selectPage" resultType="com.example.library.entity.Book"> select id, isbn, title, author, publisher, category, stock, total_stock from book <where> <if test="title != null and title != ''"> and title like concat('%', #{title}, '%') </if> <if test="category != null and category != ''"> and category = #{category} </if> </where> order by id desc limit #{offset}, #{pageSize} </select>

注意这里的分页参数用#{}而不是${},前者是预编译占位符,能有效防止SQL注入;后者是字符串拼接,虽然有时方便但绝不建议用在用户可传的参数上。还有一个细节是concat('%', #{title}, '%'),不要直接写'%${title}%',同样是SQL注入的考虑。

借书接口是系统里最体现事务控制能力的地方。整个借书流程包含三步:查图书库存是否大于0、插入一条借阅记录、把图书库存减1。这三步必须放在同一个事务里,任何一个环节失败,前面已经做的操作都要回滚。代码实现如下:

@Transactional(rollbackFor = Exception.class) public boolean borrowBook(Long userId, Long bookId) { // 1. 查询图书,检查库存是否足够 Book book = bookMapper.selectById(bookId); if (book == null || book.getStock() <= 0) { throw new BusinessException("图书不存在或库存不足"); } // 2. 校验用户是否存在且状态正常 User user = userMapper.selectById(userId); if (user == null || user.getStatus() != 1) { throw new BusinessException("用户不存在或已被禁用"); } // 3. 生成借阅记录,默认借期30天 BorrowRecord record = new BorrowRecord(); record.setUserId(userId); record.setBookId(bookId); record.setBorrowTime(new Date()); record.setDueTime(DateUtils.addDays(new Date(), 30)); record.setStatus(0); borrowRecordMapper.insert(record); // 4. 扣减库存 bookMapper.decreaseStock(bookId); return true; }

@Transactional(rollbackFor = Exception.class)这个注解一定要带上rollbackFor参数,因为Spring默认只对RuntimeException回滚,而业务异常如果是受检异常就会导致事务不生效,这个坑我在早期项目里踩过,印象特别深。

库存扣减这条SQL值得单独说一下:

update book set stock = stock - 1 where id = #{bookId} and stock > 0

这个写法是关键——把“检查库存 > 0”和“扣减库存”合并到一条SQL里完成,通过数据库的行锁和条件更新来防超借。如果在代码里先select再update,两个请求同时进来时会读到同一个库存值,导致超卖。这个知识点面试时经常被问到,能讲明白很加分。

2.4 MyBatis配置的几个易错点:Mapper扫描、驼峰映射、缓存机制

MyBatis在使用中有几个高频坑点。第一个是Mapper接口扫描。要么在启动类上写@MapperScan("com.example.library.mapper"),要么在Mapper接口上逐个加@Mapper注解。漏掉的典型表现是启动报错:Consider defining a bean of type '...UserMapper'。

第二个是驼峰映射。poperties:map-underscore-to-camel-case: true,否则数据库的create_time无法自动映射到实体的createTime字段,查出来全为null。我在配置文件里加上:

mybatis: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

注意后面那个log-impl,开发阶段建议开启,这样控制台会直接打印SQL语句和参数值,排查问题效率翻倍。生产环境记得关掉,否则日志量太大。

第三个是MyBatis的缓存机制。我们平时说的“MyBatis一级缓存”是SqlSession级别的,同一个SqlSession内执行相同的SQL,第二次会直接从缓存里拿结果,不再查数据库。但在Spring集成环境下,如果缓存没有及时清空,可能出现查询结果不刷新的诡异问题。二级缓存是Mapper级别的,需要显式开启,在分布式环境下容易踩脏数据的坑。对于图书管理系统这个体量,我的建议是:不要开二级缓存。数据库只有本地访问,缓存带来的性能提升可以忽略不计,但引入的脏数据风险却实实在在地增加了排错难度。

我还想提一下MyBatis的启动流程,这不仅是理解框架的关键,也是面试常问。MyBatis启动时会先通过XMLConfigBuilder解析全局配置文件,把<mappers>标签中的Mapper XML路径收集起来,再对每个XML进行解析,生成MappedStatement对象,最后通过MapperRegistry把Mapper接口与对应的XML Statement绑定。映射接口的全限定名必须和XML的namespace一致,接口方法名必须和statement的id一致,这就是为什么常见报错Invalid bound statement (not found),本质就是绑定环节的对应关系没对上。另外,当Spring Boot和MyBatis整合时,MybatisAutoConfiguration会帮你完成SqlSessionFactory的创建和Mapper扫描,这也是SpringBoot跟MyBatis配合那么顺畅的原因。

3. 前端实现:Vue3 + 组合式API怎么落地

3.1 项目初始化:Vite脚手架和目录规划

前端工程我直接用Vite的官方脚手架create-vue创建。相比老一代Vue CLI,Vite在冷启动速度和热更新速度上的提升非常明显,现在和面试官聊前端工程化,Vite基本是默认选项。

npm create vue@latest

创建过程中按需选择Router、Pinia、ESLint即可。创建完成后,目录结构按功能整理:

src ├── api # 接口请求模块 ├── router # 路由配置 ├── stores # Pinia状态管理 ├── views # 页面组件 ├── components # 公共组件 └── utils # Axios封装、工具函数

一个很容易忽视的细节是api目录的分层。我习惯每个业务模块一个文件,book.js里放图书相关的接口,borrow.js里放借阅相关的接口。这样做的好处是接口路径管理集中,后端接口一变,只需要在一个文件里修改。

3.2 组合式API:setup、ref、reactive,到底怎么选

Vue3最核心的变化就是组合式API。图书管理系统的图书列表页是一个特别能说明问题的例子——这个页面同时有:图书列表数据、搜索关键词、分页信息、加载状态、选中行数据。用Options API写,data里一大坨,methods里一大堆,computed还要单独写,改一个功能要来回切换多个区块。用组合式API写,代码是按“功能”聚合的:

<script setup> import { ref, reactive, computed } from 'vue' import { getBookPage } from '@/api/book' const loading = ref(false) const searchForm = reactive({ title: '', category: '' }) const pageInfo = reactive({ pageNum: 1, pageSize: 10, total: 0 }) const tableData = ref([]) const loadData = async () => { loading.value = true try { const res = await getBookPage({ ...searchForm, pageNum: pageInfo.pageNum, pageSize: pageInfo.pageSize }) tableData.value = res.rows pageInfo.total = res.total } finally { loading.value = false } } </script>

这里用到ref和reactive各有各的适用场景。我的经验是:基础类型和数组用ref,对象用reactive。搜索条件和分页信息这种结构化的对象用reactive很自然,而tableData这种需要被整体替换的数据,用ref更方便,因为tableData.value = res.rows这行代码直接替换了引用,响应式不会断。

很多教程会说组合式API比Options API“高级”,其实本质是解决逻辑复用和组织的痛点。图书管理系统的借阅记录查询页面和图书列表页面,它们的“数据加载 + 条件筛选 + 分页翻页”逻辑几乎一样,如果用Options API,每个页面都要复制一遍data和methods;用组合式API,可以把这段逻辑抽成一个useBookList.jscomposable,两个页面各自引用,代码量直接减半。这才是组合式API真正的价值。

3.3 路由配置与登录拦截

图书管理系统需要区分管理员和普通读者两种角色,前端路由要做权限控制。Vue Router的路由守卫是这里的关键:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path !== '/login' && !token) { next('/login') } else if (to.meta.roles && !to.meta.roles.includes(localStorage.getItem('role'))) { next('/403') } else { next() } })

路由守卫的逻辑其实很简单:没有token只能去登录页,登录了也不能访问没有权限的页面。但需要注意后端也要做同样的校验,前端的路由守卫只是用户体验层面的拦截,真正的安全必须依赖后端接口鉴权。

路由建议采用history模式还是hash模式?开发环境都无所谓,但如果前端要打进SpringBoot的static目录里部署,history模式刷新页面会404,因为后端没有对前端路由做回退处理。如果不打算单独配Nginx,直接用hash模式最省心。

3.4 Axios封装:拦截器里统一处理登录态和错误提示

Axios封装是每个Vue3项目都绕不开的事。图书管理系统里的接口数量不多,但如果不封装,每个页面单独写一遍请求逻辑和错误处理,代码会重复得让人头疼。我一般封装两层:实例层和拦截器层。

实例层指定baseURL和超时时间:

const service = axios.create({ baseURL: '/api', timeout: 10000 })

开发环境通过Vite的代理把/api转发到后端端口,这个配置放在vite.config.js里:

export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })

拦截器层做两件事。请求拦截器在请求头上加入token,这样后端登录拦截器就能在接口层面做认证;响应拦截器统一处理后端返回的结果结构,如果后端返回401就跳登录页,如果业务code不是0就弹出错误提示。这样每个页面调用接口时只需要关心正常返回的数据,错误处理全部收口到拦截器里。

4. 环境搭建与部署:从空环境到能访问的完整步骤

4.1 MySQL8安装与建库:字符集和时区是两个最容易被忽视的细节

MySQL8的安装方式有很多,本地环境可以直接下载安装包。开发环境我更喜欢用Docker,干净、便于删除重来:

docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root123 \ -e MYSQL_DATABASE=library \ -v /opt/mysql-data:/var/lib/mysql \ mysql:8.0

无论哪种方式,有两个参数一定要检查。第一是字符集,my.cnf里设置character-set-server=utf8mb4;第二是时区,连接URL里带上serverTimezone=Asia/Shanghai。这两个问题不解决,后面大概率会出现中文乱码或者时间字段差8小时的现象。

数据库和账号准备完成后,需要执行建库脚本。我在项目里会准备一个schema.sql,包含建库、建表、插入少量测试数据,保证任何人拿到项目都能一键初始化。测试数据很重要,没有数据的图书管理系统跑起来后空空荡荡,连SQL有没有问题都看不出来。

SpringBoot的数据库连接配置:

spring: datasource: url: jdbc:mysql://localhost:3306/library?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: root123 driver-class-name: com.mysql.cj.jdbc.Driver

4.2 前端打包进SpringBoot静态目录的实现

前后端联调完成后,为了能直接通过SpringBoot的8080端口访问整个项目,可以把前端的构建产物交给后端来托管。具体操作用两步:

先执行前端打包命令:

npm run build

打包完成后,把前端工程dist目录下的所有内容复制到后端工程的src/main/resources/static目录下。然后重新启动SpringBoot,浏览器直接访问http://localhost:8080,就不需要再单独启动Vite开发服务器了。

这个方案的优点是一个服务搞定全部,对演示和部署都方便。缺点也很明显:前端和后端不再独立部署,前后端分离的意义打了一半折扣。所以我把这个方案定位为“演示模式”,在开发环境用Vite代理联调,在交付演示或简化部署时用静态目录托管。

4.3 跨域问题的三种解决方案对比

前后端分离开发时,跨域问题一定会遇到。前端跑在5173端口,后端跑在8080端口,没有处理时浏览器会报CORS错误。解决方式有三种,适用场景各不相同:

方案实现位置优点缺点适用场景
后端CorsFilterSpringBoot配置类配置简单、全局生效放开了所有域开发阶段快速解决
前端Vite代理vite.config.js不依赖后端改动仅开发环境生效日常开发联调
Nginx反向代理Nginx配置符合生产规范需要额外部署Nginx正式环境部署

我在开发阶段推荐用Vite代理,原因很简单,后端不用加任何跨域代码,改动最小。生产环境推荐用Nginx,让前端静态资源请求和后端API请求走同一个域名,从根源上消灭跨域问题。如果只在后端加CorsFilter解决一切,生产环境接口还是会暴露跨域风险,不是一个长久的方案。

5. 我踩过的坑:前后端联调与运行环境的排查实录

5.1 后端高频报错排查表

这个项目的技术栈非常主流,踩坑概率也相对集中。我把遇到过的典型问题整理成表,方便你对照排查:

错误现象根本原因解决方法
Consider defining a bean of type 'BookMapper'Mapper没被扫描启动类加@MapperScan或Mapper接口加@Mapper
Invalid bound statement (not found)XML文件没打包进classes目录确认src/main/resources/mapper/*.xml路径正确
Access denied for user 'root'@'localhost'MySQL密码或主机授权不对执行ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码'
Table 'library.book' doesn't exist建表脚本没执行先执行初始化建表SQL
接口返回时间差8小时连接URL时区配置错误在serverTimezone=Asia/Shanghai后面加时区参数
SQL查询结果中文乱码数据库字符集不是utf8mb4改成utf8mb4,并检查连接参数characterEncoding=utf8

其中Invalid bound statement (not found)这个报错我需要单独强调一下,因为它特别容易迷惑新手。接口方法、ServiceImpl都写对了,但运行时就报“找不到绑定语句”。原因往往是SpringBoot打包时没有把src/main/resources/mapper目录下的XML文件复制到target/classes。排查时先打开target/classes/mapper目录看看有没有对应的XML文件,没有的话检查一下pom.xml里的资源目录配置是不是漏掉了xml类型。

5.2 前端联调常见问题:跨域、白屏、路径错误

前端开发调试时,最容易遇到的是三个问题。第一个是控制台报跨域错误,这对应我们上面说的Vite代理配置,确认/api前缀是否在axios请求里正确拼接。

第二个是页面白屏,没有任何报错。这种情况十有八九是Vue3的创建实例代码有问题,或者路由守卫里出现了死循环。比如beforeEach里做token检查时,如果token不存在但路由又重定向到了/login,而/login的守卫又做了同样判断,就会无限循环跳转,浏览器最后因为栈溢出停止执行。解决办法是在守卫里加上if (to.path === '/login') return next()。

第三个是路由切换后页面正常,但一刷新就404。这是history路由模式在静态部署时的问题,排查方法就是看浏览器地址栏,如果URL里带#就是hash模式,不带#就是history模式。如果不想为这个项目额外配Nginx回退规则,直接改用hash模式是最快的解法。

5.3 调试经验:从三层日志定位问题的高效思路

整个项目跑起来之后,真正调试时的效率取决于你是否有一套清晰的排查思路。我个人的习惯是“由前到后、逐层剥离”:先看浏览器Network面板,确认请求发出去了没有、返回状态码是多少;再看后端控制台日志,重点看SQL日志有没有执行、参数是什么;最后看前端解析,确认数据结构是否符合页面绑定要求。

图书管理系统里那一行log-impl: org.apache.ibatis.logging.stdout.StdOutImpl配置就特别有用。SQL日志一开,MyBatis生成的SQL语句、传入参数、返回条数全部一目了然。很多你觉得“数据查不到”的问题,看了SQL日志就能立刻明白是SQL条件配错了,还是参数传了null。比如查询条件里书名传了空串,如果XML里写的是title = #{title}而不是title like concat('%', #{title}, '%'),空串也能正常匹配到数据;换大写或去掉空格就不行了——这类问题只有日志能最快暴露。

6. 项目复盘与扩展方向:图书管理系统还能变成什么

6.1 面试官最容易追问的六个问题

如果你把这个项目写进简历,面试官大概率会围绕几个关键点深挖。提前想明白,面试时就能有的放矢:

  1. MyBatis和JPA你选哪个,为什么?
  2. 分页查询是怎么实现的,limit参数为什么不直接拼字符串?
  3. 借书操作的事务是如何控制的?如果库存扣减失败,借阅记录会不会残留?
  4. #{}和${}有什么区别,什么场景能用${}?
  5. 前后端分离是怎么解决跨域的?生产环境和开发环境分别怎么做?
  6. Vue3的ref和reactive有什么区别,什么场景用哪个?

这几个问题都能在这个项目里找到具体的答案。我的建议是:每个问题都回到自己的代码里去解释,而不是背八股文。比如事务控制那题,直接说“我在借书Service方法上加@Transactional(rollbackFor = Exception.class),一旦库存扣减失败会抛异常,整个事务就会回滚,借阅记录不会落库”——这样的回答明显比罗列概念更有说服力。

6.2 这个项目往后还能怎么扩展

图书管理系统跑通之后,扩展路径很丰富。最简单的升级是引入JWT替换Session模型,登录成功后签发token,前端请求头带上Authorization,后端用拦截器校验。这个改动能让你把SpringMVC拦截器机制和JWT的原理都过一遍。

其次是引入Redis做热门图书排行榜的缓存。借还书时更新借阅计数,定时把计数同步到Redis的ZSet,前端排行榜接口直接从Redis读。这个扩展能接触到缓存设计、定时任务、数据一致性等话题,项目技术含量直接上一个台阶。

再往后可以加Docker部署:后端打镜像、前端打包后由Nginx承载、MySQL用容器跑,再写个docker-compose文件一键拉起整个系统。这套方案会把项目交付这个维度补上,而且写进简历在“项目管理/部署运维”章节也有东西可写。无论选哪个方向,都要克制加功能的欲望,先把核心流程跑稳了再谈扩展。

写在最后的一点个人体会

图书管理系统在别人眼里可能很“老套”,但我每次回看从这个项目里学到的东西,都觉得很值。它让我把SpringBoot的自动装配、MyBatis的映射原理、事务的生效机制、分页SQL的写法、Vue3的响应式原理、跨域的来龙去脉这些零散的知识点,通过一个完整需求串成了一条线。后来我接手公司项目时发现,那些线上系统拆开来看,无非就是在这些基础能力上叠了更多业务规则和并发场景而已。

有个习惯我很推荐大家也试试:动手编码之前,先花半小时把一个接口的请求方式、路径、入参、出参写在一个Markdown文件里。图书管理系统体量不大,这个步骤可能显得多余,但等前端、后端、测试三方同时介入时,这份最原始的接口契约文档会比群聊记录靠谱得多。很多联调时的扯皮,说到底就是一开始参数格式没约定清楚。项目可以简单,做事的习惯不妨从第一天就开始认真养。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询