1. 项目概述:基于协同过滤的图书推荐系统
这个图书借阅推荐系统是我去年为某高校图书馆开发的实战项目,核心目标是通过分析学生的借阅记录,实现个性化图书推荐。系统采用Java+SpringBoot后端+Vue前端的经典架构,其中推荐算法模块使用了基于用户的协同过滤算法(UserCF),实测推荐准确率达到78%以上。
关键设计要点:推荐算法模块需要独立部署为微服务,便于后期扩展其他推荐策略(如基于内容的推荐)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心算法实现解析
2.1 协同过滤算法选型
选择UserCF而非ItemCF的主要考虑:
- 图书馆场景下用户量(约2万)远小于图书量(50万+)
- 学生群体的阅读偏好更容易形成相似用户群
- 计算用户相似度矩阵可夜间批量处理,减轻实时压力
核心公式:
code复制用户相似度 = cos(θ) = (A·B)/(||A||×||B||)
推荐评分 = Σ(相似用户对该图书的评分×相似度)/Σ相似度
2.2 算法工程化实现
java复制// 用户相似度计算示例
public Map<Long, Double> calculateUserSimilarity(User targetUser) {
return allUsers.stream()
.parallel()
.filter(u -> !u.equals(targetUser))
.collect(Collectors.toMap(
User::getId,
u -> cosineSimilarity(targetUser.getBorrowVector(), u.getBorrowVector())
));
}
// 余弦相似度实现
private double cosineSimilarity(Map<Long, Integer> v1, Map<Long, Integer> v2) {
double dotProduct = 0.0;
double normA = 0.0;
double normB = 0.0;
Set<Long> commonItems = new HashSet<>(v1.keySet());
commonItems.retainAll(v2.keySet());
for (Long itemId : commonItems) {
dotProduct += v1.get(itemId) * v2.get(itemId);
}
for (int rating : v1.values()) normA += Math.pow(rating, 2);
for (int rating : v2.values()) normB += Math.pow(rating, 2);
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
性能优化点:使用稀疏矩阵存储借阅向量,相似度计算采用MapReduce并行处理
3. 系统架构设计
3.1 技术栈选型
| 模块 | 技术选型 | 选型理由 |
|---|---|---|
| 后端框架 | SpringBoot 2.7 | 快速构建微服务,集成Spring生态 |
| 数据存储 | MySQL 8.0 + Redis | 事务型数据+高速缓存组合 |
| 推荐引擎 | Spark MLlib | 分布式计算支持大规模矩阵运算 |
| 前端框架 | Vue 3 + Element Plus | 组件化开发,丰富的UI库支持 |
| 消息队列 | RabbitMQ | 解耦借阅记录采集与推荐计算 |
3.2 微服务划分
code复制├── user-service (用户管理)
├── book-service (图书目录)
├── borrow-service (借阅记录)
├── recommendation-service (推荐核心)
└── gateway (API网关)
4. 关键功能实现
4.1 实时推荐接口
java复制@RestController
@RequestMapping("/recommend")
public class RecommendController {
@Autowired
private RecommendService recommendService;
@GetMapping("/personal/{userId}")
public List<BookDTO> getPersonalRecommendations(
@PathVariable Long userId,
@RequestParam(defaultValue = "10") int size) {
return recommendService.getTopNRecommendations(userId, size);
}
}
4.2 Vue前端实现
vue复制<template>
<el-card class="recommend-box">
<h3>为您推荐的图书</h3>
<el-row :gutter="20">
<el-col
v-for="book in bookList"
:key="book.id"
:span="6">
<book-card :book-data="book"/>
</el-col>
</el-row>
</el-card>
</template>
<script>
import { getRecommendations } from '@/api/recommend'
export default {
data() {
return {
bookList: []
}
},
async created() {
const res = await getRecommendations(this.$store.state.user.id)
this.bookList = res.data
}
}
</script>
5. 性能优化实践
5.1 冷启动问题解决方案
- 热门图书兜底:新用户推荐当月借阅Top100
- 标签匹配:根据用户注册信息(院系/专业)推荐相关分类
- 混合推荐:3次借阅后逐步增加协同过滤权重
5.2 实时性保障方案
| 策略 | 实现方式 | 效果提升 |
|---|---|---|
| 增量计算 | 每晚只计算新增用户的相似度 | 计算量减少60% |
| 滑动窗口 | 仅使用最近12个月的借阅记录 | 内存占用降低40% |
| 多级缓存 | Redis缓存热门推荐结果 | 响应时间<50ms |
6. 典型问题排查实录
6.1 相似度矩阵膨胀问题
现象:用户量达到5万时,OOM异常频发
排查过程:
- 使用JProfiler分析内存占用
- 发现UserSimilarityMatrix占用了1.2GB内存
- 检查实现发现存储了全量用户的相似度关系
解决方案:
java复制// 修改为只存储TOP100相似用户
Map<Long, List<SimilarUser>> sparseMatrix = new ConcurrentHashMap<>();
// 相似度计算后立即过滤
List<SimilarUser> similarUsers = allSimilarities.stream()
.sorted(Comparator.reverseOrder())
.limit(100)
.collect(Collectors.toList());
6.2 推荐结果重复问题
根本原因:没有考虑用户已借阅图书的去重
修复方案:
sql复制-- 在推荐SQL中添加排除条件
SELECT b.* FROM books b
WHERE b.id NOT IN (
SELECT book_id FROM borrow_records
WHERE user_id = #{userId} AND return_date IS NULL
)
ORDER BY recommendation_score DESC
LIMIT #{size}
7. 项目演进方向
- 算法升级:试验基于图神经网络的推荐算法
- 场景扩展:增加"猜你喜欢"和"相似用户也在读"模块
- 效果评估:引入A/B测试框架对比不同算法效果
实际部署后发现,系统日均推荐点击量达到1200+次,相比原随机推荐提升了3倍借阅转化率。特别值得注意的是,计算机类专业学生的技术书籍借阅量显著提升,证明推荐算法有效捕捉到了专业特征。
