1. 为什么做无人图书借阅系统
1.1 传统图书借阅模式的痛点
先讲个真实的场景。前两年我参与过一次校园图书馆的改造项目,当时图书馆的管理员跟我倒了一肚子苦水:每天大量的借还书操作都靠人工扫码,高峰期排队能排到门口;闭馆之后读者没法还书,第二天开馆门口堆着一沓书;更麻烦的是图书盘点,人工清点几千册书要花整整两天,还经常对不上账。
这类问题在中小型图书馆、企业图书角、社区阅读站里特别典型。要么养一个专职管理员成本太高,要么开放时间有限导致借阅率上不去。无人图书借阅系统要解决的,本质上就是这三件事:把借还书的操作从人工变成自助,把图书的在架状态从"靠人记忆"变成实时可查,把借阅规则的执行从"靠管理员提醒"变成系统强制约束。
1.2 系统的目标与设计边界
做这个系统之前,我给自己划了几条边界。第一,不做纯硬件方案。市面上成熟的无人图书馆大多依赖RFID门禁、自助借还机等专用设备,成本动辄十几万,不适合个人项目或小场景。第二,要做成真正能落地的东西,而不是demo。所谓落地,指的是读者端能完成借书、还书、续借、预约,管理员端能处理违规、补录、盘点,两个角色都能通过简单的Web界面操作。
第三,也是最重要的一点,系统要能够应对无人值守的场景。什么意思呢?就是读者自己操作的时候可能会误操作、可能会赖账、可能会借了书不还。用户身份怎么验证、图书怎么识别、违规怎么处理,这些不能靠"自觉",得靠流程设计硬性约束。整个系统的核心价值,不在界面多好看,而在借阅流程设计的严谨性。
这篇文章里的源码,我按Spring Boot 2.7 + MyBatis Plus + MySQL 8.0 + Redis的方案实现,前端用Thymeleaf模板引擎做管理端,自助终端页面用纯HTML + Vue 3 CDN方式嵌入,方便在触屏设备上运行。整套代码我放到Gitee仓库里,后面会讲怎么从零启动。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体架构与关键技术选型
2.1 技术选型:为什么是这组搭配
先说后端。Spring Boot是Java后端项目的事实标准,这一点没什么争议。搭配MyBatis Plus是为了减少单表CRUD的样板代码,图书信息、借阅记录、用户表这些基础操作,用MyBatis Plus的BaseMapper直接继承就能拿到现成的增删改查,能省下大概三分之一的数据层代码。为什么不用Spring Data JPA?我个人的偏好是JPA在复杂查询和SQL调优时不够直观,MyBatis的XML里写SQL可以精细控制每一行查询逻辑,对初学者也更友好——至少SQL执行计划看得清楚,不像JPA那样在Hibernate生成的SQL里绕来绕去。
Redis在系统里承担的任务很明确:缓存高频读取的图书状态,以及处理图书的临时占用锁。你可能会问,图书借阅这种低频操作为什么要用分布式锁?道理很简单,两个读者同时扫同一本书的条码,理论上系统应该只允许一个人借走。如果直接查数据库改状态,在高并发下可能出现两个事务都读到"图书在馆",然后都放行——这就是经典的丢失更新问题。Redis的SETNX可以实现一个轻量级的图书借阅锁,保证同一本书在同一时刻只有一个借书请求能进入后续流程。
前端为什么没有用重型框架?因为自助终端的页面逻辑并不复杂,主要就是展示在架图书、提供搜索、处理借还流程。Vue 3用CDN方式引入,不需要Node.js构建链,部署时只要把HTML文件丢到静态目录里就行。对小型项目来说,少一套前端工程化链路,就意味着少一层维护成本。
2.2 模块划分:清晰到能直接找到代码
源码的包结构我按业务域拆,而不是按技术层拆,这是项目可维护性的关键。很多人新手写Spring Boot项目习惯按controller、service、mapper三层建包,结果业务逻辑分散在各个service里,改一个借书需求要动七八个类。我采用的做法是这样的:
text复制com.library.unmanned
├── common // 通用类:统一返回结果、异常处理、常量定义
├── config // 配置类:Redis、MyBatis Plus、拦截器、WebMvc
├── controller // 接口层:管理员端、自助终端端、读者端
├── service // 业务层:接口 + 实现类
├── mapper // 数据访问层:MyBatis Plus的Mapper接口
├── entity // 实体类:与数据库表对应
├── dto // 数据传输对象:接收前端参数,避免实体直接暴露
├── vo // 视图对象:返回给前端的数据结构
├── enums // 枚举:借阅状态、图书状态、操作类型等
├── mq // 消息队列:异步处理还书通知、逾期提醒
└── utils // 工具类:日期计算、条码生成、RFID解析
其中enums这个包很少有人单独提,但它对业务正确性的影响比想象中大。图书状态、借阅状态、操作类型这些如果散落在代码里用魔法数字表示,调试的时候会非常痛苦。我专门定义了一个BorrowStatusEnum,把"借出中、已归还、逾期未还、已预约"这些状态全部收敛起来,任何地方要用状态判断都从这里取,避免出现"1代表什么、2代表什么"这种记忆负担。
2.3 自助终端的设备对接方案
无人图书借阅系统的自助终端,硬件上一般由三部分组成:触屏显示设备、扫码枪/条码扫描器、身份证/校园卡读卡器。这套源码里我抽象了DeviceAdapter接口:
java复制public interface DeviceAdapter {
// 识别读者身份信息,返回读者编号
ReaderInfo identifyReader() throws DeviceException;
// 扫描图书条码,返回图书编号
String scanBook() throws DeviceException;
// 门禁放行控制(可选)
void openG
