在线图书借阅平台设计与实现
在线图书借阅平台基于 Spring Boot、MyBatis-Plus 和 MySQL 构建,实现了查书、借书、续借、预约全流程线上化。系统包含用户管理、图书管理、借阅记录及超期提醒等核心功能。后端采用 Java 语言,前端使用 Thymeleaf 结合 Bootstrap 实现响应式布局。文章详细展示了实体类、数据传输对象、业务逻辑层代码以及关键页面的 HTML 结构,并涵盖了密码加密、库存校验及调试过程中的问题解决方案。

在线图书借阅平台基于 Spring Boot、MyBatis-Plus 和 MySQL 构建,实现了查书、借书、续借、预约全流程线上化。系统包含用户管理、图书管理、借阅记录及超期提醒等核心功能。后端采用 Java 语言,前端使用 Thymeleaf 结合 Bootstrap 实现响应式布局。文章详细展示了实体类、数据传输对象、业务逻辑层代码以及关键页面的 HTML 结构,并涵盖了密码加密、库存校验及调试过程中的问题解决方案。

本系统旨在实现图书馆借阅流程线上化,涵盖查书、借书、续借、预约全流程,并通过到期提醒避免超期。针对传统借阅流程繁琐的问题,设计了一款轻量化在线图书借阅平台,既能实现全流程线上化,又能通过到期提醒避免超期。
选择 IDEA 社区版(免费且功能满足需求)。在 JetBrains 官网下载 Windows 安装包。 安装时注意勾选'Add launchers dir to the PATH'(添加到环境变量)及'Create Desktop Shortcut'(创建桌面快捷方式)。
系统包含 3 类核心实体:用户(区分学生/管理员角色)、图书、借阅记录。实现核心功能:注册/登录、图书查询、借阅/续借/归还、超期提醒、管理员管理。技术栈:Spring Boot 3.x + MyBatis-Plus + MySQL 8.0,前端用 Thymeleaf+Bootstrap。
项目结构清晰,核心包与类已完整创建:
com.student.library
├─ entity // 实体类(映射数据库表)
│ ├─ User.java // 用户实体(学生/管理员)
│ ├─ Book.java // 图书实体
│ ├─ BorrowRecord.java // 借阅记录实体
│ └─ Category.java // 图书分类实体
├─ dto // 数据传输对象(接收前端请求参数)
│ ├─ UserRegisterDTO.java // 注册请求 DTO
│ ├─ BookAddDTO.java // 图书添加 DTO
│ ├─ BorrowBookDTO.java // 图书借阅 DTO
│ └─ BookQueryDTO.java // 图书查询 DTO
├─ vo // 视图对象(向前端返回数据)
│ ├─ BookDetailVO.java // 图书详情 VO
│ ├─ BorrowRecordVO.java // 借阅记录 VO
│ └─ UserBorrowVO.java // 学生借阅列表 VO
├─ mapper // 数据访问接口(MyBatis-Plus)
│ ├─ UserMapper.java
│ ├─ BookMapper.java
│ └─ BorrowRecordMapper.java
├─ service // 业务逻辑层
│ ├─ UserService.java
│ ├─ BookService.java
│ └─ BorrowService.java
├─ controller // 接口控制层
│ ├─ UserController.java
│ ├─ BookController.java
│ └─ BorrowController.java
└─ config // 配置类(数据库、静态资源映射)
└─ WebConfig.java
User.java(用户实体,区分学生/管理员角色)
package com.student.library.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* <p>
* 用户实体类:映射 user 表,区分学生(role=0)和管理员(role=1)角色
* </p>
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User {
/**
* 用户 ID:主键,自增
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 账号:学生填学号,管理员填工号,唯一非空
*/
private String account;
/**
* 密码:存储 BCrypt 加密后的字符串
*/
private String password;
/**
* 姓名:用户真实姓名
*/
private String name;
/**
* 角色:0=学生,1=管理员(控制权限,学生不能添加图书)
*/
private Integer role;
/**
* 班级:仅学生有值,格式如'2024-计算机 1 班'
*/
private String className;
/**
* 邮箱:用于接收超期提醒、密码找回
*/
private String email;
/**
* 创建时间:用户注册时间,自动填充
*/
private LocalDateTime createTime;
}
Book.java(图书实体,含馆藏与可借数量)
package com.student.library.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* <p>
* 图书实体类:映射 book 表,记录图书基本信息与库存状态
* </p>
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("book")
public class Book {
/**
* 图书编号:主键,自增
*/
@TableId(type = IdType.AUTO)
private Long bookNo;
/**
* 书名:非空
*/
private String bookName;
/**
* 作者:非空
*/
private String author;
/**
* 图书分类:关联 category 表
*/
private Long categoryId;
/**
* 出版社:图书出版机构
*/
private String publisher;
/**
* 出版日期:图书出版时间
*/
private LocalDateTime publishDate;
/**
* 馆藏总数:图书馆拥有的该图书总数量
*/
private Integer totalCount;
/**
* 剩余可借数量:totalCount - 已借出数量
*/
private Integer availableCount;
/**
* 图书简介:描述图书内容
*/
private String description;
}
BorrowBookDTO.java(学生借阅图书的请求 DTO)
package com.student.library.dto;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* <p>
* 图书借阅请求 DTO:接收学生借阅时的参数,含参数校验
* </p>
*/
@Data
public class BorrowBookDTO {
/**
* 学生 ID:必填
*/
@NotNull(message = "学生 ID 不能为空")
private Long studentId;
/**
* 图书编号:必填
*/
@NotNull(message = "图书编号不能为空")
private Long bookNo;
/**
* 借阅天数:必填,默认 30 天
*/
@NotNull(message = "借阅天数不能为空")
private Integer borrowDays = 30;
}
BookDetailVO.java(图书详情 VO,给学生展示)
package com.student.library.vo;
import lombok.Data;
import java.time.LocalDateTime;
/**
* <p>
* 图书详情 VO:封装学生查看图书时需要的信息,隐藏敏感字段
* </p>
*/
@Data
public class BookDetailVO {
private Long bookNo;
private String bookName;
private String author;
private String categoryName;
private String publisher;
private LocalDateTime publishDate;
private Integer availableCount;
private String description;
private Boolean canBorrow;
}
BorrowServiceImpl.java(借阅服务实现类,含借阅/续借逻辑)
package com.student.library.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.student.library.dto.BorrowBookDTO;
import com.student.library.entity.Book;
import com.student.library.entity.BorrowRecord;
import com.student.library.entity.User;
import com.student.library.mapper.BookMapper;
import com.student.library.mapper.BorrowRecordMapper;
import com.student.library.mapper.UserMapper;
import com.student.library.service.BorrowService;
import com.student.library.vo.BorrowRecordVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.stream.Collectors;
/**
* <p>
* 借阅服务实现类:处理图书借阅、续借、归还等核心业务
* </p>
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class BorrowServiceImpl extends ServiceImpl<BorrowRecordMapper, BorrowRecord> implements BorrowService {
private final BorrowRecordMapper borrowRecordMapper;
private final BookMapper bookMapper;
private final UserMapper userMapper;
/**
* 学生借阅图书(核心逻辑:校验库存、计算到期时间)
*/
@Override
@Transactional
public BorrowRecord borrowBook(BorrowBookDTO borrowBookDTO) {
log.info("学生借阅图书:学生 ID={}, 图书编号={}, 借阅天数={}",
borrowBookDTO.getStudentId(), borrowBookDTO.getBookNo(), borrowBookDTO.getBorrowDays());
// 1. 校验学生身份
User student = userMapper.selectById(borrowBookDTO.getStudentId());
if (student == null || student.getRole() != 0) {
throw new RuntimeException("无效的学生账号,无法借阅图书");
}
// 2. 校验图书状态
Book book = bookMapper.selectById(borrowBookDTO.getBookNo());
if (book == null) {
throw new RuntimeException("该图书不存在");
}
if (book.getAvailableCount() <= 0) {
throw new RuntimeException("该图书已无库存,可尝试预约");
}
// 3. 生成借阅记录
BorrowRecord borrowRecord = new BorrowRecord();
borrowRecord.setStudentId(borrowBookDTO.getStudentId());
borrowRecord.setBookNo(borrowBookDTO.getBookNo());
borrowRecord.setBorrowDate(LocalDate.now());
borrowRecord.setDueDate(LocalDate.now().plusDays(borrowBookDTO.getBorrowDays()));
borrowRecord.setReturnStatus(0);
// 4. 更新图书剩余可借数量
book.setAvailableCount(book.getAvailableCount() - 1);
bookMapper.updateById(book);
save(borrowRecord);
return borrowRecord;
}
/**
* 学生续借图书(核心逻辑:校验是否超期、限制续借次数)
*/
@Override
@Transactional
public BorrowRecord renewBook(Long recordId) {
log.info("学生续借图书:借阅记录 ID={}", recordId);
BorrowRecord record = getById(recordId);
if (record == null) {
throw new RuntimeException("借阅记录不存在");
}
if (record.getReturnStatus() == 1) {
throw new RuntimeException("该图书已归还,无需续借");
}
if (LocalDate.now().isAfter(record.getDueDate())) {
throw new RuntimeException("图书已超期,请先归还并处理罚款");
}
if (record.getRenewCount() >= 1) {
throw new RuntimeException("该图书已续借 1 次,无法再次续借");
}
record.setDueDate(record.getDueDate().plusDays(30));
record.setRenewCount(record.getRenewCount() + 1);
updateById(record);
return record;
}
/**
* 查询学生的借阅记录(含超期提醒)
*/
@Override
public List<BorrowRecordVO> getStudentBorrowRecords(Long studentId) {
List<BorrowRecord> recordList = lambdaQuery()
.eq(BorrowRecord::getStudentId, studentId)
.eq(BorrowRecord::getReturnStatus, 0)
.list();
return recordList.stream().map(record -> {
BorrowRecordVO vo = new BorrowRecordVO();
BeanUtils.copyProperties(record, vo);
Book book = bookMapper.selectById(record.getBookNo());
vo.setBookName(book.getBookName());
vo.setAuthor(book.getAuthor());
long daysLeft = ChronoUnit.DAYS.between(LocalDate.now(), record.getDueDate());
vo.setDaysLeft((int) daysLeft);
if (daysLeft < 0) {
vo.setOverdueReminder("已超期" + (-daysLeft) + "天,请尽快归还");
} else if (daysLeft <= 3) {
vo.setOverdueReminder("即将超期,剩余" + daysLeft + "天");
} else {
vo.setOverdueReminder("正常借阅中");
}
return vo;
}).collect(Collectors.toList());
}
}
前端采用 Bootstrap 实现响应式布局,界面简洁清晰。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图书查询 - 校园图书馆系统</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.category-item { transition: all 0.2s; }
.category-item:hover, .category-item.active { background-color: #0d6efd; color: white !important; }
.book-card { transition: transform 0.3s, box-shadow 0.3s; }
.book-card:hover { transform: translateY(-5px); box-shadow: 0 10px 20px rgba(0,0,0,0.1); }
.no-stock { position: absolute; top: 10px; right: 10px; }
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary mb-4">
<div class="container">
<a class="navbar-brand" href="#"><i class="fa fa-book mr-2"></i>校园图书馆</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item"><a class="nav-link active" href="book-search.html">图书查询</a></li>
<li class="nav-item"><a class="nav-link" href="borrowing-record.html">借阅记录</a></li>
<li class="nav-item"><a class="nav-link" href="#">个人中心</a></li>
</ul>
</div>
</div>
</nav>
<div class="container">
<h3 class="mb-4">图书查询</h3>
<!-- 搜索栏 -->
<div class="row mb-4">
<div class="col-md-8 offset-md-2">
<div class="input-group">
<input type="text" class="form-control" placeholder="请输入书名或作者进行搜索..." id="searchInput">
<button class="btn btn-primary" type="button" id="searchBtn"><i class="fa fa-search mr-1"></i>搜索</button>
</div>
</div>
</div>
<div class="row">
<!-- 分类筛选栏 -->
<div class="col-md-3 mb-4">
<div class="card">
<div class="card-header bg-light"><h5 class="mb-0">图书分类</h5></div>
<div class="list-group list-group-flush">
<a href="#" class="list-group-item list-group-item-action category-item active">全部图书</a>
<a href="#" class="list-group-item list-group-item-action category-item">计算机类</a>
<a href="#" class="list-group-item list-group-item-action category-item">文学类</a>
</div>
</div>
</div>
<!-- 图书列表 -->
<div class="col-md-9">
<div class="row">
<div class="col-md-4 mb-4">
<div class="card book-card h-100">
<img src="https://picsum.photos/id/24/300/400" class="card-img-top" alt="Java 编程思想">
<div class="card-body">
<h5 class="card-title">Java 编程思想</h5>
<p class="card-text text-muted">作者:Bruce Eckel</p>
<p class="card-text"><small>剩余可借:<span class="text-success">5</span>/10</small></p>
<a href="#" class="btn btn-primary btn-sm w-100">查看详情</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>借阅记录 - 校园图书馆系统</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.overdue-alert { border-left: 4px solid #dc3545; }
.soon-expire { color: #dc3545; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<h3 class="mb-4">借阅记录</h3>
<!-- 超期提醒 -->
<div class="alert alert-danger overdue-alert mb-4">
<h5 class="alert-heading"><i class="fa fa-exclamation-circle mr-2"></i>超期提醒</h5>
<p>您有 1 本图书已超期未还,请尽快归还。</p>
</div>
<!-- 借阅记录表格 -->
<div class="card">
<div class="card-body">
<table class="table table-hover">
<thead class="table-light">
<tr><th>图书名称</th><th>作者</th><th>借阅时间</th><th>到期时间</th><th>剩余天数</th><th>操作</th></tr>
</thead>
<tbody>
<tr>
<td>Java 编程思想</td><td>Bruce Eckel</td><td>2023-05-10</td><td>2023-06-10</td>
<td class="soon-expire">2 天 <span class="badge bg-danger">即将超期</span></td>
<td><button class="btn btn-sm btn-primary">续借</button></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
bCryptPasswordEncoder.matches() 校验。bookMapper.updateById(book)。解决:补充该代码。本系统实现了基于 Spring Boot 的在线图书借阅平台,涵盖了用户管理、图书管理及借阅记录等核心功能。后端采用 Java 语言构建,前端使用 Thymeleaf 结合 Bootstrap 实现响应式布局。文章详细展示了实体类、数据传输对象、业务逻辑层代码以及关键页面的 HTML 结构,并涵盖了密码加密、库存校验及调试过程中的问题解决方案。后续可进一步扩展图书预约及罚款对接功能。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online