图书管理系统是软件工程领域经典的入门级项目,它涵盖了CRUD操作、用户认证、权限管理、前后端分离、数据库设计等全栈开发的几乎所有核心知识点。不同于只关注“能跑起来”的速成教程,本教程将深入每一行代码,详细讲解“为什么这么写”、“底层原理是什么”、“不同方案如何选择”。
本教程采用前后端分离的架构:
后端:Java + Spring Boot + MyBatis Plus + MySQL + JWT + Spring Security
前端:Vue 3 + Element Plus + Axios + Vue Router + Pinia
数据库:MySQL 8.0
我们将从零开始,搭建一个完整的图书管理系统,实现以下功能:
用户注册、登录、JWT认证
图书的增删改查、分页查询、条件搜索
借书、还书功能(含库存扣减、借阅数量限制、逾期判断)
权限管理(普通用户只能借书还书,管理员可管理图书)
前端响应式界面
第一部分:系统架构与接口设计
1.1 前后端分离架构解析
传统Web开发中,后端(如JSP、Thymeleaf)直接渲染HTML返回给浏览器,前端只负责显示,这种模式称为“服务端渲染”。它的优点是SEO友好、首屏加载快;缺点是前后端耦合严重,难以维护和扩展。
前后端分离架构将前端和后端解耦:
https://tmywi.cn
前端:独立项目(Vue/React/Angular),负责UI交互和路由,通过HTTP API与后端通信
后端:独立项目(Spring Boot),只提供RESTful API接口,返回JSON数据
这种架构的优势:
技术栈独立:前后端可以选用最适合的技术
开发并行:前后端可以根据接口文档并行开发
部署独立:可以分别部署,互不影响
多端复用:一套后端API可以同时服务于Web、App、小程序
1.2 API接口设计
在开发之前,我们首先设计好前后端交互的API接口规范。良好的接口设计是团队协作的基础。
1.3 统一响应格式
为了便于前端统一处理,我们定义了统一的响应格式:
// 成功响应(有数据)
{
"code": 200,
"message": "success",
"data": { ... },
"timestamp": 1714212345678
}
// 成功响应(无数据)
{
"code": 200,
"message": "操作成功",
"data": null,
"timestamp": 1714212345678
}
// 失败响应
{
"code": 401,
"message": "用户名或密码错误",
"data": null,
"timestamp": 1714212345678
}
为什么需要统一响应格式:
前端可以根据code字段判断请求成功/失败,无需解析不同的数据结构
timestamp字段可以用于防重放攻击和日志追踪
统一的错误处理可以在拦截器中统一实现
第二部分:数据库设计
2.1 数据库设计原则
在开始编码之前,我们需要先设计好数据库表结构。好的数据库设计应该遵循以下原则:
范式化 vs 反范式化:通常遵循第三范式(3NF)减少数据冗余,但在查询频繁的场景可以适当反范式化增加冗余字段
字段类型选择:选择最合适的字段类型,避免浪费存储空间
索引设计:为频繁查询的字段建立索引,但要避免过度索引影响写入性能
软删除:使用status字段标记删除状态,而非物理删除,便于数据恢复
2.2 创建数据库
-- 如果数据库已存在则删除(开发环境),生产环境慎用
DROP DATABASE IF EXISTS library_db;
-- 创建数据库,指定字符集为utf8mb4(支持emoji),排序规则为utf8mb4_unicode_ci(支持多语言排序)
CREATE DATABASE library_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE library_db;
为什么选择utf8mb4而不是utf8:MySQL的utf8最多支持3字节字符,无法存储emoji表情。utf8mb4是真正的UTF-8,支持4字节字符。
2.3 用户表
-- 用户表:存储系统用户信息
CREATE TABLE `user` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID,自增主键',
`username` VARCHAR(50) NOT NULL COMMENT '用户名,登录使用,必须唯一',
`password` VARCHAR(255) NOT NULL COMMENT '密码,使用BCrypt加密存储,长度60左右,255足够',
`real_name` VARCHAR(50) COMMENT '真实姓名',
`email` VARCHAR(100) COMMENT '邮箱',
`phone` VARCHAR(20) COMMENT '手机号',
`role` VARCHAR(20) NOT NULL DEFAULT 'user' COMMENT '角色:admin(管理员) / user(普通用户)',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0(禁用) 1(启用)',
`avatar` VARCHAR(255) COMMENT '头像URL',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`),
KEY `idx_role` (`role`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';

索引设计:
uk_username:唯一索引,保证用户名不重复,同时加速登录查询
idx_role:普通索引,用于按角色筛选用户
idx_status:普通索引,用于查询启用/禁用用户
2.4 图书表
-- 图书表:存储图书基本信息
CREATE TABLE `book` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '图书ID',
`isbn` VARCHAR(20) NOT NULL COMMENT 'ISBN编号(国际标准书号),唯一标识一本书',
`name` VARCHAR(200) NOT NULL COMMENT '图书名称',
`author` VARCHAR(100) NOT NULL COMMENT '作者',
`publisher` VARCHAR(100) COMMENT '出版社',
`publish_year` INT COMMENT '出版年份',
`category` VARCHAR(50) COMMENT '分类(如:编程语言、前端开发、后端框架等)',
`price` DECIMAL(10,2) COMMENT '价格,DECIMAL保证精度,不用FLOAT/DOUBLE',
`stock` INT NOT NULL DEFAULT 0 COMMENT '库存数量,初始为0',
`borrowed` INT NOT NULL DEFAULT 0 COMMENT '已借出数量,与stock之和为总藏书量',
`cover_url` VARCHAR(500) COMMENT '封面图片URL',
`description` TEXT COMMENT '图书描述,TEXT类型可存储长文本',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0(下架) 1(上架)',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_isbn` (`isbn`),
KEY `idx_name` (`name`),
KEY `idx_category` (`category`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='图书表';
关键字段解析:
isbn:国际标准书号,全球唯一,用唯一索引防止重复录入
price:使用DECIMAL(10,2)而非FLOAT,因为浮点数有精度损失,金额必须精确
stock和borrowed:分开记录,便于计算剩余库存和借出统计
status:软删除机制,下架而非物理删除
2.5 借阅记录表
-- 借阅记录表:存储用户借还书记录
CREATE TABLE `borrow_record` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '记录ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`book_id` BIGINT NOT NULL COMMENT '图书ID',
`borrow_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '借书时间',
`due_time` DATETIME NOT NULL COMMENT '应还时间(借书时间 + 借阅期限)',
`return_time` DATETIME COMMENT '实际还书时间,NULL表示未归还',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0(借出中) 1(已归还) 2(逾期)',
`fine` DECIMAL(10,2) DEFAULT 0.00 COMMENT '罚款金额',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_book_id` (`book_id`),
KEY `idx_status` (`status`),
KEY `idx_due_time` (`due_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='借阅记录表';
状态流转:
借书时:status=0,记录borrow_time和due_time,return_time=NULL
还书时:status=1,记录return_time
逾期判断:due_time < NOW() 且 status=0
2.6 初始化数据
-- 插入管理员账户
-- 注意:这里插入的是BCrypt加密后的密码占位符,实际使用需要后端生成
-- BCrypt每次加密结果不同,需要通过代码插入
INSERT INTO `user` (`username`, `password`, `real_name`, `role`)
SELECT 'admin', '$2a$10$N.Zq9qZ1xXkLJX5tRrJj5uQ8l9o0iP7lKjHgFdSaSdFgHjJkKlL;', '系统管理员', 'admin'
WHERE NOT EXISTS (SELECT 1 FROM `user` WHERE `username` = 'admin');
-- 插入示例图书数据
INSERT INTO `book` (`isbn`, `name`, `author`, `publisher`, `category`, `stock`) VALUES
('9787115536734', 'Java从入门到精通', '李刚', '清华大学出版社', '编程语言', 10),
('9787121345666', 'Vue.js实战', '梁灏', '电子工业出版社', '前端开发', 8),
('9787302512666', 'Spring Boot实战', 'Craig Walls', '人民邮电出版社', '后端框架', 5);