计算机毕业设计262—基于Springboot+Vue3+Ai对话的图书馆图书借阅系统(源代码+数据库+开题+PPT)
用户:图书协同过滤推荐、热门图书、热门分类、图书搜索、图书借阅、续借、还书、图书预约、评论、查看通知、AI助手、个人信息管理员:图书管理、图书分类管理、用户管理:启用禁用借阅管理:借阅记录查询,图书归还,借阅审核,逾期管理预约管理:预约记录查询,预约取消,预约通知评论管理、统计分析后端:Springboot3mybatisPlusJwt前端:Vue3elementPlusecharts
毕设所有选题:
https://blog.csdn.net/2303_76227485/article/details/131104075
基于Springboot+Vue3+Ai对话的图书馆图书借阅系统(源代码+数据库+开题+PPT)
项目编号:262
一、系统介绍
本项目前后端分离,分为用户、管理员2种角色。
1、用户:
- 图书协同过滤推荐、热门图书、热门分类、图书搜索、图书借阅、续借、还书、图书预约、评论、查看通知、AI助手、个人信息
2、管理员:
- 图书管理:图书信息录入,图书信息查询,图书信息修改,图书信息删除,图书分类管理
- 用户管理:启用禁用
- 借阅管理:借阅记录查询,图书归还,借阅审核,逾期管理
- 预约管理:预约记录查询,预约取消,预约通知
- 评论管理:评论查看,评论删除
- 统计分析:借阅统计,热门图书排行,用户借阅排行,图书库存统计,逾期统计
3、亮点:
- 实现ai对话优化用户体验
- 使用协同过滤算法为用户推荐图书
- 后台首页大屏使用echarts图表统计,更直观的看出系统的运行数据
- 使用定时任务查询逾期的借阅并通知用户
二、所用技术
后端技术栈:
- Springboot3
- mybatisPlus
- Jwt
- Mysql
- Maven
前端技术栈:
- Vue3
- Vue-router
- axios
- elementPlus
- echarts
三、环境介绍
基础环境 :IDEA/eclipse, JDK1.8, Mysql5.7及以上, Maven3.6, node16, navicat, 通义千问apikey
所有项目以及源代码本人均调试运行无问题 可支持远程调试运行
四、页面截图
1、用户:
















2、管理员:









五、浏览地址
前台地址:http://localhost:3000
-
用户账号密码:zhangsan/123456
-
管理员账户密码:admin/123456
六、部署教程
-
使用Navicat或者其它工具,在mysql中创建对应名称的数据库,并执行项目的sql文件
-
使用IDEA/Eclipse导入backend项目,若为maven项目请选择maven,等待依赖下载完成
-
修改application.yml里面的数据库配置和通义千问的apikey配置,src/main/java/com/library/LibraryManagementApplication.java启动后端项目
-
vscode或idea打开frontend项目
-
在编译器中打开terminal,执行npm install 依赖下载完成后执行 npm run serve,执行成功后会显示访问地址
七、协同过滤推荐部分代码
@Override
public IPage<BookVO> recommendBooksByCF(Long userId, Integer page, Integer size) {
// 1:获取目标用户的借阅记录(已借阅的图书ID)
Set<Long> userBorrowedBookIds = getBorrowedBookIds(userId);
if (CollectionUtils.isEmpty(userBorrowedBookIds)) {
// 无借阅历史,返回热门图书(兜底策略)
return getHotBooks(page, size);
}
// 2:构建用户-图书借阅矩阵(用户ID -> 图书ID -> 借阅次数)
Map<Long, Map<Long, Integer>> userBookMatrix = buildUserBookMatrix();
// 3:计算目标用户与其他用户的相似度
Map<Long, Double> userSimilarityMap = calculateUserSimilarity(userId, userBookMatrix);
// 4:筛选相似度最高的N个用户
List<Long> similarUserIds = userSimilarityMap.entrySet().stream()
.sorted((e1, e2) -> Double.compare(e2.getValue(), e1.getValue()))
.limit(SIMILAR_USER_COUNT)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
// 5:收集相似用户借阅过、目标用户未借阅的图书,计算推荐分数
Map<Long, Double> bookRecommendScore = calculateBookRecommendScore(
similarUserIds, userSimilarityMap, userBookMatrix, userBorrowedBookIds);
// 6:按推荐分数排序,过滤无效图书,分页返回
return getRecommendBookPage(bookRecommendScore, page, size);
}
/**
* 获取用户已借阅的图书ID
*/
private Set<Long> getBorrowedBookIds(Long userId) {
return borrowRecordMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BorrowRecord>()
.eq(BorrowRecord::getUserId, userId)
.in(BorrowRecord::getStatus,
Constants.BORROW_STATUS_BORROWED,
Constants.BORROW_STATUS_RETURNED)
).stream()
.map(BorrowRecord::getBookId)
.collect(Collectors.toSet());
}
/**
* 构建用户-图书借阅矩阵
*/
private Map<Long, Map<Long, Integer>> buildUserBookMatrix() {
// 查询所有有效借阅记录(排除待审核/拒绝的)
List<BorrowRecord> allBorrowRecords = borrowRecordMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<BorrowRecord>()
.in(BorrowRecord::getStatus,
Constants.BORROW_STATUS_BORROWED,
Constants.BORROW_STATUS_RETURNED)
);
// 统计每个用户对每本书的借阅次数
Map<Long, Map<Long, Integer>> userBookMatrix = new HashMap<>();
for (BorrowRecord record : allBorrowRecords) {
Long userId = record.getUserId();
Long bookId = record.getBookId();
userBookMatrix.putIfAbsent(userId, new HashMap<>());
Map<Long, Integer> bookCountMap = userBookMatrix.get(userId);
bookCountMap.put(bookId, bookCountMap.getOrDefault(bookId, 0) + 1);
}
// 过滤借阅次数过少的记录(减少噪声)
userBookMatrix.values().forEach(bookCountMap ->
bookCountMap.entrySet().removeIf(entry -> entry.getValue() < MIN_BORROW_COUNT)
);
// 过滤无有效借阅的用户
userBookMatrix.entrySet().removeIf(entry -> entry.getValue().isEmpty());
return userBookMatrix;
}
/**
* 计算目标用户与其他用户的余弦相似度
*/
private Map<Long, Double> calculateUserSimilarity(Long targetUserId, Map<Long, Map<Long, Integer>> userBookMatrix) {
Map<Long, Integer> targetUserRatings = userBookMatrix.get(targetUserId);
if (CollectionUtils.isEmpty(targetUserRatings)) {
return new HashMap<>();
}
Map<Long, Double> similarityMap = new HashMap<>();
for (Map.Entry<Long, Map<Long, Integer>> entry : userBookMatrix.entrySet()) {
Long otherUserId = entry.getKey();
if (otherUserId.equals(targetUserId)) {
continue; // 跳过自己
}
Map<Long, Integer> otherUserRatings = entry.getValue();
// 计算余弦相似度
double similarity = calculateCosineSimilarity(targetUserRatings, otherUserRatings);
if (similarity > 0) { // 只保留正相似的用户
similarityMap.put(otherUserId, similarity);
}
}
return similarityMap;
}
/**
* 计算两个用户的余弦相似度
* 公式:相似度 = (A·B) / (|A| * |B|),其中A·B是点积,|A|是A的模长
*/
private double calculateCosineSimilarity(Map<Long, Integer> userARatings, Map<Long, Integer> userBRatings) {
// 找出共同借阅的图书
Set<Long> commonBookIds = new HashSet<>(userARatings.keySet());
commonBookIds.retainAll(userBRatings.keySet());
if (commonBookIds.isEmpty()) {
return 0.0; // 无共同图书,相似度为0
}
// 计算点积
double dotProduct = 0.0;
for (Long bookId : commonBookIds) {
dotProduct += userARatings.get(bookId) * userBRatings.get(bookId);
}
// 计算模长
double normA = Math.sqrt(userARatings.values().stream()
.mapToDouble(r -> r * r)
.sum());
double normB = Math.sqrt(userBRatings.values().stream()
.mapToDouble(r -> r * r)
.sum());
if (normA == 0 || normB == 0) {
return 0.0;
}
return dotProduct / (normA * normB);
}
/**
* 计算图书推荐分数(相似用户相似度 * 借阅次数)
*/
private Map<Long, Double> calculateBookRecommendScore(
List<Long> similarUserIds,
Map<Long, Double> userSimilarityMap,
Map<Long, Map<Long, Integer>> userBookMatrix,
Set<Long> userBorrowedBookIds) {
Map<Long, Double> bookScoreMap = new HashMap<>();
for (Long similarUserId : similarUserIds) {
double similarity = userSimilarityMap.get(similarUserId);
Map<Long, Integer> similarUserBooks = userBookMatrix.get(similarUserId);
for (Map.Entry<Long, Integer> entry : similarUserBooks.entrySet()) {
Long bookId = entry.getKey();
int borrowCount = entry.getValue();
// 跳过目标用户已借阅的图书
if (userBorrowedBookIds.contains(bookId)) {
continue;
}
// 推荐分数 = 相似度 * 借阅次数(加权)
double score = similarity * borrowCount;
bookScoreMap.put(bookId, bookScoreMap.getOrDefault(bookId, 0.0) + score);
}
}
return bookScoreMap;
}
/**
* 过滤无效图书,分页返回推荐列表
*/
private IPage<BookVO> getRecommendBookPage(Map<Long, Double> bookScoreMap, Integer page, Integer size) {
if (CollectionUtils.isEmpty(bookScoreMap)) {
// 无推荐结果,返回热门图书
return getHotBooks(page, size);
}
// 按推荐分数降序排序
List<Long> recommendBookIds = bookScoreMap.entrySet().stream()
.sorted((e1, e2) -> Double.compare(e2.getValue(), e1.getValue()))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
// 过滤下架/库存不足的图书
List<Long> validBookIds = recommendBookIds.stream()
.filter(bookId -> {
Book book = bookMapper.selectById(bookId);
return book != null
&& book.getStatus() == Constants.BOOK_STATUS_ONLINE
&& book.getAvailableQuantity() > 0;
})
.collect(Collectors.toList());
// 分页处理
Page<BookVO> resultPage = new Page<>(page, size);
int start = (page - 1) * size;
int end = Math.min(start + size, validBookIds.size());
if (start >= validBookIds.size()) {
resultPage.setRecords(new ArrayList<>());
resultPage.setTotal(0);
return resultPage;
}
// 查询分页后的图书VO
List<Long> pageBookIds = validBookIds.subList(start, end);
List<BookVO> bookVOs = pageBookIds.stream()
.map(this::getBookById)
.collect(Collectors.toList());
resultPage.setRecords(bookVOs);
resultPage.setTotal(validBookIds.size());
return resultPage;
}
/**
* 兜底策略:返回热门图书(按借阅次数排序)
*/
private IPage<BookVO> getHotBooks(Integer page, Integer size) {
Page<Book> bookPage = new Page<>(page, size);
// 查询上架且有库存的热门图书(按借阅次数降序)
List<Book> hotBooks = bookMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Book>()
.eq(Book::getStatus, Constants.BOOK_STATUS_ONLINE)
.gt(Book::getAvailableQuantity, 0)
.orderByDesc(Book::getBorrowCount)
);
// 分页处理
Page<BookVO> resultPage = new Page<>(page, size);
int start = (page - 1) * size;
int end = Math.min(start + size, hotBooks.size());
if (start >= hotBooks.size()) {
resultPage.setRecords(new ArrayList<>());
resultPage.setTotal(0);
return resultPage;
}
List<BookVO> bookVOs = hotBooks.subList(start, end).stream()
.map(book -> this.getBookById(book.getId()))
.collect(Collectors.toList());
resultPage.setRecords(bookVOs);
resultPage.setTotal(hotBooks.size());
return resultPage;
}
更多推荐



所有评论(0)