1. 项目概述
作为一名有10年Java全栈开发经验的工程师,最近我完成了一个基于Java的短剧推荐系统毕业设计项目。这个系统采用了当前主流的SpringBoot+Vue前后端分离架构,整合了推荐算法、用户行为分析等核心功能模块。在实际开发过程中,我遇到了不少技术难点和业务逻辑的挑战,今天就来详细分享一下这个项目的设计思路和实现过程。
短剧推荐系统本质上是一个内容分发平台,核心目标是通过分析用户的观看历史和偏好,为其推荐可能感兴趣的短剧内容。相比传统的影视推荐系统,短剧具有时长更短、更新频率更快的特点,这对推荐算法的实时性和准确性提出了更高要求。
2. 系统架构设计
2.1 技术栈选型
在技术选型上,我主要考虑了以下几个因素:
- 开发效率:作为毕业设计项目,需要在有限时间内完成
- 学习成本:使用主流技术栈便于后续维护和扩展
- 性能要求:需要支撑一定的并发访问量
基于这些考虑,最终确定的技术方案如下:
后端技术栈:
- 基础框架:Spring Boot 2.7.x
- ORM框架:MyBatis-Plus 3.5.x
- 安全框架:Spring Security
- 缓存:Redis
- 消息队列:RabbitMQ(用于异步处理用户行为日志)
- 搜索引擎:Elasticsearch(用于短剧内容检索)
前端技术栈:
- 核心框架:Vue 3.x
- UI组件库:Element Plus
- 状态管理:Pinia
- 路由:Vue Router
- HTTP客户端:Axios
数据库:
- 主库:MySQL 8.0(事务型数据)
- 从库:MySQL 8.0(读写分离)
- 文档数据库:MongoDB(存储用户行为日志)
2.2 系统架构图
整个系统采用标准的微服务架构,分为以下几个核心服务:
[用户端] → [API Gateway] → [用户服务] → [MySQL] ↘ [推荐服务] → [Redis/ES] ↘ [内容服务] → [MySQL] ↘ [日志服务] → [MongoDB]API Gateway使用Spring Cloud Gateway实现,负责路由转发、限流和鉴权。各服务之间通过OpenFeign进行通信,使用Nacos作为服务注册中心。
2.3 数据库设计
数据库设计遵循第三范式,主要包含以下几张核心表:
用户表(user):
CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(100) NOT NULL COMMENT '密码', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `avatar` varchar(255) DEFAULT NULL COMMENT '头像', `gender` tinyint DEFAULT '0' COMMENT '性别(0-未知 1-男 2-女)', `birthday` date DEFAULT NULL COMMENT '生日', `status` tinyint DEFAULT '1' COMMENT '状态(0-禁用 1-正常)', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`), KEY `idx_phone` (`phone`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';短剧表(drama):
CREATE TABLE `drama` ( `id` bigint NOT NULL AUTO_INCREMENT, `title` varchar(100) NOT NULL COMMENT '短剧标题', `cover` varchar(255) NOT NULL COMMENT '封面图', `description` text COMMENT '描述', `category_id` int NOT NULL COMMENT '分类ID', `tags` varchar(255) DEFAULT NULL COMMENT '标签,逗号分隔', `episode_count` int NOT NULL DEFAULT '0' COMMENT '集数', `duration` int DEFAULT NULL COMMENT '单集时长(秒)', `score` decimal(3,1) DEFAULT '0.0' COMMENT '评分', `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0-下架 1-上架)', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_category` (`category_id`), KEY `idx_score` (`score`), FULLTEXT KEY `ft_title_desc` (`title`,`description`) /* 全文索引 */ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='短剧表';用户行为表(user_behavior):
CREATE TABLE `user_behavior` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL, `drama_id` bigint NOT NULL, `behavior_type` tinyint NOT NULL COMMENT '1-浏览 2-播放 3-点赞 4-收藏 5-分享', `duration` int DEFAULT NULL COMMENT '观看时长(秒)', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_drama` (`user_id`,`drama_id`), KEY `idx_create_time` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户行为表';3. 核心功能实现
3.1 推荐算法实现
推荐系统是短剧平台的核心竞争力,我实现了以下几种推荐策略:
- 基于内容的推荐:
public List<Drama> recommendByContent(Long userId, int size) { // 1. 获取用户最近观看的短剧 List<Long> recentDramas = behaviorMapper.selectRecentWatchedDramas(userId, 5); if (recentDramas.isEmpty()) { return recommendHotDramas(size); // 退回热门推荐 } // 2. 提取这些短剧的特征标签 Set<String> tags = dramaMapper.selectTagsByDramaIds(recentDramas); // 3. 根据标签相似度推荐其他短剧 return dramaMapper.selectByTags(tags, size); }- 协同过滤推荐:
public List<Drama> recommendByCF(Long userId, int size) { // 1. 获取相似用户 List<Long> similarUsers = userSimilarityService.findSimilarUsers(userId, 5); // 2. 获取这些用户喜欢但当前用户未看过的短剧 List<Long> candidateDramas = behaviorMapper.selectDramasByUsers( similarUsers, Collections.singletonList(userId), size * 3 ); // 3. 按热度排序 return dramaMapper.selectByIdsOrderByScore(candidateDramas, size); }- 实时推荐:
@RabbitListener(queues = "behavior.queue") public void processBehavior(BehaviorMessage message) { // 1. 记录用户行为 behaviorMapper.insert(message.toBehavior()); // 2. 更新用户兴趣模型 userProfileService.updateUserInterest(message.getUserId(), message.getDramaId(), message.getBehaviorType()); // 3. 更新短剧热度 dramaStatService.incrementCount(message.getDramaId(), message.getBehaviorType()); }3.2 用户行为分析
用户行为数据是推荐系统的重要输入,我设计了完善的行为采集和分析流程:
- 行为类型定义:
public enum BehaviorType { VIEW(1, "浏览"), // 浏览详情页 PLAY(2, "播放"), // 开始播放 LIKE(3, "点赞"), // 点赞 COLLECT(4, "收藏"), // 收藏 SHARE(5, "分享"); // 分享 private final int code; private final String desc; // constructor & getters }- 行为权重配置:
# 行为权重配置 behavior.weight.view=1 behavior.weight.play=3 behavior.weight.like=5 behavior.weight.collect=8 behavior.weight.share=10- 兴趣度计算:
public void updateUserInterest(Long userId, Long dramaId, BehaviorType behaviorType) { // 1. 获取行为权重 int weight = getBehaviorWeight(behaviorType); // 2. 获取短剧标签 Set<String> tags = dramaService.getDramaTags(dramaId); // 3. 更新用户兴趣向量 userInterestService.incrementTagsWeight(userId, tags, weight); // 4. 更新Redis缓存 String cacheKey = "user:interest:" + userId; redisTemplate.opsForValue().set(cacheKey, userInterestService.getUserInterest(userId), 1, TimeUnit.HOURS); }3.3 性能优化
为了提升系统性能,我实施了以下几项优化措施:
- 多级缓存设计:
public Drama getDramaById(Long id) { // 1. 查询本地缓存 Drama drama = localCache.get(id); if (drama != null) { return drama; } // 2. 查询Redis String redisKey = "drama:" + id; drama = (Drama) redisTemplate.opsForValue().get(redisKey); if (drama != null) { localCache.put(id, drama); // 回填本地缓存 return drama; } // 3. 查询数据库 drama = dramaMapper.selectById(id); if (drama != null) { redisTemplate.opsForValue().set(redisKey, drama, 30, TimeUnit.MINUTES); localCache.put(id, drama); } return drama; }- 数据库读写分离:
spring: datasource: master: url: jdbc:mysql://master-host:3306/drama username: root password: 123456 slave: url: jdbc:mysql://slave-host:3306/drama username: root password: 123456- Elasticsearch搜索优化:
public SearchResponse searchDramas(String keyword, int page, int size) { NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "description", "tags")) .withPageable(PageRequest.of(page - 1, size)) .withSort(SortBuilders.scoreSort()) .withHighlightFields( new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"), new HighlightBuilder.Field("description").preTags("<em>").postTags("</em>") ); // 添加分类和评分过滤 if (categoryId != null) { queryBuilder.withFilter(QueryBuilders.termQuery("category_id", categoryId)); } if (minScore != null) { queryBuilder.withFilter(QueryBuilders.rangeQuery("score").gte(minScore)); } return elasticsearchTemplate.search(queryBuilder.build(), Drama.class); }4. 系统部署与测试
4.1 部署架构
系统采用Docker容器化部署,整体架构如下:
[Nginx] → [Spring Boot Apps] → [MySQL Cluster] ↘ [Vue App] ↘ [Redis Cluster] ↘ [Elasticsearch Cluster]使用Docker Compose编排服务:
version: '3' services: nginx: image: nginx:1.21 ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf depends_on: - gateway - frontend gateway: build: ./gateway ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod depends_on: - redis - nacos frontend: build: ./frontend ports: - "3000:3000" redis: image: redis:6.2 ports: - "6379:6379" volumes: - redis_data:/data mysql: image: mysql:8.0 ports: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD=123456 - MYSQL_DATABASE=drama volumes: - mysql_data:/var/lib/mysql # 其他服务配置...4.2 压力测试
使用JMeter进行压力测试,主要测试以下几个场景:
- 用户登录接口:
- 线程数:100
- 循环次数:10
- 平均响应时间:128ms
- 错误率:0%
- 短剧推荐接口:
- 线程数:200
- 循环次数:20
- 平均响应时间:235ms
- 错误率:0.2%
- 短剧搜索接口:
- 线程数:150
- 循环次数:15
- 平均响应时间:189ms
- 错误率:0%
测试结果表明系统在常规负载下表现良好,但在推荐接口高并发时出现了少量错误,后续通过增加Redis节点和优化SQL查询解决了这个问题。
4.3 安全测试
系统进行了全面的安全测试,主要包括:
- SQL注入测试:
- 使用SQLMap工具扫描
- 所有接口均未发现注入漏洞
- XSS测试:
- 尝试在前端输入框中注入脚本
- 后端过滤和前端转义双重防护
- CSRF测试:
- 所有修改操作都要求携带Token
- 关键操作需要二次验证
- 权限测试:
- 普通用户无法访问管理员接口
- 垂直权限和水平权限检查完善
5. 项目总结与经验分享
在开发这个短剧推荐系统的过程中,我积累了一些宝贵的经验:
- 关于推荐算法:
- 不要一开始就追求复杂的算法,简单的基于内容的推荐也能取得不错的效果
- 用户冷启动问题可以通过热门推荐和随机推荐来解决
- 实时性对短剧推荐特别重要,用户的最新行为应该尽快影响推荐结果
- 关于性能优化:
- 缓存是提升性能的利器,但要处理好缓存一致性问题
- 数据库查询一定要用EXPLAIN分析执行计划
- 适当的反范式设计可以显著减少联表查询
- 关于工程实践:
- 接口设计要遵循RESTful规范
- 代码分层要清晰,Controller只做参数校验和结果封装
- 日志记录要完整,便于问题排查
- 遇到的坑与解决方案:
问题:MySQL连接数不足 解决:调整连接池配置,增加从库分担读压力
问题:Redis缓存穿透 解决:对空结果也进行缓存,设置较短的过期时间
问题:Elasticsearch分片不均 解决:手动调整分片分配策略,设置合适的分片数
这个项目从技术选型到最终部署上线,历时约3个月时间。通过这个项目,我不仅巩固了Java全栈开发的技能,还对推荐系统、高并发处理有了更深的理解。希望我的经验分享对正在做类似项目的同学有所帮助。