无人借阅系统全栈实战: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({

![配图](https://i-blog.csdnimg.cn/img_convert/d8aec06c3faad60d20ba19349fe3c9c0.png)
Logo

一站式 AI 云服务平台

更多推荐