1. 项目概述:基于SpringBoot+Vue的在线点播系统
这个毕业设计项目构建了一个完整的在线视频点播平台,采用前后端分离架构。后端使用SpringBoot框架提供RESTful API服务,前端基于Vue.js实现用户交互界面,数据存储采用MySQL关系型数据库。系统实现了视频上传、分类管理、用户权限控制、播放记录追踪等核心功能模块。
我在实际开发中发现,这种技术组合特别适合中小型视频平台开发。SpringBoot的自动配置特性大幅减少了后端开发工作量,Vue的组件化开发模式让前端功能模块可以高效复用,而MySQL的稳定性和成熟生态则确保了数据安全。整套系统可以在普通服务器上流畅运行,对硬件要求不高但能支撑不错的并发量。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构解析
2.1 后端技术选型
SpringBoot 2.7.x作为后端框架,主要基于以下考虑:
- 内嵌Tomcat服务器,无需额外部署
- 自动配置机制简化了SSM框架整合
- 丰富的Starter依赖(如spring-boot-starter-web、spring-boot-starter-security)
- 完善的文档和社区支持
数据库操作采用MyBatis-Plus 3.5.x,相比原生MyBatis:
- 内置通用CRUD方法,减少30%以上的重复代码
- 强大的条件构造器简化复杂查询
- 分页插件自动处理物理分页逻辑
重要提示:建议使用MyBatis-Plus的乐观锁插件处理并发更新问题,特别是在视频点赞、收藏等高频操作场景。
2.2 前端技术方案
Vue 3.x组合式API开发前端应用,核心优势包括:
- 按需引入特性减小打包体积
- Composition API使逻辑关注点更集中
- 更好的TypeScript支持
Element Plus作为UI组件库,提供:
- 现成的表单、表格、弹窗等业务组件
- 完善的国际化支持
- 可定制的主题系统
视频播放采用video.js + hls.js方案,支持:
- MP4、HLS(m3u8)等多种视频格式
- 自适应码率切换
- 自定义控制栏样式
2.3 数据库设计要点
MySQL 8.0主要表结构设计原则:
-
视频表(video)包含字段:
- 哈希值(避免重复上传)
- 转码状态(区分原始文件和转码后文件)
- 审核状态(实现内容管控)
-
用户表(user)设计考虑:
- 密码使用BCrypt加密存储
- 角色字段实现RBAC权限控制
- 最后登录时间用于活跃度统计
-
特别注意的点:
- 视频分类表使用闭包表设计,支持无限级分类
- 播放记录表添加索引优化查询性能
- 评论表采用软删除机制
3. 核心功能实现细节
3.1 视频上传与处理流程
完整的上传处理链路如下:
java复制// 伪代码示例:视频上传控制器
@PostMapping("/upload")
public Result uploadVideo(@RequestParam MultipartFile file,
@CurrentUser User user) {
// 1. 校验文件类型和大小
if(!isVideoFile(file)) {
return Result.error("仅支持MP4/MOV格式");
}
// 2. 生成文件哈希值(防重复)
String fileHash = generateFileHash(file);
Video existVideo = videoService.getByHash(fileHash);
if(existVideo != null) {
return Result.ok("视频已存在", existVideo);
}
// 3. 保存原始文件
String originalPath = fileStorageService.storeOriginal(file);
// 4. 创建视频记录(初始状态为待处理)
Video video = new Video();
video.setUserId(user.getId());
video.setOriginalUrl(originalPath);
video.setStatus(VideoStatus.PROCESSING);
videoService.save(video);
// 5. 提交转码任务到消息队列
mqService.sendTranscodeTask(video.getId());
return Result.ok("上传成功", video);
}
转码服务关键配置:
yaml复制# application.yml片段
ffmpeg:
path: /usr/bin/ffmpeg
hls:
segment_time: 10
hls_time: 10
hls_list_size: 0
resolutions:
- name: 480p
width: 854
height: 480
- name: 720p
width: 1280
height: 720
3.2 权限控制系统实现
基于Spring Security的权限控制方案:
- 安全配置类核心代码:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/v1/auth/**").permitAll()
.antMatchers("/api/v1/video/play/**").permitAll()
.antMatchers("/api/v1/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
- JWT令牌生成逻辑:
java复制public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("roles", userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()));
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
3.3 视频播放统计实现
播放量统计的优化方案:
- 使用Redis HyperLogLog统计UV:
java复制public void recordPlay(Long videoId, String clientIp) {
// 每日UV统计
String uvKey = "video:uv:" + LocalDate.now() + ":" + videoId;
redisTemplate.opsForHyperLogLog().add(uvKey, clientIp);
// 总播放量增量
String countKey = "video:play_count:" + videoId;
redisTemplate.opsForValue().increment(countKey);
// 定时持久化到数据库
if(!redisTemplate.hasKey("job:sync_play_count")) {
scheduleSyncTask();
}
}
- 播放记录存储设计:
sql复制CREATE TABLE `play_history` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint DEFAULT NULL COMMENT '匿名用户为null',
`video_id` bigint NOT NULL,
`progress` int DEFAULT '0' COMMENT '观看进度(秒)',
`duration` int DEFAULT NULL COMMENT '视频总时长',
`device_type` varchar(20) DEFAULT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_video` (`user_id`,`video_id`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4. 部署与运维实践
4.1 生产环境部署方案
推荐使用Docker Compose部署:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
MYSQL_DATABASE: video_db
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:6
ports:
- "6379:6379"
volumes:
- redis_data:/data
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
environment:
SPRING_PROFILES_ACTIVE: prod
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend
volumes:
mysql_data:
redis_data:
4.2 性能优化技巧
-
视频文件存储策略:
- 原始文件存放到NAS或对象存储(如MinIO)
- 转码后的HLS分片使用CDN加速
- 为不同清晰度准备多套码率
-
数据库查询优化示例:
java复制// 不好的写法
List<Video> videos = videoService.list();
videos.forEach(v -> {
v.setAuthor(userService.getById(v.getUserId()));
v.setPlayCount(redisTemplate.opsForValue().get("video:play_count:"+v.getId()));
});
// 优化后的写法
List<Video> videos = videoService.list();
List<Long> userIds = videos.stream().map(Video::getUserId).distinct().collect(Collectors.toList());
Map<Long, User> userMap = userService.listByIds(userIds).stream()
.collect(Collectors.toMap(User::getId, Function.identity()));
List<String> countKeys = videos.stream()
.map(v -> "video:play_count:" + v.getId())
.collect(Collectors.toList());
List<Long> counts = redisTemplate.opsForValue().multiGet(countKeys);
for(int i=0; i<videos.size(); i++) {
videos.get(i).setAuthor(userMap.get(videos.get(i).getUserId()));
videos.get(i).setPlayCount(counts.get(i));
}
- Nginx配置优化要点:
nginx复制# 视频文件缓存配置
location ~* \.(mp4|m3u8)$ {
expires 30d;
add_header Cache-Control "public";
# 支持断点续传
mp4;
mp4_buffer_size 4m;
mp4_max_buffer_size 10m;
}
# 前端静态资源
location / {
try_files $uri $uri/ /index.html;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
}
5. 开发经验与问题排查
5.1 常见问题解决方案
- 跨域问题处理:
java复制// SpringBoot后端配置
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.exposedHeaders("Authorization");
}
};
}
-
视频播放卡顿排查步骤:
- 检查Nginx是否开启gzip_static
- 确认CDN节点分布是否合理
- 测试直接访问m3u8文件响应时间
- 检查ffmpeg转码参数是否合适
-
内存泄漏排查记录:
- 使用Arthas监控JVM内存
- 发现未关闭的InputStream
- 修复方案:使用try-with-resources语法
5.2 项目扩展建议
-
推荐的功能扩展方向:
- 弹幕功能(WebSocket实现)
- 视频AI审核(接入第三方API)
- 用户行为分析(ELK日志系统)
- 付费订阅系统(集成支付SDK)
-
性能监控方案:
bash复制# Prometheus监控配置示例
spring:
application:
name: video-service
metrics:
export:
prometheus:
enabled: true
tags:
service: ${spring.application.name}
management:
endpoints:
web:
exposure:
include: health,info,prometheus
metrics:
tags:
application: ${spring.application.name}
- 压力测试数据参考:
- 测试工具:JMeter
- 单机配置:4核8G
- 并发能力:800-1200 QPS(视频列表接口)
- 优化后延迟:<200ms(P99)
