1. 项目概述
这个图书管理系统是我去年为一个高校图书馆开发的实际项目,采用目前主流的前后端分离架构。系统上线后稳定运行至今,日均处理3000+图书借阅记录。整套技术栈选型经过充分验证,特别适合需要快速开发又要求稳定性的中小型图书管理场景。
系统最核心的价值在于解决了传统图书管理中的三个痛点:手工登记易出错、数据统计滞后、多校区协同困难。通过数字化流程,将图书入库、借阅、归还、统计等环节全部线上化,管理员工作效率提升60%以上。现在连最抗拒新技术的老馆员都能熟练使用这套系统。
2. 技术架构解析
2.1 后端技术栈
SpringBoot 2.7.4选型是经过多个版本对比测试后的决定。相比旧版的2.5.x,这个版本在启动速度和内存占用上有明显优化,特别适合需要长期运行的图书管理系统。我在pom.xml中精心配置了以下核心依赖:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>8.0.28</version>
</dependency>
MyBatis 3.5.9配合MyBatis-Plus 3.5.2使用,在简化CRUD操作的同时保留了足够的灵活性。这里有个重要技巧:一定要在application.yml中配置map-underscore-to-camel-case为true,这样数据库的下划线命名会自动转为Java的驼峰命名:
yaml复制mybatis:
configuration:
map-underscore-to-camel-case: true
2.2 前端技术栈
Vue 3.2.13组合式API比选项式API更适合复杂的图书管理界面开发。项目采用Vite 3.0.7构建,相比Webpack冷启动时间缩短了80%。Element Plus 2.2.28提供了一套完善的UI组件,特别适合快速开发管理后台。
我在src/api目录下设计了符合RESTful规范的接口调用层,使用axios进行封装。这里分享一个实战技巧:为所有请求添加统一的错误处理:
javascript复制const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 5000
})
service.interceptors.response.use(
response => {
return response.data
},
error => {
if (error.response.status === 401) {
router.push('/login')
}
return Promise.reject(error)
}
)
3. 数据库设计
3.1 核心表结构
MySQL 8.0采用InnoDB引擎,字符集统一为utf8mb4以支持完整Unicode字符(包括emoji)。以下是经过实际检验的五个核心表:
- 图书表(book)
sql复制CREATE TABLE `book` (
`id` bigint NOT NULL AUTO_INCREMENT,
`isbn` varchar(20) NOT NULL COMMENT '国际标准书号',
`title` varchar(100) NOT NULL,
`author` varchar(50) NOT NULL,
`publisher` varchar(50) NOT NULL,
`publish_date` date NOT NULL,
`price` decimal(10,2) NOT NULL,
`status` tinyint NOT NULL DEFAULT '1' COMMENT '1-在馆 2-借出 3-维修',
`location` varchar(50) NOT NULL COMMENT '书架位置',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_isbn` (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 用户表(user)
sql复制CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
`password` varchar(100) NOT NULL,
`real_name` varchar(20) NOT NULL,
`phone` varchar(20) NOT NULL,
`role` tinyint NOT NULL COMMENT '1-管理员 2-普通用户',
`max_borrow` int NOT NULL DEFAULT '5' COMMENT '最大借阅量',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
重要安全提示:密码字段必须使用BCryptPasswordEncoder加密存储,绝对禁止明文保存!
3.2 索引优化实践
在图书查询接口的优化过程中,我发现三个关键索引能显著提升性能:
- 复合索引:
title+author(覆盖80%的搜索场景) - 单列索引:
publish_date(用于新书推荐) - 单列索引:
status(用于快速筛选可借阅图书)
实测在50万条记录下,有索引的查询速度比无索引快30倍以上。但要注意索引不是越多越好,每增加一个索引都会降低写入速度。
4. 核心功能实现
4.1 图书借阅流程
借阅业务看似简单,实则要考虑多种边界情况。这是我的实现逻辑:
java复制@Transactional
public BorrowResult borrowBook(Long userId, Long bookId) {
// 1. 校验用户是否存在且未达到最大借阅量
User user = userMapper.selectById(userId);
if (user == null) {
throw new BusinessException("用户不存在");
}
Integer currentBorrow = borrowMapper.countByUserIdAndStatus(userId, 1);
if (currentBorrow >= user.getMaxBorrow()) {
throw new BusinessException("已达到最大借阅量");
}
// 2. 校验图书状态
Book book = bookMapper.selectById(bookId);
if (book == null) {
throw new BusinessException("图书不存在");
}
if (book.getStatus() != 1) {
throw new BusinessException("图书当前不可借");
}
// 3. 创建借阅记录
Borrow borrow = new Borrow();
borrow.setUserId(userId);
borrow.setBookId(bookId);
borrow.setBorrowDate(LocalDate.now());
borrow.setDueDate(LocalDate.now().plusDays(30));
borrow.setStatus(1);
borrowMapper.insert(borrow);
// 4. 更新图书状态
book.setStatus(2);
bookMapper.updateById(book);
return new BorrowResult(borrow.getId(), book.getTitle(), borrow.getDueDate());
}
关键点:
- 使用@Transactional确保操作原子性
- 先查询后更新的模式要注意并发问题
- 日期计算使用Java 8的LocalDate避免时区问题
4.2 逾期提醒功能
通过Spring Scheduled实现每日凌晨1点检查逾期记录:
java复制@Scheduled(cron = "0 0 1 * * ?")
public void checkOverdue() {
LocalDate today = LocalDate.now();
List<Borrow> overdueList = borrowMapper.selectOverdueRecords(today);
overdueList.forEach(borrow -> {
User user = userMapper.selectById(borrow.getUserId());
Book book = bookMapper.selectById(borrow.getBookId());
// 实际项目中这里调用短信或邮件服务
log.info("提醒用户{}:图书《{}》已逾期,请尽快归还",
user.getRealName(), book.getTitle());
});
}
5. 安全防护方案
5.1 SQL注入防护
MyBatis使用#{}占位符可以有效防止SQL注入。绝对禁止这样写:
java复制@Select("select * from book where title like '%${title}%'") // 危险!
List<Book> searchByTitle(@Param("title") String title);
应该使用:
java复制@Select("select * from book where title like concat('%',#{title},'%')")
List<Book> searchByTitleSafe(@Param("title") String title);
5.2 接口权限控制
采用Spring Security + JWT的方案,核心配置如下:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/login").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()));
}
}
6. 部署实践
6.1 多环境配置
使用Spring Boot的profile功能管理不同环境配置:
code复制application.yml # 公共配置
application-dev.yml # 开发环境
application-test.yml # 测试环境
application-prod.yml # 生产环境
启动时通过--spring.profiles.active参数指定环境。
6.2 前端部署优化
Vite构建时配置chunk拆分策略:
javascript复制build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor'
}
}
}
}
}
这样生成的静态文件更适合长期缓存。实测部署后首屏加载时间从2.1s降至1.3s。
7. 常见问题排查
7.1 MyBatis映射问题
症状:查询返回的字段值为null
排查步骤:
- 检查数据库字段名与实体类属性名是否一致(注意下划线转驼峰)
- 确认resultMap或@Results注解配置正确
- 检查getter/setter方法是否存在
7.2 Vue响应式失效
症状:数据变化但视图不更新
解决方案:
- 对于数组,使用push()等变异方法而非直接索引赋值
- 对于对象,使用Vue.set()或展开运算符创建新对象
- 检查是否在data()中正确定义了响应式属性
7.3 跨域问题
后端解决方案(推荐):
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.maxAge(3600);
}
}
前端开发环境解决方案(vite.config.js):
javascript复制server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
8. 性能优化记录
8.1 数据库连接池调优
根据实际压测结果,最终采用的HikariCP配置:
yaml复制spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
idle-timeout: 30000
max-lifetime: 1800000
connection-timeout: 30000
这个配置在200并发下保持稳定,连接获取平均时间<50ms。
8.2 接口缓存策略
对不常变动的数据如图书分类,采用Redis缓存:
java复制@Cacheable(value = "categories", key = "#root.methodName")
public List<Category> getAllCategories() {
return categoryMapper.selectList(null);
}
缓存命中后,接口响应时间从120ms降至15ms。
9. 项目扩展方向
这套系统我已经在三个不同场景进行了扩展:
- 多校区版本:增加campus_id字段,配合ShardingSphere实现分库
- 微信小程序端:基于uni-app开发,与现有后端API兼容
- 大数据分析:将借阅记录同步到Hive,使用Superset可视化
最近正在尝试接入AI能力,比如基于借阅历史的智能推荐,使用协同过滤算法实现"猜你喜欢"功能。
