1. 项目概述与背景
电影推荐系统作为现代互联网服务的重要组成部分,已经深入到我们的日常娱乐生活中。传统电影推荐平台往往采用简单的热门排行或分类筛选机制,难以满足用户个性化的观影需求。我在实际开发中发现,基于用户行为和兴趣的个性化推荐算法能显著提升用户体验和平台粘性。
本项目采用SpringBoot+Vue+MySQL技术栈构建了一个完整的个性化电影推荐系统。系统通过分析用户的历史评分、浏览记录和收藏行为,运用协同过滤算法实现千人千面的电影推荐。相比传统推荐方式,本系统具有以下核心优势:
- 动态兴趣追踪:系统会持续更新用户画像,根据最新行为调整推荐策略
- 多维度推荐:综合考虑评分相似度、类型偏好和社交关系等多个因素
- 冷启动解决方案:新用户注册时通过偏好问卷快速建立初始推荐模型
2. 技术架构设计
2.1 整体架构设计
系统采用前后端分离架构,后端基于SpringBoot框架提供RESTful API,前端使用Vue.js构建响应式用户界面,数据库采用MySQL存储结构化数据。这种架构选择主要基于以下考虑:
- 开发效率:SpringBoot的自动配置特性大幅减少了XML配置工作
- 性能考量:Vue的虚拟DOM机制能高效处理频繁更新的推荐列表
- 数据一致性:MySQL的事务支持确保用户行为数据的准确记录
技术栈组成如下:
code复制后端:SpringBoot 2.7 + MyBatis-Plus + Redis缓存
前端:Vue 3 + Element Plus + Axios
数据库:MySQL 8.0 + Redis 6.2
算法:基于用户的协同过滤(UserCF)
2.2 数据库设计关键点
2.2.1 用户表设计优化
用户表(user_info)除了存储基本账户信息外,特别设计了偏好字段:
sql复制CREATE TABLE `user_info` (
`user_id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL UNIQUE,
`email` VARCHAR(100) NOT NULL UNIQUE,
`password_hash` VARCHAR(255) NOT NULL,
`pref_genre` VARCHAR(200) COMMENT '偏好类型JSON数组',
`pref_actor` VARCHAR(200) COMMENT '偏好演员JSON数组',
`last_recommend_time` DATETIME COMMENT '最后推荐时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
注意:密码字段必须使用BCrypt等强哈希算法加密存储,绝对禁止明文保存
2.2.2 电影表特殊处理
电影表(movie_data)包含用于推荐的统计指标:
sql复制CREATE TABLE `movie_data` (
`movie_id` BIGINT PRIMARY KEY,
`title` VARCHAR(100) NOT NULL,
`director` VARCHAR(50),
`genre_tags` VARCHAR(200) COMMENT '类型标签,逗号分隔',
`avg_rating` DECIMAL(3,1) DEFAULT 0.0,
`rating_count` INT DEFAULT 0 COMMENT '评分人数',
`heat_index` INT DEFAULT 100 COMMENT '热度指数'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
实际开发中建议添加全文索引以支持标题搜索:
sql复制ALTER TABLE `movie_data` ADD FULLTEXT INDEX `idx_title` (`title`);
3. 核心功能实现
3.1 用户行为采集模块
系统通过AOP切面技术无侵入式地记录用户行为:
java复制@Aspect
@Component
public class UserBehaviorAspect {
@Autowired
private UserBehaviorService behaviorService;
@AfterReturning(pointcut="execution(* com.controller.MovieController.rateMovie(..))",
returning="result")
public void logRatingBehavior(JoinPoint jp, Object result) {
Object[] args = jp.getArgs();
Long userId = (Long) args[0];
Long movieId = (Long) args[1];
Integer rating = (Integer) args[2];
if(result instanceof R && ((R)result).getCode() == 200) {
behaviorService.saveBehavior(userId, movieId,
BehaviorType.RATING, rating.toString());
}
}
}
3.2 协同过滤推荐算法
3.2.1 用户相似度计算
采用改进的余弦相似度算法,考虑时间衰减因子:
java复制public double calculateUserSimilarity(Long user1, Long user2) {
// 获取共同评分电影
List<RatingPair> commonRatings = ratingMapper.selectCommonRatings(user1, user2);
if(commonRatings.size() < 5) return 0.0; // 最小共同评分阈值
double sum1 = 0, sum2 = 0, sum1Sq = 0, sum2Sq = 0, pSum = 0;
for(RatingPair pair : commonRatings) {
// 时间衰减因子 (0-1)
double timeFactor = calculateTimeFactor(pair.getRateTime());
double r1 = pair.getUser1Rating() * timeFactor;
double r2 = pair.getUser2Rating() * timeFactor;
sum1 += r1;
sum2 += r2;
sum1Sq += Math.pow(r1, 2);
sum2Sq += Math.pow(r2, 2);
pSum += r1 * r2;
}
double num = pSum - (sum1 * sum2 / commonRatings.size());
double den = Math.sqrt(
(sum1Sq - Math.pow(sum1, 2)/commonRatings.size()) *
(sum2Sq - Math.pow(sum2, 2)/commonRatings.size()));
return den == 0 ? 0 : num / den;
}
3.2.2 推荐结果生成
基于相似用户的加权评分预测:
java复制public List<RecommendItem> generateRecommendations(Long userId, int size) {
// 1. 获取K个最相似用户
List<SimilarUser> similarUsers = findSimilarUsers(userId, 20);
// 2. 收集候选电影(相似用户喜欢但目标用户未看过的)
Map<Long, Double> candidateMovies = new HashMap<>();
for(SimilarUser simUser : similarUsers) {
for(MovieRating rating : getHighRatings(simUser.getUserId())) {
if(!hasSeenMovie(userId, rating.getMovieId())) {
double weightedScore = rating.getScore() * simUser.getSimilarity();
candidateMovies.merge(rating.getMovieId(), weightedScore, Double::sum);
}
}
}
// 3. 标准化并排序
return candidateMovies.entrySet().stream()
.map(e -> new RecommendItem(e.getKey(),
e.getValue() / similarUsers.size())) // 平均加权
.sorted(Comparator.comparingDouble(RecommendItem::getScore).reversed())
.limit(size)
.collect(Collectors.toList());
}
4. 系统优化实践
4.1 性能优化方案
4.1.1 推荐结果缓存
使用Redis缓存推荐结果,设置合理的过期策略:
java复制@Cacheable(value = "recommendations", key = "#userId",
unless = "#result == null || #result.size() == 0")
public List<RecommendItem> getRecommendations(Long userId) {
// 实时计算逻辑...
}
// 缓存更新策略
@Scheduled(fixedRate = 6 * 60 * 60 * 1000) // 每6小时
public void refreshAllRecommendations() {
userMapper.selectAllIds().forEach(userId -> {
List<RecommendItem> items = recommendationService.generateRecommendations(userId, 20);
redisTemplate.opsForValue().set(
"recommendations::" + userId,
items,
8, TimeUnit.HOURS); // 设置8小时过期
});
}
4.1.2 数据库查询优化
针对高频查询添加复合索引:
sql复制-- 用户行为表索引
ALTER TABLE `user_behavior` ADD INDEX `idx_user_movie` (`user_id`, `movie_id`);
ALTER TABLE `user_behavior` ADD INDEX `idx_movie_rating` (`movie_id`, `rating_score`);
-- 分页查询优化
EXPLAIN SELECT * FROM `movie_data`
WHERE `avg_rating` > 7.0
ORDER BY `heat_index` DESC
LIMIT 20 OFFSET 0;
4.2 冷启动解决方案
对于新用户,采用混合推荐策略:
- 注册问卷:收集初始偏好(喜欢的类型、演员等)
- 热门填补:推荐当前热门且符合问卷结果的电影
- 探索机制:随机混入少量不同类型电影收集反馈
实现代码示例:
java复制public List<RecommendItem> handleColdStart(Long userId) {
// 1. 获取注册问卷数据
UserPreference pref = preferenceMapper.selectByUserId(userId);
// 2. 基于偏好的热门推荐
List<RecommendItem> basedOnPref = movieMapper.selectPopularByGenre(
pref.getFavoriteGenres(), 15);
// 3. 添加探索项
List<RecommendItem> exploration = movieMapper.selectRandomMovies(5);
// 合并结果
List<RecommendItem> finalRecommend = new ArrayList<>();
finalRecommend.addAll(basedOnPref);
finalRecommend.addAll(exploration);
return finalRecommend.stream()
.distinct()
.limit(20)
.collect(Collectors.toList());
}
5. 部署与运维实践
5.1 生产环境部署方案
推荐使用Docker Compose进行容器化部署:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
MYSQL_DATABASE: movie_rec
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:6.2-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/movie_rec
SPRING_REDIS_HOST: redis
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend
volumes:
mysql_data:
redis_data:
5.2 监控与日志配置
SpringBoot集成Prometheus监控:
java复制@Configuration
@EnablePrometheusEndpoint
@EnableSpringBootMetricsCollector
public class PrometheusConfig implements WebMvcConfigurer {
@Bean
public CollectorRegistry collectorRegistry() {
return new CollectorRegistry(true);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/prometheus/**")
.addResourceLocations("classpath:/static/prometheus/");
}
}
日志收集建议采用ELK栈:
properties复制# application.properties
logging.file.name=/var/log/movie-rec/app.log
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
6. 常见问题排查
6.1 推荐质量下降分析
当发现推荐准确率下降时,可按以下步骤排查:
-
数据检查:
- 确认用户行为数据是否正常采集
- 检查最近新增电影数据是否完整
- 验证用户相似度计算是否异常
-
算法验证:
java复制// 测试相似度计算 @Test public void testSimilarityCalculation() { Long user1 = 1001L, user2 = 1002L; double sim = recommender.calculateUserSimilarity(user1, user2); assertTrue(sim >= -1.0 && sim <= 1.0); // 验证已知相似用户对 Long similarPair1 = 1003L, similarPair2 = 1004L; double knownSim = recommender.calculateUserSimilarity(similarPair1, similarPair2); assertTrue(knownSim > 0.7); } -
业务因素:
- 是否近期有大量新用户注册(冷启动问题)
- 是否有热门电影导致推荐多样性下降
- 用户活跃度是否显著变化
6.2 性能问题处理
遇到系统响应变慢时,建议检查:
-
数据库慢查询:
sql复制-- 开启慢查询日志 SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log'; -- 分析执行计划 EXPLAIN ANALYZE SELECT * FROM user_behavior WHERE user_id = 1001 AND behavior_type = 'RATING'; -
缓存命中率:
bash复制# Redis缓存统计 redis-cli info stats | grep keyspace_hits redis-cli info stats | grep keyspace_misses # 计算命中率 hits=$(redis-cli info stats | grep keyspace_hits | cut -d: -f2) misses=$(redis-cli info stats | grep keyspace_misses | cut -d: -f2) echo "Hit Rate: $(echo "scale=2; $hits/($hits+$misses)*100" | bc)%" -
JVM调优:
properties复制# application.properties # 推荐JVM参数 server.tomcat.max-threads=200 spring.datasource.hikari.maximum-pool-size=20 management.endpoints.web.exposure.include=health,metrics,prometheus
7. 项目扩展方向
基于现有系统,可以考虑以下扩展方向:
-
多算法融合:
- 加入基于内容的推荐(CB)
- 实现矩阵分解(MF)算法
- 开发混合推荐引擎
-
实时推荐:
java复制// 使用WebSocket实现实时推荐更新 @Controller public class RealtimeRecommendController { @Autowired private SimpMessagingTemplate messagingTemplate; @EventListener public void handleBehaviorEvent(UserBehaviorEvent event) { List<RecommendItem> newRecs = recommender .generateRecommendations(event.getUserId(), 5); messagingTemplate.convertAndSendToUser( event.getUserId().toString(), "/queue/recommendations", newRecs); } } -
AB测试框架:
java复制@Service public class ABTestService { private final Map<String, RecommendationStrategy> strategies; public ABTestService() { strategies = Map.of( "ALGO_A", new UserCFStrategy(), "ALGO_B", new ItemCFStrategy(), "ALGO_C", new HybridStrategy() ); } public List<RecommendItem> getRecommendations(Long userId) { String group = getUserTestGroup(userId); return strategies.get(group).recommend(userId); } private String getUserTestGroup(Long userId) { // 简单哈希分组 int groupNum = (int)(userId % 3); return new String[]{"ALGO_A", "ALGO_B", "ALGO_C"}[groupNum]; } }
在实际开发过程中,我发现推荐系统的效果高度依赖数据质量。建议建立定期的数据清洗机制,处理异常评分和刷单行为。同时,推荐结果的评估不能仅依赖算法指标,还需要结合真实的用户反馈进行持续优化。