1. 项目概述
这套前后端分离的图书管理系统是我去年为一个高校图书馆开发的实战项目,采用目前主流的SpringBoot+Vue技术栈。相比传统单体架构,前后端分离的设计让我们的开发效率提升了40%以上,特别是在应对频繁的需求变更时优势明显。
系统核心解决了三个痛点:
- 图书信息管理混乱:纸质登记易丢失,Excel管理难检索
- 借阅流程效率低下:人工核对耗时长,容易出错
- 数据统计困难:无法实时掌握图书流通情况
技术选型上,后端用SpringBoot 2.7 + MyBatis-Plus 3.5,前端用Vue 3 + Element Plus,数据库是MySQL 8.0。这套组合拳的优点是:
- SpringBoot的自动配置省去了大量XML配置
- MyBatis-Plus的代码生成器让基础CRUD开发时间缩短60%
- Vue 3的Composition API使组件复用更灵活
关键提示:在中小型管理系统开发中,这套技术栈的性价比极高。我们团队实测从零搭建到上线平均只需2-3周。
2. 系统架构设计
2.1 技术架构图解
code复制前端层(Vue.js)
│
├── 展示层(Element UI)
├── 状态管理(Pinia)
└── 网络请求(Axios)
↓ ↑
API网关层(SpringBoot)
│
├── 控制层(RESTful API)
├── 业务逻辑层(Service)
└── 数据访问层(MyBatis)
↓
数据存储层(MySQL)
2.2 数据库设计精要
2.2.1 图书表优化方案
原始设计的book_info表存在两个问题:
- book_status只有两种状态,实际需要扩展(如"维修中")
- 缺少图书封面字段
我的改进方案:
sql复制ALTER TABLE book_info
MODIFY COLUMN book_status TINYINT(4) NOT NULL COMMENT '0-可借阅 1-已借出 2-维修中 3-已下架',
ADD COLUMN cover_url VARCHAR(255) COMMENT '封面图URL';
2.2.2 索引设计心得
在user_info和borrow_record表上必须建立以下索引:
sql复制-- 用户账号唯一索引
CREATE UNIQUE INDEX idx_account ON user_info(user_account);
-- 借阅记录复合索引
CREATE INDEX idx_borrow_search ON borrow_record(user_id, borrow_status);
血泪教训:初期没加复合索引,当借阅记录超过10万条时,查询速度从200ms骤降到5s+。
3. 核心功能实现
3.1 JWT鉴权实战
采用JJWT库实现Token签发:
java复制// JWT工具类核心代码
public class JwtUtil {
private static final String SECRET = "your-256-bit-secret";
private static final long EXPIRATION = 86400000L; // 24小时
public static String generateToken(UserDetails user) {
return Jwts.builder()
.setSubject(user.getUsername())
.claim("roles", user.getAuthorities())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
}
// 验证方法省略...
}
前端axios拦截器配置:
javascript复制// request拦截器
axios.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
}, error => {
return Promise.reject(error);
});
3.2 借阅业务逻辑
借阅操作的核心Service实现:
java复制@Transactional
public BorrowResult borrowBook(Long userId, Long bookId) {
// 1. 校验用户资格
User user = userMapper.selectById(userId);
if (user == null) {
throw new BusinessException("用户不存在");
}
// 2. 检查图书状态
Book book = bookMapper.selectById(bookId);
if (book == null || book.getStatus() != BookStatus.AVAILABLE) {
return BorrowResult.failed("图书不可借");
}
// 3. 创建借阅记录
BorrowRecord record = new BorrowRecord();
record.setUserId(userId);
record.setBookId(bookId);
record.setBorrowTime(new Date());
record.setStatus(BorrowStatus.ON_LOAN);
borrowMapper.insert(record);
// 4. 更新图书状态
book.setStatus(BookStatus.BORROWED);
bookMapper.updateById(book);
return BorrowResult.success(record);
}
4. 部署实战指南
4.1 后端部署要点
- 打包时排除测试依赖:
xml复制<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
- 生产环境配置建议:
yaml复制# application-prod.yml
server:
port: 8080
compression:
enabled: true
mime-types: application/json,application/xml
spring:
datasource:
url: jdbc:mysql://localhost:3306/library?useSSL=false&serverTimezone=UTC
username: prod_user
password: ${DB_PASSWORD} # 从环境变量读取
redis:
host: redis-server
port: 6379
4.2 前端优化技巧
- 路由懒加载配置:
javascript复制const routes = [
{
path: '/books',
component: () => import('../views/BookList.vue')
},
// 其他路由...
]
- 生产环境打包命令:
bash复制# 安装依赖时使用--production模式
npm install --production
# 构建生产包
vue-cli-service build --modern
5. 踩坑实录与解决方案
5.1 MyBatis分页缓存问题
现象:使用PageHelper分页时,第二次查询返回结果与第一次相同。
解决方案:
java复制// 在Mapper接口方法前添加清除缓存注解
@Options(flushCache = Options.FlushCachePolicy.TRUE)
List<Book> selectByCondition(BookQuery query);
5.2 Vue响应式丢失问题
当直接通过索引修改数组元素时:
javascript复制// 错误做法
this.books[0] = newBook; // 不会触发视图更新
// 正确做法
this.$set(this.books, 0, newBook);
// 或使用新数组
this.books = [...this.books.slice(0,0), newBook, ...this.books.slice(1)];
5.3 跨域会话保持
开发环境常见跨域问题解决方案:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8081")
.allowCredentials(true) // 关键配置
.allowedMethods("*")
.maxAge(3600);
}
}
6. 性能优化实践
6.1 接口响应优化
- 启用Gzip压缩:
java复制@Configuration
public class CompressionConfig {
@Bean
public FilterRegistrationBean<GzipFilter> gzipFilter() {
FilterRegistrationBean<GzipFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new GzipFilter());
registration.addUrlPatterns("/*");
return registration;
}
}
- 添加缓存注解:
java复制@Cacheable(value = "books", key = "#id")
public Book getBookById(Long id) {
return bookMapper.selectById(id);
}
6.2 前端懒加载策略
- 图片懒加载:
html复制<img v-lazy="book.coverUrl" alt="图书封面">
- 组件动态导入:
javascript复制const BookDetail = defineAsyncComponent(() =>
import('./components/BookDetail.vue')
)
7. 扩展功能建议
7.1 扫码借阅功能
集成ZXing实现图书二维码扫描:
java复制// 后端生成二维码
@GetMapping("/books/{id}/qrcode")
public void generateQRCode(@PathVariable Long id, HttpServletResponse response) throws Exception {
Book book = bookService.getById(id);
String content = "BOOK:" + book.getId();
QRCodeWriter writer = new QRCodeWriter();
BitMatrix matrix = writer.encode(content, BarcodeFormat.QR_CODE, 200, 200);
response.setContentType("image/png");
MatrixToImageWriter.writeToStream(matrix, "PNG", response.getOutputStream());
}
7.2 逾期提醒服务
定时任务实现:
java复制@Scheduled(cron = "0 0 9 * * ?") // 每天上午9点执行
public void checkOverdueBooks() {
List<BorrowRecord> overdueRecords = borrowMapper.selectOverdueRecords();
overdueRecords.forEach(record -> {
String message = String.format("您借阅的书籍《%s》已逾期,请尽快归还",
bookService.getById(record.getBookId()).getName());
smsService.send(record.getUser().getPhone(), message);
});
}
8. 项目二次开发建议
- 添加Elasticsearch搜索支持:
java复制// 在Book实体类添加注解
@Document(indexName = "books")
public class Book {
@Id
private Long id;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String name;
// 其他字段...
}
- 接入第三方登录:
java复制// OAuth2配置示例
@Configuration
@EnableWebSecurity
public class OAuth2Config extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login()
.userInfoEndpoint()
.userService(customOAuth2UserService);
}
}
这套系统经过三个月的实际运行检验,日均处理借阅操作300+次,最关键的借阅接口平均响应时间稳定在150ms以内。最大的收获是验证了前后端分离架构在管理类系统中的优势 - 当图书馆提出新增移动端需求时,我们仅用2天就完成了API适配和移动端开发。