1. 项目概述与设计思路
在当今内容爆炸的时代,短视频平台已经成为人们获取信息和娱乐的主要渠道之一。作为一名长期从事推荐系统开发的工程师,我发现很多计算机专业的同学在做毕业设计时,往往对推荐系统的实现原理和工程细节缺乏深入理解。这个基于SpringBoot的短视频推荐系统设计,正是为了解决这个问题而生。
这个系统最核心的价值在于:它完整复现了工业级推荐系统的核心流程,但做了恰到好处的简化,使其既适合作为毕业设计项目,又能让开发者真正理解推荐系统的工作原理。系统采用经典的B/S三层架构,前端使用Vue.js实现响应式交互,后端基于SpringBoot框架,数据存储采用MySQL,推荐算法则实现了基于用户的协同过滤(UserCF)。
提示:选择SpringBoot+Vue的技术栈,不仅因为它们是当前企业开发的主流选择,更因为这种前后端分离的架构能让项目结构更清晰,便于展示你的全栈能力。
2. 系统架构与技术选型
2.1 整体架构设计
系统采用典型的三层架构:
- 表现层:Vue.js + ElementUI
- 业务逻辑层:SpringBoot + Spring Security
- 数据持久层:MyBatis + MySQL
这种分层设计的优势在于:
- 职责分离:各层只需关注自己的核心逻辑
- 易于扩展:例如要更换推荐算法,只需修改业务层的对应服务
- 便于测试:可以针对每一层进行独立测试
2.2 数据库设计关键点
系统共设计13张核心表,这里重点说明几个关键表的设计考量:
- 用户表(user)
sql复制CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(64) NOT NULL COMMENT '登录账号',
`password` varchar(64) NOT NULL COMMENT '密码(BCrypt加密)',
`nickname` varchar(64) DEFAULT NULL COMMENT '昵称',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',
`gender` tinyint DEFAULT '0' COMMENT '性别(0-未知 1-男 2-女)',
`tags` varchar(255) DEFAULT NULL COMMENT '兴趣标签,逗号分隔',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 视频表(video)
sql复制CREATE TABLE `video` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(128) NOT NULL COMMENT '视频标题',
`category_id` bigint NOT NULL COMMENT '分类ID',
`cover_url` varchar(255) NOT NULL COMMENT '封面图URL',
`video_url` varchar(255) NOT NULL COMMENT '视频文件URL',
`description` text COMMENT '视频描述',
`tags` varchar(255) DEFAULT NULL COMMENT '视频标签,逗号分隔',
`user_id` bigint NOT NULL COMMENT '上传用户ID',
`view_count` int DEFAULT '0' COMMENT '播放量',
`like_count` int DEFAULT '0' COMMENT '点赞数',
`collect_count` int DEFAULT '0' COMMENT '收藏数',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`),
KEY `idx_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
注意:tags字段的设计非常关键,它既用于内容分类,也是推荐算法的重要特征。在实际应用中,可以考虑使用专门的标签表来实现多对多关系,但为了简化毕业设计,这里直接使用逗号分隔的字符串。
3. 核心功能实现细节
3.1 用户行为采集与处理
推荐系统的质量很大程度上取决于用户行为数据的质量。本系统设计了完整的行为采集链路:
- 前端埋点方案
javascript复制// 在视频播放组件中埋点
export default {
methods: {
handleVideoPlay() {
this.$axios.post('/api/behavior/play', {
videoId: this.video.id,
playDuration: this.currentTime
})
},
handleVideoLike() {
this.$axios.post('/api/behavior/like', {
videoId: this.video.id
})
}
}
}
- 后端行为处理服务
java复制@RestController
@RequestMapping("/api/behavior")
public class BehaviorController {
@Autowired
private UserBehaviorService behaviorService;
@PostMapping("/play")
public Result logPlayBehavior(@RequestBody PlayBehaviorDTO dto) {
behaviorService.logPlayBehavior(dto);
return Result.success();
}
@PostMapping("/like")
public Result logLikeBehavior(@RequestBody LikeBehaviorDTO dto) {
behaviorService.logLikeBehavior(dto);
return Result.success();
}
}
3.2 协同过滤推荐实现
系统实现了基于用户的协同过滤算法,核心逻辑如下:
- 用户相似度计算
java复制public class UserCFRecommender {
// 计算用户相似度矩阵
public Map<Long, Map<Long, Double>> calculateUserSimilarity(
List<UserBehavior> behaviors) {
// 1. 构建用户-物品倒排表
Map<Long, Set<Long>> userItemMap = behaviors.stream()
.collect(Collectors.groupingBy(
UserBehavior::getUserId,
Collectors.mapping(UserBehavior::getVideoId, Collectors.toSet())
));
// 2. 计算余弦相似度
Map<Long, Map<Long, Double>> similarityMatrix = new HashMap<>();
List<Long> users = new ArrayList<>(userItemMap.keySet());
for (int i = 0; i < users.size(); i++) {
Long u1 = users.get(i);
Set<Long> itemsU1 = userItemMap.get(u1);
for (int j = i + 1; j < users.size(); j++) {
Long u2 = users.get(j);
Set<Long> itemsU2 = userItemMap.get(u2);
// 计算交集
Set<Long> intersection = new HashSet<>(itemsU1);
intersection.retainAll(itemsU2);
if (!intersection.isEmpty()) {
double similarity = intersection.size() /
Math.sqrt(itemsU1.size() * itemsU2.size());
similarityMatrix
.computeIfAbsent(u1, k -> new HashMap<>())
.put(u2, similarity);
similarityMatrix
.computeIfAbsent(u2, k -> new HashMap<>())
.put(u1, similarity);
}
}
}
return similarityMatrix;
}
}
- 推荐生成逻辑
java复制public List<Video> recommendForUser(Long userId, int topN) {
// 1. 获取目标用户的最近K个相似用户
List<Long> similarUsers = findTopKSimilarUsers(userId, 20);
// 2. 获取这些相似用户喜欢但目标用户未看过的视频
Set<Long> viewedVideos = getUserViewedVideos(userId);
Map<Long, Double> candidateVideos = new HashMap<>();
for (Long similarUser : similarUsers) {
double similarity = userSimilarity.get(userId).get(similarUser);
Set<Long> similarUserVideos = getUserViewedVideos(similarUser);
for (Long videoId : similarUserVideos) {
if (!viewedVideos.contains(videoId)) {
candidateVideos.merge(videoId, similarity, Double::sum);
}
}
}
// 3. 按加权得分排序取TopN
return candidateVideos.entrySet().stream()
.sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
.limit(topN)
.map(entry -> videoService.getById(entry.getKey()))
.collect(Collectors.toList());
}
实操心得:在实际测试中发现,当用户数量较多时,全量计算用户相似度矩阵的性能会成为瓶颈。在毕业设计演示时,可以限制用户规模(如1000个活跃用户);如果要做性能优化,可以考虑使用MinHash等近似算法。
4. 系统关键问题与解决方案
4.1 冷启动问题处理
新用户或新视频缺乏足够的行为数据,会导致推荐效果不佳。系统实现了以下解决方案:
- 基于内容的推荐兜底
java复制public List<Video> contentBasedRecommend(Long userId, int topN) {
User user = userService.getById(userId);
if (user.getTags() == null || user.getTags().isEmpty()) {
// 新用户返回热门视频
return videoService.getHotVideos(topN);
}
// 根据用户标签匹配视频
Set<String> userTags = Arrays.stream(user.getTags().split(","))
.collect(Collectors.toSet());
return videoService.listAll().stream()
.filter(v -> v.getTags() != null)
.map(v -> {
Set<String> videoTags = Arrays.stream(v.getTags().split(","))
.collect(Collectors.toSet());
// 计算Jaccard相似度
Set<String> intersection = new HashSet<>(userTags);
intersection.retainAll(videoTags);
Set<String> union = new HashSet<>(userTags);
union.addAll(videoTags);
double score = union.isEmpty() ? 0 :
(double) intersection.size() / union.size();
return new AbstractMap.SimpleEntry<>(v, score);
})
.sorted(Map.Entry.<Video, Double>comparingByValue().reversed())
.limit(topN)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
- 探索与利用平衡(E&E)
java复制// 在推荐结果中混入一定比例的新视频
public List<Video> hybridRecommend(Long userId, int topN) {
List<Video> cfRecommend = cfRecommender.recommendForUser(userId, (int)(topN * 0.7));
List<Video> newVideos = videoService.getNewVideos(topN - cfRecommend.size());
List<Video> result = new ArrayList<>(cfRecommend);
result.addAll(newVideos);
Collections.shuffle(result);
return result;
}
4.2 性能优化实践
- 推荐结果缓存
java复制@Cacheable(value = "recommend", key = "'user:' + #userId")
public List<Video> getRecommendVideos(Long userId) {
return hybridRecommend(userId, 20);
}
// 使用Spring Cache抽象,实际可以配置为Redis缓存
- 异步日志处理
java复制@Slf4j
@RestController
@RequestMapping("/api/behavior")
public class BehaviorController {
@Autowired
private AsyncBehaviorService asyncService;
@PostMapping("/play")
public Result logPlayBehavior(@RequestBody PlayBehaviorDTO dto) {
asyncService.logPlayBehavior(dto);
return Result.success();
}
}
@Service
public class AsyncBehaviorService {
@Async
public void logPlayBehavior(PlayBehaviorDTO dto) {
// 写入数据库或消息队列
userBehaviorMapper.insert(convertToEntity(dto));
}
}
5. 系统部署与测试
5.1 环境搭建步骤
- 数据库初始化
bash复制# 创建数据库
mysql -uroot -p -e "CREATE DATABASE short_video DEFAULT CHARSET utf8mb4"
# 导入表结构
mysql -uroot -p short_video < schema.sql
- 后端服务启动
bash复制# 使用Maven打包
mvn clean package -DskipTests
# 运行SpringBoot应用
java -jar target/short-video-0.0.1-SNAPSHOT.jar
- 前端服务启动
bash复制# 安装依赖
npm install
# 开发模式运行
npm run serve
# 生产构建
npm run build
5.2 测试方案设计
- 功能测试用例
markdown复制| 测试场景 | 测试步骤 | 预期结果 |
|---------|---------|---------|
| 用户注册 | 1. 访问注册页面<br>2. 填写有效信息提交 | 注册成功,跳转到首页 |
| 视频推荐 | 1. 登录用户<br>2. 浏览首页 | 展示个性化推荐视频列表 |
| 行为反馈 | 1. 播放视频<br>2. 点赞视频 | 播放记录和点赞记录被正确记录 |
- 性能测试指标
java复制@SpringBootTest
public class PerformanceTest {
@Autowired
private RecommenderService recommender;
@Test
void testRecommendLatency() {
int iterations = 100;
long totalTime = 0;
for (int i = 0; i < iterations; i++) {
long start = System.currentTimeMillis();
recommender.recommendForUser(1L, 10);
totalTime += System.currentTimeMillis() - start;
}
double avgLatency = (double) totalTime / iterations;
System.out.printf("Average latency: %.2f ms%n", avgLatency);
assertTrue(avgLatency < 100, "Recommendation latency too high");
}
}
6. 项目扩展方向
这个基础版本完成后,可以从以下几个方向进行深化:
- 算法升级
- 实现矩阵分解(SVD、ALS)
- 引入深度学习模型(如YouTube的DNN推荐模型)
- 增加实时推荐能力
- 工程优化
- 引入Redis缓存推荐结果
- 使用Kafka处理用户行为流
- 实现AB测试框架
- 功能增强
- 增加社交关系推荐
- 实现多模态内容理解(视频、音频、文本)
- 开发创作者后台数据分析功能
在实际开发过程中,我发现推荐系统最关键的不仅是算法本身,更是对业务场景的理解。比如短视频推荐和电商推荐就有很大不同:前者更注重内容的即时吸引力和新鲜度,后者则更关注长期兴趣和转化率。这个项目虽然以短视频为场景,但设计的架构和核心逻辑完全可以迁移到其他推荐场景。