1. 项目概述
这个电影推荐系统采用了当前企业级开发中最流行的技术组合:SpringBoot+Vue3+MyBatis。作为一个前后端分离架构的项目,它完美展现了现代Web应用开发的典型范式。我在实际开发中发现,这种技术栈组合特别适合需要快速迭代的中大型项目,既能保证后端服务的稳定性,又能提供流畅的前端用户体验。
系统核心功能是通过协同过滤算法实现个性化电影推荐。与传统的基于内容的推荐不同,协同过滤能挖掘用户之间的行为关联,这种"物以类聚,人以群分"的思路在实际应用中往往能产生意想不到的好效果。我在多个商业项目中验证过,这种推荐方式特别适合电影这类强偏好的内容领域。
2. 技术架构解析
2.1 后端技术选型
SpringBoot 2.7.x作为后端框架,这是经过深思熟虑的选择。相比原生Spring,SpringBoot的自动配置特性让开发者能更专注于业务逻辑。我在配置时特别加入了spring-boot-starter-data-redis,因为推荐系统的用户行为数据非常适合用Redis做缓存。
MyBatis-Plus 3.5.x作为ORM框架,它的Wrapper条件构造器能极大简化复杂查询的编写。对于电影推荐系统这种读多写少的场景,我特别配置了二级缓存,并针对热映电影数据做了缓存预热策略。
重要提示:MyBatis的N+1问题在推荐系统中尤为明显,务必在关联查询时使用
标签做延迟加载配置。
2.2 前端技术方案
Vue3的组合式API相比Options API更适合复杂交互场景。我在开发中发现,推荐系统的用户画像展示模块用setup语法糖写起来特别顺畅。Element Plus作为UI库,它的虚拟滚动表格完美解决了电影列表的渲染性能问题。
前端工程化方面,我配置了:
- Vite 3.x构建工具(比Webpack快一个数量级)
- Pinia状态管理(替代Vuex的轻量方案)
- Axios拦截器统一处理推荐结果的分页加载
2.3 数据库设计
MySQL 8.0的表结构设计有几个关键点:
sql复制CREATE TABLE `user_behavior` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT '用户ID',
`movie_id` bigint NOT NULL COMMENT '电影ID',
`behavior_type` tinyint NOT NULL COMMENT '1浏览 2收藏 3评分',
`rating` decimal(3,1) DEFAULT NULL COMMENT '评分0-5',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_movie` (`user_id`,`movie_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
这个行为日志表是协同过滤算法的数据基础。我特意将behavior_type和rating放在一起,因为在实际业务中,用户评分是最有价值的显式反馈数据。
3. 协同过滤算法实现
3.1 用户相似度计算
采用改进的皮尔逊相关系数计算用户相似度:
java复制public double similarity(User a, User b) {
// 获取共同评分电影集合
Set<Long> commonMovies = getCommonRatedMovies(a, b);
double sum1 = 0, sum2 = 0, sum3 = 0;
double avgA = a.getAverageRating();
double avgB = b.getAverageRating();
for (Long movieId : commonMovies) {
double ra = a.getRating(movieId) - avgA;
double rb = b.getRating(movieId) - avgB;
sum1 += ra * rb;
sum2 += ra * ra;
sum3 += rb * rb;
}
return sum1 / (Math.sqrt(sum2) * Math.sqrt(sum3));
}
在实际测试中,我发现当共同评分项目少于5个时,相似度计算会失真。因此增加了过滤条件:仅当|commonMovies|≥5时才计算相似度。
3.2 推荐结果生成
基于用户的协同过滤核心逻辑:
java复制public List<Recommendation> recommend(User user, int size) {
// 1. 找出K个最相似用户
List<SimilarUser> neighbors = findKNearestNeighbors(user, 20);
// 2. 统计邻居评过但目标用户未评的电影
Map<Long, Double> candidateMovies = new HashMap<>();
for (SimilarUser neighbor : neighbors) {
for (Rating rating : neighbor.getUser().getRatings()) {
if (!user.hasRated(rating.getMovieId())) {
double weightedScore = rating.getScore() * neighbor.getSimilarity();
candidateMovies.merge(rating.getMovieId(), weightedScore, Double::sum);
}
}
}
// 3. 按加权得分排序
return candidateMovies.entrySet().stream()
.sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
.limit(size)
.map(e -> new Recommendation(e.getKey(), e.getValue()))
.collect(Collectors.toList());
}
性能优化点:实际部署时应该用Redis的ZSET存储用户相似度矩阵,计算邻居时直接ZREVRANGE取TOP K,比全量计算快10倍以上。
4. 前后端交互设计
4.1 API接口规范
采用RESTful风格设计的关键接口:
| 端点 | 方法 | 描述 |
|---|---|---|
| /api/recommend | GET | 获取推荐列表 |
| /api/behavior | POST | 提交用户行为 |
| /api/movie/ | GET | 获取电影详情 |
推荐接口的响应数据结构示例:
json复制{
"code": 200,
"data": {
"recommendations": [
{
"movieId": 123,
"title": "肖申克的救赎",
"poster": "/posters/123.jpg",
"predictedRating": 4.8,
"reason": "与您喜好相似的用户也喜欢"
}
],
"pagination": {
"page": 1,
"size": 10,
"total": 100
}
}
}
4.2 实时行为采集
前端埋点方案:
javascript复制// 在电影详情页挂载时
onMounted(() => {
recordBehavior('view', movieId.value)
})
// 收藏按钮点击
const handleCollect = () => {
recordBehavior('collect', movieId.value)
}
// 评分组件变化时
const handleRating = (score) => {
recordBehavior('rate', movieId.value, { score })
}
后端通过Kafka异步处理行为日志,解耦主业务流程。我在生产环境配置了3分区的Kafka topic,确保高并发下的吞吐量。
5. 性能优化实践
5.1 缓存策略设计
采用多级缓存架构:
- 本地缓存(Caffeine):存储用户最近10次的推荐结果
- Redis缓存:存储热点电影数据和用户画像
- MySQL持久层:全量数据存储
缓存更新策略特别重要,我的经验是:
- 用户行为触发时:立即失效相关推荐缓存
- 电影数据变更时:延迟双删策略
- 冷启动用户:使用热门榜单作为兜底
5.2 算法优化技巧
- 降维处理:对用户-电影评分矩阵做SVD分解,将万维空间降到100-200维
- 分片计算:按电影类别分片计算相似度,减少单次计算量
- 离线预处理:每天凌晨用Spark批量计算全量用户相似度矩阵
6. 部署实战
6.1 容器化部署
Docker Compose编排方案:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql-data:/var/lib/mysql
redis:
image: redis:6-alpine
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
6.2 性能监控配置
SpringBoot Actuator + Prometheus + Grafana监控方案:
properties复制# application.properties
management.endpoints.web.exposure.include=*
management.metrics.export.prometheus.enabled=true
我在实践中发现这几个指标最关键:
- 推荐接口的99线延迟
- 用户行为日志的堆积量
- Redis缓存命中率
7. 踩坑实录
-
冷启动问题:新用户没有行为数据时,采用"热门+随机"的混合策略,逐步收集用户偏好
-
数据稀疏性:当用户行为数据不足时,引入电影内容特征做混合推荐
-
算法偏见:定期检测推荐结果的多样性,避免陷入信息茧房
-
性能陷阱:全量计算用户相似度时OOM,改为分批次计算并持久化中间结果
-
前后端时间格式:统一使用UTC时间戳传输,前端按需格式化显示
这个项目最让我惊喜的是Vue3的Composition API与SpringBoot的响应式编程配合起来异常默契。在开发实时推荐看板时,前后端通过WebSocket保持连接,任何用户行为都能立即反映在推荐结果中,这种即时反馈极大地提升了用户体验。