在实际企业级应用开发中,图书管理系统是一个经典且综合性的练手项目,它几乎涵盖了Web开发的所有核心模块:用户认证、权限管理、数据增删改查、业务逻辑处理以及报表统计。对于Java开发者而言,使用SpringBoot作为后端框架可以快速搭建项目骨架,而选择一个成熟的后台管理框架则能极大提升开发效率,避免重复造轮子。若依(RuoYi)正是这样一个基于SpringBoot的权限管理系统,它提供了用户、角色、菜单、部门等基础管理功能,并支持代码生成器,是快速开发后台管理系统的利器。
本文将带你从零开始,基于SpringBoot和若依框架,构建一个功能完整的图书管理系统。我们将不仅实现图书的增删改查,还会深入集成若依的权限体系,实现不同角色(如管理员、图书管理员、普通读者)对图书借阅、归还、查询等操作的不同访问权限。文章会详细解释每一步的配置、代码编写和背后的设计逻辑,确保你不仅能跑通项目,更能理解如何在一个成熟框架上进行二次开发,应对实际项目中可能遇到的环境、配置、权限和数据一致性等问题。
1. 理解技术栈选型:为什么是SpringBoot + 若依
在开始编码之前,明确技术栈的选型理由至关重要。这决定了项目的技术边界、开发效率和未来的维护成本。
1.1 SpringBoot:简化配置的Java后端基石
SpringBoot的核心价值在于“约定大于配置”。它通过自动配置和起步依赖,将开发者从繁琐的XML配置和复杂的依赖管理中解放出来。对于一个图书管理系统,我们至少需要Web MVC、数据持久化、事务管理和安全控制。手动整合Spring MVC、MyBatis、Spring Security会耗费大量时间在配置文件和版本兼容性上。SpringBoot的spring-boot-starter-web、spring-boot-starter-data-jdbc或mybatis-spring-boot-starter等依赖可以一键引入所有必要组件,并提供了合理的默认配置。
例如,数据库连接池、Tomcat内嵌服务器、JSON序列化器都已默认集成。开发者只需在application.yml中填写数据库地址、用户名和密码,即可连接数据库。这使得我们可以将精力集中于业务逻辑,而非基础设施搭建。
1.2 若依框架:快速搭建后台管理系统的脚手架
若依是一个开源的企业级快速开发平台,其核心是基于SpringBoot、Shiro(或Spring Security)、MyBatis-Plus、Vue等技术的后台权限管理系统。对于图书管理系统,我们最需要的是它已经实现好的几大模块:
- 用户权限体系:完整的用户、角色、菜单管理,支持基于角色的访问控制(RBAC)。图书管理系统中,管理员可以管理所有功能,图书管理员只能管理图书和借阅记录,普通读者只能查询和借阅图书,这套权限模型可以直接复用。
- 代码生成器:根据数据库表结构,自动生成实体类、Mapper、Service、Controller以及前端Vue页面代码。对于图书、借阅记录等标准CRUD模块,可以极大减少重复劳动。
- 内置工具组件:如数据字典、操作日志、定时任务、系统监控等。图书管理系统可能需要记录操作日志(谁在何时借阅/归还了哪本书),或者定时任务(如自动计算逾期罚款),这些功能若依都已提供基础实现。
选择若依,意味着我们站在了一个功能完备的起点上,只需专注于图书业务领域的定制开发。
1.3 数据库:MySQL的稳定与通用性
MySQL作为最流行的开源关系型数据库之一,其稳定性、性能和丰富的社区生态使其成为此类管理系统的首选。图书管理系统的数据关系明确(图书、读者、借阅记录),适合用关系模型来设计。SpringBoot通过JDBC或MyBatis可以非常方便地与之集成。若依框架默认也使用MySQL,其代码生成器和SQL脚本都是为MySQL语法设计的,兼容性最好。
2. 环境准备与项目初始化
在开始编码前,确保你的本地开发环境已经就绪。这是后续所有步骤的基础,环境问题往往是新手遇到的第一个障碍。
2.1 基础环境检查清单
请按顺序检查并安装以下软件,并确认版本号。版本不匹配是许多奇怪错误的根源。
| 软件/工具 | 推荐版本 | 检查命令 | 作用说明 |
|---|---|---|---|
| Java JDK | 1.8 或 11 (LTS版本) | java -version | SpringBoot 2.x 对 JDK 8 兼容性最好。若依4.x版本通常要求JDK 8+。 |
| Maven | 3.6+ | mvn -v | 用于管理项目依赖、构建和打包。 |
| MySQL | 5.7 或 8.0 | mysql --version | 项目数据库。确保服务已启动。 |
| IDE | IntelliJ IDEA 或 Eclipse | - | 推荐使用IDEA,对SpringBoot支持更友好。 |
| Git | 最新版 | git --version | 用于克隆若依项目源码。 |
| Node.js | 14+ | node -vnpm -v | 若依是前后端分离项目,前端部分需要Node环境运行。 |
注意:请务必使用命令行执行检查命令,确认输出信息与预期版本一致,而不仅仅是安装了软件。例如,
java -version应显示类似“java version “1.8.0_301””的信息。
2.2 获取若依项目骨架
若依提供了多种版本(单体、前后端分离、微服务)。对于图书管理系统,我们选择经典的前后端分离版本,后端基于SpringBoot,前端基于Vue。这样结构清晰,也便于理解现代Web应用架构。
克隆后端项目: 打开命令行工具,进入你准备存放项目的目录,执行以下命令克隆若依的后端代码。
git clone https://gitee.com/y_project/RuoYi-Vue.git cd RuoYi-Vue克隆完成后,目录结构应包含
ruoyi-admin(主模块)、ruoyi-common(通用模块)、ruoyi-framework(框架核心)、ruoyi-system(系统模块)等。导入IDE: 使用IntelliJ IDEA打开克隆下来的项目根目录(包含
pom.xml的文件夹)。IDEA会自动识别为Maven项目并开始下载依赖。这个过程取决于网络速度,可能需要几分钟。请耐心等待控制台日志结束,确保没有“BUILD FAILURE”错误。初始化数据库:
- 使用MySQL客户端(如命令行、Navicat、MySQL Workbench)连接你的MySQL服务。
- 创建一个新的数据库,例如命名为
ry_book(字符集建议utf8mb4,排序规则utf8mb4_general_ci)。
CREATE DATABASE IF NOT EXISTS `ry_book` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;- 在若依项目的
/sql目录下,找到对应的SQL脚本文件(如quartz.sql和ry_2023xxxx.sql,具体文件名可能随版本变化)。在ry_book数据库中依次执行这两个SQL文件,创建若依系统所需的基础表和数据。
2.3 配置后端连接数据库
若依的数据库配置位于后端模块的配置文件中。我们需要修改它,使其指向我们刚创建的ry_book数据库。
找到配置文件:
ruoyi-admin/src/main/resources/application-druid.yml。这是数据源专用配置。修改
url、username和password为你本地MySQL的环境信息。# 数据源配置 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ry_book?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root # 改为你的MySQL用户名 password: 123456 # 改为你的MySQL密码关键解释:
serverTimezone=GMT%2B8设置了东八区时区,避免时间相关字段出错。useSSL=true在本地开发环境有时会因证书问题导致连接失败,如果遇到,可尝试改为false,但生产环境建议使用SSL。验证后端启动: 找到启动类
RuoYiApplication(通常在ruoyi-admin/src/main/java/com/ruoyi目录下),右键运行。观察控制台日志,如果没有报错,并看到类似“Started RuoYiApplication in X.XXX seconds”的日志,且最后一行是“RuoYi启动成功”,说明后端服务启动成功。默认端口是8080,你可以访问http://localhost:8080,但此时由于前端未启动,可能只看到一些JSON接口信息或空白页,这是正常的。
3. 设计图书管理系统的核心数据模型
在若依已有的用户、角色、菜单等系统表基础上,我们需要设计图书业务相关的表。清晰的数据模型是业务逻辑正确实现的基础。
3.1 核心业务表设计
我们至少需要三张核心表:book(图书)、reader(读者)、borrow_record(借阅记录)。这里给出一个简化的DDL设计,你可以在MySQL中执行这些SQL语句来创建表。
-- 图书表 CREATE TABLE `book` ( `book_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '图书ID', `book_isbn` varchar(50) NOT NULL COMMENT 'ISBN编号', `book_name` varchar(255) NOT NULL COMMENT '图书名称', `book_author` varchar(100) DEFAULT NULL COMMENT '作者', `book_publisher` varchar(100) DEFAULT NULL COMMENT '出版社', `book_publish_date` date DEFAULT NULL COMMENT '出版日期', `book_price` decimal(10,2) DEFAULT NULL COMMENT '价格', `book_stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存数量', `book_status` char(1) DEFAULT '0' COMMENT '状态(0可借 1借出 2下架)', `create_by` varchar(64) DEFAULT '' COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) DEFAULT '' COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`book_id`), UNIQUE KEY `uni_isbn` (`book_isbn`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图书信息表'; -- 读者表 (可以与若依的sys_user表关联,这里为简化独立设计) CREATE TABLE `reader` ( `reader_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '读者ID', `reader_card` varchar(50) NOT NULL COMMENT '借书证号', `reader_name` varchar(50) NOT NULL COMMENT '读者姓名', `reader_gender` char(1) DEFAULT '0' COMMENT '性别(0男 1女 2未知)', `reader_phone` varchar(20) DEFAULT NULL COMMENT '手机号码', `reader_max_borrow` int(11) DEFAULT '5' COMMENT '最大可借数量', `reader_status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)', `create_by` varchar(64) DEFAULT '' COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) DEFAULT '' COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`reader_id`), UNIQUE KEY `uni_card` (`reader_card`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='读者信息表'; -- 借阅记录表 CREATE TABLE `borrow_record` ( `record_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '记录ID', `book_id` bigint(20) NOT NULL COMMENT '图书ID', `reader_id` bigint(20) NOT NULL COMMENT '读者ID', `borrow_time` datetime NOT NULL COMMENT '借出时间', `due_time` datetime NOT NULL COMMENT '应还时间', `return_time` datetime DEFAULT NULL COMMENT '实际归还时间', `record_status` char(1) DEFAULT '0' COMMENT '状态(0借出中 1已归还 2逾期)', `create_by` varchar(64) DEFAULT '' COMMENT '创建者', `update_by` varchar(64) DEFAULT '' COMMENT '更新者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`record_id`), KEY `idx_book_id` (`book_id`), KEY `idx_reader_id` (`reader_id`), CONSTRAINT `fk_book` FOREIGN KEY (`book_id`) REFERENCES `book` (`book_id`), CONSTRAINT `fk_reader` FOREIGN KEY (`reader_id`) REFERENCES `reader` (`reader_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图书借阅记录表';设计要点解释:
- 字段命名:采用了
表名_字段名的格式,避免与若依系统表字段混淆。若依风格常用下划线分隔。 - 公共字段:
create_by,create_time,update_by,update_time,remark是若依代码生成器预期的字段,用于记录操作日志和软删除(如果启用)。保留它们有利于与框架风格统一。 - 状态字段:使用
char(1)存储状态码,在代码中用枚举类管理,如‘0’代表“可借”。这是若依处理数据字典的常见方式。 - 外键约束:
borrow_record表通过外键关联book和reader,保证了数据一致性。在实际高并发场景下,外键可能会影响性能,可根据实际情况决定是否在应用层保证一致性。
3.2 利用若依代码生成器创建基础CRUD代码
若依的强大之处在于其代码生成器。我们不需要手动编写每一层的代码。
- 启动代码生成器功能:确保后端服务已启动。使用默认管理员账号登录若依前端(账号
admin,密码admin123,前提是已执行SQL脚本初始化了数据)。 - 配置数据源和生成路径:
- 在系统工具 -> 代码生成菜单中,点击“导入”按钮,选择我们刚创建的
book表。 - 导入后,在列表中点击“编辑”按钮。
- 关键配置:
- 生成模块名:
book(这会影响Java包名,如com.ruoyi.book)。 - 生成业务名:
book(这会影响类名,如BookController)。 - 生成功能作者:填写你的名字。
- 上级菜单:可以选择一个已存在的菜单(如“系统工具”)作为父菜单,也可以新建。这里建议新建一个“图书管理”菜单。
- 仔细核对表信息和字段信息,确保生成属性(如Java类型、查询方式)正确。例如,
book_status字段的查询方式可以设置为“下拉框”,字典类型需要后续在系统管理中配置。
- 生成模块名:
- 在系统工具 -> 代码生成菜单中,点击“导入”按钮,选择我们刚创建的
- 生成代码:配置完成后,点击“提交”。然后回到列表页,选中
book表,点击“生成代码”。浏览器会下载一个名为ruoyi.zip的压缩包。 - 解压并集成代码:
- 解压
ruoyi.zip,里面会有一个标准的Maven多模块结构文件夹。 - 将
main/java下的com.ruoyi包复制到后端项目的ruoyi-admin/src/main/java/com/ruoyi/目录下,合并文件夹。 - 将
main/resources下的mapper/book文件夹复制到ruoyi-admin/src/main/resources/mapper/目录下。 - 将
vue文件夹下的文件,复制到前端项目的对应目录(通常是src/views/下)。前端项目需要单独启动,我们稍后介绍。
- 解压
- 重启后端并配置菜单:重启
RuoYiApplication。登录系统管理 -> 菜单管理,你应该能看到一个名为“图书管理”的菜单目录,其下有一个“图书信息”菜单。点击它,即可访问刚刚生成的图书信息管理页面,进行增删改查操作。
常见坑点1:代码生成后页面404或空白
- 现象:点击生成的菜单,页面加载失败或空白。
- 原因:前端代码未正确集成或路由未注册。
- 排查:检查浏览器开发者工具(F12)的Console和Network标签页,看是否有JS错误或请求404。确保前端
src/views目录下有对应的.vue文件,并且src/router/index.js中已自动或手动添加了该页面的路由。- 解决:按照若依前端项目README,正确放置
.vue文件,并确保路由配置正确。通常代码生成器生成的前端文件包含路由定义,需要手动合并到项目的路由文件中。
4. 实现核心业务逻辑:借书与还书
代码生成器帮我们完成了单表的CRUD,但图书管理系统的核心业务是借书和还书,这涉及多表操作和业务规则校验,需要我们自己编写Service层和Controller层的方法。
4.1 业务规则定义
在编码前,明确业务规则:
- 借书时:
- 检查图书状态是否为“可借”(
book_status = ‘0’)。 - 检查图书库存(
book_stock)是否大于0。 - 检查读者状态是否为“正常”(
reader_status = ‘0’)。 - 检查读者当前借阅数量是否小于其最大可借数量(
reader_max_borrow)。 - 满足以上条件后,创建借阅记录,将图书状态改为“借出”,库存减1,读者当前借阅数加1(需要在读者表增加
current_borrow字段,或通过查询borrow_record统计)。
- 检查图书状态是否为“可借”(
- 还书时:
- 检查借阅记录状态是否为“借出中”(
record_status = ‘0’)。 - 更新借阅记录的归还时间和状态为“已归还”。
- 将对应图书状态改回“可借”,库存加1。
- 如果归还时间晚于应还时间(
due_time),计算逾期天数,可能触发罚款逻辑(这里简化处理,仅更新状态为“逾期”)。
- 检查借阅记录状态是否为“借出中”(
4.2 编写Service层业务代码
在ruoyi-book模块的service包下(或直接在ruoyi-admin的对应包下),创建或修改IBookBorrowService接口和其实现类BookBorrowServiceImpl。
接口定义 (IBookBorrowService.java):
package com.ruoyi.book.service; import com.ruoyi.book.domain.BorrowRecord; /** * 图书借阅服务接口 */ public interface IBookBorrowService { /** * 借阅图书 * @param bookId 图书ID * @param readerId 读者ID * @return 结果 */ public boolean borrowBook(Long bookId, Long readerId); /** * 归还图书 * @param recordId 借阅记录ID * @return 结果 */ public boolean returnBook(Long recordId); }实现类 (BookBorrowServiceImpl.java):
package com.ruoyi.book.service.impl; import com.ruoyi.book.domain.Book; import com.ruoyi.book.domain.Reader; import com.ruoyi.book.domain.BorrowRecord; import com.ruoyi.book.mapper.BookMapper; import com.ruoyi.book.mapper.ReaderMapper; import com.ruoyi.book.mapper.BorrowRecordMapper; import com.ruoyi.book.service.IBookBorrowService; import com.ruoyi.common.utils.DateUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Date; @Service public class BookBorrowServiceImpl implements IBookBorrowService { @Autowired private BookMapper bookMapper; @Autowired private ReaderMapper readerMapper; @Autowired private BorrowRecordMapper borrowRecordMapper; @Override @Transactional(rollbackFor = Exception.class) // 开启事务,确保数据一致性 public boolean borrowBook(Long bookId, Long readerId) { // 1. 校验图书 Book book = bookMapper.selectBookById(bookId); if (book == null || !"0".equals(book.getBookStatus()) || book.getBookStock() <= 0) { throw new RuntimeException("图书不可借阅或库存不足"); } // 2. 校验读者 Reader reader = readerMapper.selectReaderById(readerId); if (reader == null || !"0".equals(reader.getReaderStatus())) { throw new RuntimeException("读者状态异常"); } // 查询读者当前借阅数量 (假设Reader实体有currentBorrow字段,需先查询) Long currentBorrowCount = borrowRecordMapper.countBorrowingByReaderId(readerId); if (currentBorrowCount >= reader.getReaderMaxBorrow()) { throw new RuntimeException("读者借阅数量已达上限"); } // 3. 更新图书信息 book.setBookStatus("1"); // 状态改为借出 book.setBookStock(book.getBookStock() - 1); bookMapper.updateBook(book); // 4. 创建借阅记录 BorrowRecord record = new BorrowRecord(); record.setBookId(bookId); record.setReaderId(readerId); record.setBorrowTime(DateUtils.getNowDate()); // 假设借期30天 record.setDueTime(DateUtils.addDays(record.getBorrowTime(), 30)); record.setRecordStatus("0"); // 借出中 // 设置创建人等信息 (从若依的SecurityUtils获取当前登录用户) // record.setCreateBy(SecurityUtils.getUsername()); borrowRecordMapper.insertBorrowRecord(record); return true; } @Override @Transactional(rollbackFor = Exception.class) public boolean returnBook(Long recordId) { // 1. 获取借阅记录 BorrowRecord record = borrowRecordMapper.selectBorrowRecordById(recordId); if (record == null || !"0".equals(record.getRecordStatus())) { throw new RuntimeException("借阅记录状态异常,无法归还"); } // 2. 更新记录 Date now = DateUtils.getNowDate(); record.setReturnTime(now); // 判断是否逾期 if (now.after(record.getDueTime())) { record.setRecordStatus("2"); // 逾期 // 此处可扩展逾期罚款逻辑 } else { record.setRecordStatus("1"); // 已归还 } borrowRecordMapper.updateBorrowRecord(record); // 3. 更新图书信息 Book book = bookMapper.selectBookById(record.getBookId()); if (book != null) { book.setBookStatus("0"); // 状态改回可借 book.setBookStock(book.getBookStock() + 1); bookMapper.updateBook(book); } return true; } }关键点解释:
@Transactional:借书和还书涉及多张表的更新,必须放在一个数据库事务中。rollbackFor = Exception.class确保发生任何异常时,所有操作回滚,避免数据不一致(例如,扣了库存却没生成借阅记录)。- 业务校验:在操作前进行充分的校验,并抛出明确的异常信息。这是保证业务规则正确执行的关键。
- 依赖注入:使用
@Autowired自动注入Mapper,这是MyBatis-Plus/MyBatis的常用方式。 - 工具类:
DateUtils是若依框架提供的日期工具类,方便进行日期计算和格式化。
4.3 编写Controller层接口
在controller包下创建BookBorrowController,提供RESTful API供前端调用。
package com.ruoyi.book.controller; import com.ruoyi.book.service.IBookBorrowService; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * 图书借还控制器 */ @RestController @RequestMapping("/book/borrow") public class BookBorrowController extends BaseController { @Autowired private IBookBorrowService bookBorrowService; @PostMapping("/borrow") public AjaxResult borrow(@RequestParam Long bookId, @RequestParam Long readerId) { try { boolean success = bookBorrowService.borrowBook(bookId, readerId); return success ? AjaxResult.success("借书成功") : AjaxResult.error("借书失败"); } catch (RuntimeException e) { return AjaxResult.error(e.getMessage()); // 返回具体的业务异常信息 } catch (Exception e) { logger.error("借书操作异常", e); return AjaxResult.error("系统异常,借书失败"); } } @PostMapping("/return/{recordId}") public AjaxResult returnBook(@PathVariable Long recordId) { try { boolean success = bookBorrowService.returnBook(recordId); return success ? AjaxResult.success("还书成功") : AjaxResult.error("还书失败"); } catch (RuntimeException e) { return AjaxResult.error(e.getMessage()); } catch (Exception e) { logger.error("还书操作异常", e); return AjaxResult.error("系统异常,还书失败"); } } }关键点解释:
- 继承
BaseController:若依的基类,提供了日志记录器(logger)和一些通用方法。 @RestController和@RequestMapping:声明这是一个REST API控制器,并定义基础路径。AjaxResult:若依定义的标准响应体,包含code、msg、data,前端会按照此格式解析。- 异常处理:捕获业务异常(
RuntimeException)和系统异常,分别返回给前端友好的错误信息和系统错误日志。这是生产环境必备的健壮性处理。
5. 集成若依权限控制与前端对接
业务逻辑完成后,需要确保这些功能在若依的权限体系下安全运行,并与前端页面集成。
5.1 配置菜单与权限
- 添加菜单:在系统管理 -> 菜单管理中,为“图书管理”目录下添加两个新菜单:“图书借阅”和“图书归还”。填写菜单名称、路由地址(如
/book/borrow)、组件路径(对应前端的.vue文件路径)、权限标识(如book:borrow:view)。 - 分配权限:在角色管理中,为“图书管理员”角色分配“图书借阅”和“图书归还”的菜单权限。这样,只有拥有该角色的用户才能看到和操作这些功能。
5.2 使用若依安全框架保护接口
若依默认使用Spring Security或Apache Shiro进行安全控制。我们需要在Controller的方法上添加权限注解,确保只有授权用户才能访问。
使用@PreAuthorize注解(Spring Security):
import org.springframework.security.access.prepost.PreAuthorize; @RestController @RequestMapping("/book/borrow") public class BookBorrowController extends BaseController { // ... @PreAuthorize("@ss.hasPermi('book:borrow:borrow')") // 权限标识符 @PostMapping("/borrow") public AjaxResult borrow(@RequestParam Long bookId, @RequestParam Long readerId) { // ... } @PreAuthorize("@ss.hasPermi('book:borrow:return')") @PostMapping("/return/{recordId}") public AjaxResult returnBook(@PathVariable Long recordId) { // ... } }@ss.hasPermi是若依自定义的权限验证方法,它会检查当前登录用户是否拥有指定的权限字符串。这个字符串需要与菜单管理中配置的“权限标识”一致。
5.3 前端页面开发与联调
前端部分,我们需要创建两个Vue页面:一个用于借书(选择图书和读者),一个用于还书(查询借阅记录并操作)。
- 创建Vue组件:在若依前端项目的
src/views目录下,创建book/borrow/index.vue和book/return/index.vue。 - 编写页面逻辑:页面主要包含表单、表格和按钮。使用Element UI组件(若依已集成)快速搭建。关键是通过
axios调用我们刚写的后端接口(/book/borrow/borrow和/book/borrow/return/{recordId})。 - 配置路由:在
src/router/index.js中,引入刚创建的组件,并添加到路由配置中,使其与后台菜单的路由地址匹配。 - 启动前端项目:在若依前端项目根目录下,执行
npm install安装依赖,然后执行npm run dev启动开发服务器。默认访问http://localhost:80。 - 联调测试:登录系统,进入“图书借阅”页面,选择图书和读者,点击借阅。观察浏览器Network请求,确认请求是否成功,后端返回的数据是否正确。还书功能同理。
常见坑点2:前端调用接口报404或403
- 404:检查后端Controller的
@RequestMapping路径、请求方法(@PostMapping)是否正确;检查前端axios请求的URL是否拼写正确(注意上下文路径)。- 403 (Forbidden):通常是权限问题。检查
@PreAuthorize注解的权限标识是否与菜单配置的一致;检查当前登录用户所属角色是否拥有该菜单/按钮权限。- 通用排查:打开浏览器开发者工具,查看Network请求的详细信息(URL、Method、Headers、Payload、Response),这是定位前后端联调问题的第一现场。
6. 生产环境部署与优化考量
当系统开发测试完毕,准备上线时,需要考虑生产环境的配置和优化。
6.1 应用打包与部署
- 后端打包:在项目根目录执行Maven命令,打包成可执行的JAR文件。
打包后的JAR文件位于mvn clean package -DskipTestsruoyi-admin/target/ruoyi-admin.jar。 - 前端打包:在前端项目目录下,执行构建命令,生成静态资源。
生成的npm run build:proddist文件夹包含所有静态文件。 - 部署方式:
- 前后端分离部署:将
ruoyi-admin.jar上传到服务器,使用java -jar ruoyi-admin.jar --spring.profiles.active=prod启动(prod配置文件需提前配置数据库、Redis等)。将前端dist目录下的文件部署到Nginx或Apache等Web服务器,并配置反向代理,将API请求转发到后端服务。 - 前后端不分离(传统):若依也支持将前端资源打包进JAR。修改后端配置,将前端
dist内容复制到resources/static下,然后打包。这种方式只需部署一个JAR。
- 前后端分离部署:将
6.2 生产环境关键配置
创建application-prod.yml,覆盖开发环境的配置。
# 数据源配置 (连接池参数优化) spring: datasource: druid: url: jdbc:mysql://生产数据库IP:3306/ry_book?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: 生产用户名 password: 强密码 initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 validation-query: SELECT 1 # Redis配置 (用于会话管理、缓存) redis: host: 生产RedisIP port: 6379 password: Redis密码 database: 0 timeout: 3000ms # 日志配置 (输出到文件,按天滚动) logging: file: name: /var/log/ruoyi/ruoyi-book.log logback: rollingpolicy: max-file-size: 10MB max-history: 30 # 文件上传路径 (绝对路径) ruoyi: profile: /home/ruoyi/uploadPath6.3 性能与安全最佳实践
- 数据库优化:
- 索引:为
borrow_record表的book_id,reader_id,record_status字段添加索引,加速查询。 - SQL监控:启用Druid的SQL监控功能,定期分析慢查询。
- 连接池:根据实际并发调整Druid连接池参数(
initialSize,maxActive等)。
- 索引:为
- 应用层优化:
- 缓存:将不常变动的数据(如图书分类、数据字典)放入Redis缓存,减少数据库压力。
- 异步处理:对于非实时性要求高的操作,如发送借阅成功通知邮件,可以使用Spring的
@Async或消息队列进行异步处理。 - 限流与降级:在高并发场景下,考虑使用Sentinel等工具对核心接口(如借书)进行限流,防止系统被压垮。
- 安全加固:
- 密码加密:确保数据库中的用户密码是加密存储的(若依默认使用BCrypt)。
- XSS与CSRF防护:若依前端已集成相关防护,确保生产环境不要禁用。
- 接口防刷:对登录、借书等关键接口,增加验证码或IP限流策略。
- 定期更新依赖:关注SpringBoot、MyBatis、若依等框架的安全公告,及时更新版本修复漏洞。
常见坑点3:生产环境内存溢出 (OutOfMemoryError)
- 现象:服务运行一段时间后崩溃,日志显示
java.lang.OutOfMemoryError: Java heap space。- 原因:JVM堆内存不足;可能存在内存泄漏,如未关闭的数据库连接、大对象未释放、缓存无限增长。
- 排查:
- 使用
jps查看Java进程ID。- 使用
jmap -heap <pid>查看堆内存使用情况。- 使用
jmap -histo:live <pid>查看存活对象 histogram。- 分析GC日志(启动时添加
-Xloggc参数)。- 解决:
- 调整JVM启动参数,增加堆内存:
-Xms512m -Xmx1024m(根据服务器内存调整)。- 检查代码,确保资源(如IO流、数据库连接)使用后正确关闭。
- 检查缓存策略,设置合理的过期时间和最大容量。
- 对于大文件上传/下载,使用流式处理,避免一次性加载到内存。
通过以上步骤,一个基于SpringBoot和若依框架的图书管理系统就从环境搭建、数据库设计、代码生成、业务开发、权限集成到生产部署完成了全流程的构建。这个项目不仅实现了基本功能,更展示了如何在成熟框架上进行符合企业级规范的二次开发。你可以在此基础上,继续扩展预约功能、罚款计算、数据统计报表等模块,使其更加完善。