无人借阅系统全栈实战:Spring Boot + UniApp 构建无人值守借阅平台
无人借阅系统全栈实战:Spring Boot + UniApp 构建无人值守借阅平台
无人借阅系统是一套通过身份认证、/RFID 识别、自动门禁和后台服务实现 24 小时无人值守借还书的软件系统。它的核心不是硬件堆叠,而是借阅状态机、库存一致性和多端协同。本文从业务流程、后端架构、跨端实现和部署要点四个角度,拆解一套可二次开发的无人借阅系统如何落地,技术栈以 Spring Boot + MyBatis Plus + MySQL 为后端,UniApp(Vue 语法)为用户端,Vue + Element UI 为管理后台,适配 H5、APP 和小程序。
一、无人借阅系统的核心业务流程与状态设计
无人借阅场景下,用户通常经历“身份核验 → 扫码/刷卡 → 借书 → 归还 → 逾期处理”几个阶段。系统必须把每一步映射为可追踪的状态,否则会出现“书被拿走了但系统仍显示在架”的脏数据。
一个小可用的借阅状态机可以这样设计:
- 图书状态:在架、借出、预约、下架、遗失。
- 借阅记录状态:借出、已还、逾期、续借。
- 用户状态:正常、冻结、黑名单。
借书时,后端需要同时完成三件事:校验用户资格、扣减图书库存、写入借阅记录。这三步必须在一个事务内完成,否则会出现库存扣了但记录没写,或者记录写了但库存没扣。还书时则相反:增加库存、更新借阅记录为已还、计算是否逾期。
状态流转建议集中在服务层,不要散落在 Controller 里。这样后续做二次开发时,无论是增加“预约取书”还是“信用借阅”,都只需要扩展状态机,而不是重写业务。
二、后端实现:Spring Boot + MyBatis Plus 的借阅与库存一致性
后端采用 Spring Boot + MyBatis Plus + MySQL 是常见组合,原因是开发效率高、生态成熟、便于二次开发。下面给出关键代码思路。
先定义借阅记录实体:
@TableName("borrow_record")
public class BorrowRecord {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private Long bookId;
private LocalDateTime borrowTime;
private LocalDateTime dueTime;
private LocalDateTime returnTime;
private Integer status; // 0 借出 1 已还 2 逾期
}
借书服务需要处理并发。仅靠 stock > 0 查询再更新,在高并发下会超卖。推荐使用乐观锁或条件更新:
@Transactional(rollbackFor = Exception.class)
public BorrowResult borrow(Long userId, Long bookId) {
Book book = bookMapper.selectById(bookId);
if (book == null || book.getStock() <= 0) {
return BorrowResult.fail("库存不足");
}
int updated = bookMapper.decreaseStock(bookId, book.getVersion());
if (updated == 0) {
return BorrowResult.fail("并发冲突,请重试");
}
BorrowRecord record = new BorrowRecord();
record.setUserId(userId);
record.setBookId(bookId);
record.setBorrowTime(LocalDateTime.now());
record.setDueTime(LocalDateTime.now().plusDays(30));
record.setStatus(0);
borrowRecordMapper.insert(record);
return BorrowResult.success(record.getId());
}
对应的 Mapper SQL:
UPDATE book
SET stock = stock - 1, version = version + 1
WHERE id = #{bookId}
AND stock > 0
AND version = #{version};
除了库存,还要防止同一用户重复借同一本书。可以在业务层先查询“未归还记录”,再配合数据库索引做兜底。MyBatis Plus 的 LambdaQueryWrapper 很适合这种查询:
Long count = borrowRecordMapper.selectCount(
new LambdaQueryWrapper<BorrowRecord>()
.eq(BorrowRecord::getUserId, userId)
.eq(BorrowRecord::getBookId, bookId)
.in(BorrowRecord::getStatus, 0, 2)
);
if (count > 0) {
return BorrowResult.fail("该图书尚未归还");
}
逾期处理通常用定时任务扫描 due_time < now() 且状态为借出的记录,批量更新为逾期,并触发站内通知。定时任务建议独立线程池,避免影响借阅主流程。
三、多端实践:UniApp 用户端与 Vue + Element UI 管理后台
用户端采用 UniApp(Vue 语法)可以一套代码编译到 H5、APP 和小程序,降低多端维护成本。无人借阅中常用的能力是扫码。UniApp 提供 uni.scanCode,但不同平台存在差异,可以用条件编译处理:
function scanBookCode() {
// #ifdef MP-WEIXIN
.scanCode({
success: res => {
submitBorrow(res.result);
}
});
// #endif
// #ifdef H5
// H5 可调用摄像头或提供手动输入入口
const code = prompt("请输入图书编码");
if (code) submitBorrow(code);
// #endif
// #ifdef APP-PLUS
uni.scanCode({

更多推荐


所有评论(0)