视频处理
需求分析
原始视频通常需要经过编码处理,生成m3u8和ts文件放可以基于HLS协议播放视频,通常用户上传原始视频,系统自动处理成标准格式,系统对用户上传的视频自动编码,转换,最总生成m3u8文件和ts文件,处理流程如下:
1.用户上传视频成功
2.系统对上传成功的视频自动开始编码处理
3.用户查看视频处理结果,没有处理成功的视频用户可以再管理界面再次促发处理
4.视频处理完成将视频地址及处理结果保存到数据库
视频处理流程如下:
视频处理开发
视频处理工程创建
单独处理视频进程处理,不在媒资上传项目中处理.导入项目
2.RabbitMQ配置
使用RabbitMQ的routing交换机模式,视频处理程序监听视频处理队列,如下图:
RabbitMQ配置
package com.xuecheng.manage_media_process.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Administrator
* @version 1.0
* @create 2018-07-12 9:04
**/
@Configuration
public class RabbitMQConfig {
public static final String EX_MEDIA_PROCESSTASK = "ex_media_processor";
//视频处理队列
@Value("${xc-service-manage-media.mq.queue-media-video-processor}")
public String queue_media_video_processtask;
//视频处理路由
@Value("${xc-service-manage-media.mq.routingkey-media-video}")
public String routingkey_media_video;
//消费者并发数量
public static final int DEFAULT_CONCURRENT = 10;
/**
* 交换机配置
* @return the exchange
*/
@Bean(EX_MEDIA_PROCESSTASK)
public Exchange EX_MEDIA_VIDEOTASK() {
return ExchangeBuilder.directExchange(EX_MEDIA_PROCESSTASK).durable(true).build();
}
//声明队列
@Bean("queue_media_video_processtask")
public Queue QUEUE_PROCESSTASK() {
Queue queue = new Queue(queue_media_video_processtask,true,false,true);
return queue;
}
/**
* 绑定队列到交换机 .
* @param queue the queue
* @param exchange the exchange
* @return the binding
*/
@Bean
public Binding binding_queue_media_processtask(@Qualifier("queue_media_video_processtask") Queue queue, @Qualifier(EX_MEDIA_PROCESSTASK) Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(routingkey_media_video).noargs();
}
}
视频处理技术方案
如何通过程序进行视频处理?
ffmpeg是一个可行的视频处理程序,可以通过java调用ffmpeg.exe完成视频处理.
在java中可以使用Runtime类和ProcessBuilder类两种方式来执行外部程序,工作中至少掌握一种,本项目使用ProcessBuilder的方式来调用ffmpeg完成视频处理.
Runtime可以线下查询一些资料进行自学
这里使用ProcessBuilder
关于ProcessBuilder测试如下:
package com.xuecheng.manage_media_process;
import com.xuecheng.framework.utils.Mp4VideoUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* @author Administrator
* @version 1.0
* @create 2018-07-12 9:11
**/
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestProcessBuilder {
//使用ProcessBuilder来调用第三饭程序
@Test
public void testProcessBuilder() throws IOException {
// 首先,创建一个processBuilder对象
ProcessBuilder processBuilder = new ProcessBuilder();
// 设置第三方程序的命令
processBuilder.command("ping", "127.0.0.1");
// 将标输入流和错误流合并
processBuilder.redirectErrorStream(true);
// 启动进程,相当于在命令行输入了这样一个命令
Process process = processBuilder.start();
// 通过标准输入流,拿到正常和错误的信息了
InputStream inputStream = process.getInputStream();
// 装成字符流
// 相当于包装了一个字符流
InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"gbk");
// 缓冲
char[] chars = new char[1024];
int len = -1;
while ((len = inputStreamReader.read(chars))!=-1){
String string = new String(chars,0,len);
System.out.println(string);
}
inputStream.close();
inputStreamReader.close();
}
}
测试成功
视频处理实现
//使用ProcessFFmpeg来调用第三饭程序
@Test
public void testProcessFFmpeg() throws IOException {
// 首先,创建一个processBuilder对象
ProcessBuilder processBuilder = new ProcessBuilder();
// 设置第三方程序的命令
//因为调用FFmpeg会有很多的参数,所以这里用list
List<String> command = new ArrayList<>();
command.add("D:\\BaiduNetdiskDownload\\ffmpeg-20180227-fa0c9d6-win64-static\\bin\\ffmpeg.exe");
command.add("-i");
command.add("E:\\jetbrain\\video\\lucene.avi");
command.add("-y");
command.add("-c:v");
command.add("libx264");
command.add("-s");
command.add("1280x720");
command.add("-pix_fmt");
command.add("yuv420p");
command.add("-b:a");
command.add("63k");
command.add("-b:v");
command.add("753k");
command.add("-r");
command.add("18");
command.add("E:\\jetbrain\\video\\1.mp4");
processBuilder.command(command);
// 将标输入流和错误流合并
processBuilder.redirectErrorStream(true);
// 启动进程,相当于在命令行输入了这样一个命令
Process process = processBuilder.start();
// 通过标准输入流,拿到正常和错误的信息了
InputStream inputStream = process.getInputStream();
// 装成字符流
// 相当于包装了一个字符流
InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"gbk");
// 缓存
char[] chars = new char[1024];
int len = -1;
while ((len = inputStreamReader.read(chars))!=-1){
String string = new String(chars,0,len);
System.out.println(string);
}
inputStream.close();
inputStreamReader.close();
}
处理成功,将lucene.avi转换成1.MP4
// 调用mp4Util使用避免直接写processBuilder
@Test
public void testMp4VideoUtil(){
//String ffmpeg_path,Stirng videoo_path ,String mp4_name, String mp4folder_path
String ffmpeg_path = "D:\\BaiduNetdiskDownload\\ffmpeg-20180227-fa0c9d6-win64-static\\bin\\ffmpeg.exe";
String video_path = "E:\\jetbrain\\video\\lucene.avi";
String mp4_name = "2.mp4";
String mp4folder_path = "E:\\jetbrain\\video\\";
Mp4VideoUtil mp4VideoUtil = new Mp4VideoUtil(ffmpeg_path,video_path,mp4_name,mp4folder_path);
// 生成mp4
String result = mp4VideoUtil.generateMp4();
System.out.println(result);
}
}
使用mp4VedioUtil工具类成功
确定消息格式
MQ消息统一采用json格式,视频处理生产方会向MQ发送如下消息,视频处理消费方接受此消息后会进行视频处理:
{“mediald”:XXX}
处理流程
1.接受视频处理消息
2.判断媒体文件是否需要处理(本视频处理程序目前只接收avi视频的处理)
当前只有avi文件需要处理,其他文件需要更新处理状态为"无需处理".
3.处理前初始化处理状态为"未处理:
4.处理失败需要在数据库记录处理日志,及处理状态为"处理失败".
5.处理成功记录处理状态为"处理成功"
package com.xuecheng.manage_media_process.mq;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author Andrewer
* @version 1.0
* @project xcEduService01
* @description Rabbitmq
* @date 2023/1/7 10:10:57
*/
@Component
public class MediaProcessTask {
// 接收视频处理消息进行视频处理
@RabbitListener(queues = "${xc-service-manage-media.mq.queue-media-video-processor}")
public void receiveMediaProcessTask(String msg){
// 1.解析消息内容得到mediaid
// 2.那mediaId从数据库查询文件信息
// 3.使用工具类将avi文件生成mp4
// 4.将MP4生成m3u8和ts文件
}
}
数据模型
视频处理生成Mp4
视频处理生成m3u8
package com.xuecheng.manage_media_process.mq;
import com.alibaba.fastjson.JSON;
import com.xuecheng.framework.domain.media.MediaFile;
import com.xuecheng.framework.domain.media.MediaFileProcess_m3u8;
import com.xuecheng.framework.utils.HlsVideoUtil;
import com.xuecheng.framework.utils.Mp4VideoUtil;
import com.xuecheng.manage_media_process.dao.MediaFileRepository;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* @author Andrewer
* @version 1.0
* @project xcEduService01
* @description Rabbitmq
* @date 2023/1/7 10:10:57
*/
@Component
public class MediaProcessTask {
@Value("${xc-service-manage-media.ffmpeg-path}")
String ffmpeg_path;
@Value("${xc-service-manage-media.video-location}")
String video_location;
@Autowired
MediaFileRepository mediaFileRepository;
// 接收视频处理消息进行视频处理
@RabbitListener(queues = "${xc-service-manage-media.mq.queue-media-video-processor}")
public void receiveMediaProcessTask(String msg) {
// 1.解析消息内容得到mediaid
Map map = JSON.parseObject(msg, Map.class);
Object mediaId = map.get("mediaId");
// 2.那mediaId从数据库查询文件信息
Optional<MediaFile> optional = mediaFileRepository.findById((String) mediaId);
if (!optional.isPresent()) {
return;
}
MediaFile mediaFile = optional.get();
// 判断文件的拓展名是什么类型
String fileType = mediaFile.getFileType();
if (!fileType.equals("avi")) {
mediaFile.setProcessStatus("303004");//无需处理
mediaFileRepository.save(mediaFile);
} else {
mediaFile.setProcessStatus("303001");//处理中
}
// 3.使用工具类将avi文件生成mp4
//String ffmpeg_path,Stirng videoo_path ,String mp4_name, String mp4folder_path
// 要处理的视频文件的路径
// 生成的mp4的文件名
String vedeo_path = video_location + mediaFile.getFilePath() + mediaFile.getFileName();
String mp4_name = mediaFile.getFileId() + ".mp4";
// 生成的mp4所在的路径
String mp4folder_path = vedeo_path + mediaFile.getFilePath();
Mp4VideoUtil mp4VideoUtil = new Mp4VideoUtil(ffmpeg_path, vedeo_path, mp4_name, mp4folder_path);
// 运行处理
String result = mp4VideoUtil.generateMp4();
if (result == null || !result.equals("success")) {
// 处理失败
// 记录处理失败的状态
mediaFile.setProcessStatus("303003");
// 记录失败原因
MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8();
mediaFileProcess_m3u8.setErrormsg(result);
mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8);//记录失败原因
mediaFileRepository.save(mediaFile);
return;
}
// 处理成功
// 4.将MP4生成m3u8和ts文件
// String ffmpeg_path,String video_path,String m3u8_name,String m3u8folder_path
// mp4视频文件的路径
String mp4_video_path = vedeo_path + mediaFile.getFilePath() + mp4_name;
// m3u8的文件名称
String m3u8_name = mediaFile.getFileId() + ".m3u8";
// m3u8文件所在的目录
String m3u8folder_path = vedeo_path + mediaFile.getFilePath() + "hls/";
HlsVideoUtil hlsVideoUtil = new HlsVideoUtil(ffmpeg_path, mp4_video_path, m3u8_name, m3u8folder_path);
//生成m3u8文件和ts文件
String tsresult = hlsVideoUtil.generateM3u8();
if (tsresult == null || !tsresult.equals("success")) {
// 和上边的代码一摸一样
// 处理失败
// 记录处理失败的状态
mediaFile.setProcessStatus("303003");
// 记录失败原因
MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8();
mediaFileProcess_m3u8.setErrormsg(result);
mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8);//记录失败原因
mediaFileRepository.save(mediaFile);
return;
}
// 处理成功
// 获取ts文件的列表
List<String> ts_list = hlsVideoUtil.get_ts_list();
mediaFile.setProcessStatus("303002");
// 定义mediaFileProcess_m3u8
MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8();
mediaFileProcess_m3u8.setTslist(ts_list);
mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8);
// 保存fileURL
// 这个url就是视频播放的相对路径
String fileUrl = mediaFile.getFilePath() + "hls/" + m3u8_name;
mediaFile.setFileUrl(fileUrl);
mediaFileRepository.save(mediaFile);
}
}
发送视频处理消息
当视频上传成功后,向MQ发送视频,处理消息.
修改媒资管理类服务的文件上传代码,当文件上传成功向MQ发送视频处理消息.
RabbitMq配置
1.将media-processor工程下的RabbitMQConfig配置类拷贝到media工程下.
2.在media工程下配置mq队列等消息
修改application.yml
修改Service
视频处理测试
package com.xuecheng.manage_media_process.mq;
import com.alibaba.fastjson.JSON;
import com.xuecheng.framework.domain.media.MediaFile;
import com.xuecheng.framework.domain.media.MediaFileProcess_m3u8;
import com.xuecheng.framework.utils.HlsVideoUtil;
import com.xuecheng.framework.utils.Mp4VideoUtil;
import com.xuecheng.manage_media_process.dao.MediaFileRepository;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* @author Andrewer
* @version 1.0
* @project xcEduService01
* @description Rabbitmq
* @date 2023/1/7 10:10:57
*/
@Component
public class MediaProcessTask {
@Value("${xc-service-manage-media.ffmpeg-path}")
String ffmpeg_path;
@Value("${xc-service-manage-media.video-location}")
String video_location;
@Autowired
MediaFileRepository mediaFileRepository;
// 接收视频处理消息进行视频处理
@RabbitListener(queues = "${xc-service-manage-media.mq.queue-media-video-processor}")
public void receiveMediaProcessTask(String msg) {
// 1.解析消息内容得到mediaid
Map map = JSON.parseObject(msg, Map.class);
Object mediaId = map.get("mediaId");
// 2.那mediaId从数据库查询文件信息
Optional<MediaFile> optional = mediaFileRepository.findById((String) mediaId);
if (!optional.isPresent()) {
return;
}
MediaFile mediaFile = optional.get();
// 判断文件的拓展名是什么类型
String fileType = mediaFile.getFileType();
if (!fileType.equals("avi")) {
mediaFile.setProcessStatus("303004");//无需处理
mediaFileRepository.save(mediaFile);
} else {
mediaFile.setProcessStatus("303001");//处理中
}
// 3.使用工具类将avi文件生成mp4
//String ffmpeg_path,Stirng videoo_path ,String mp4_name, String mp4folder_path
// 要处理的视频文件的路径
// 生成的mp4的文件名
String vedeo_path = video_location + mediaFile.getFilePath() + mediaFile.getFileName();
String mp4_name = mediaFile.getFileId() + ".mp4";
// 生成的mp4所在的路径
String mp4folder_path = video_location + mediaFile.getFilePath();
Mp4VideoUtil mp4VideoUtil = new Mp4VideoUtil(ffmpeg_path, vedeo_path, mp4_name, mp4folder_path);
// 运行处理
String result = mp4VideoUtil.generateMp4();
if (result == null || !result.equals("success")) {
// 处理失败
// 记录处理失败的状态
mediaFile.setProcessStatus("303003");
// 记录失败原因
MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8();
mediaFileProcess_m3u8.setErrormsg(result);
mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8);//记录失败原因
mediaFileRepository.save(mediaFile);
return;
}
// 处理成功
// 4.将MP4生成m3u8和ts文件
// String ffmpeg_path,String video_path,String m3u8_name,String m3u8folder_path
// mp4视频文件的路径
String mp4_video_path = video_location + mediaFile.getFilePath() + mp4_name;
// m3u8的文件名称
String m3u8_name = mediaFile.getFileId() + ".m3u8";
// m3u8文件所在的目录
String m3u8folder_path = video_location + mediaFile.getFilePath() + "hls/";
HlsVideoUtil hlsVideoUtil = new HlsVideoUtil(ffmpeg_path, mp4_video_path, m3u8_name, m3u8folder_path);
//生成m3u8文件和ts文件
String tsresult = hlsVideoUtil.generateM3u8();
if (tsresult == null || !tsresult.equals("success")) {
// 和上边的代码一摸一样
// 处理失败
// 记录处理失败的状态
mediaFile.setProcessStatus("303003");
// 记录失败原因
MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8();
mediaFileProcess_m3u8.setErrormsg(result);
mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8);//记录失败原因
mediaFileRepository.save(mediaFile);
return;
}
// 处理成功
// 获取ts文件的列表
List<String> ts_list = hlsVideoUtil.get_ts_list();
mediaFile.setProcessStatus("303002");
// 定义mediaFileProcess_m3u8
MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8();
mediaFileProcess_m3u8.setTslist(ts_list);
mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8);
// 保存fileURL
// 这个url就是视频播放的相对路径
String fileUrl = mediaFile.getFilePath() + "hls/" + m3u8_name;
mediaFile.setFileUrl(fileUrl);
mediaFileRepository.save(mediaFile);
}
}
视频处理并发设置
代码中使用@RabbitListener注解指定消费方法,默认情况是单线程监听队列,可以观察当队列有多个任务是消费端每次只消费一个消息,单线程处理消息容易引起消息处理缓慢,消息堆积,不能最大利用硬件资源.
可以配置mq的容器工厂参数,增加并发处理数量即可实现多线程监听队列,实现多线程处理消息.
1.在RabbitmqConfig.java中添加容器工厂配置:
//消费者并发数量
public static final int DEFAULT_CONCURRENT = 10;
@Bean("customContainerFactory")
public SimpleRabbitListenerContainerFactory containerFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
ConnectionFactory connectionFactory){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConcurrentConsumers(DEFAULT_CONCURRENT);
factory.setMaxConcurrentConsumers(DEFAULT_CONCURRENT);
configurer.configure(factory,connectionFactory);
return factory;
}
在mqTask中指定容器
// 接收视频处理消息进行视频处理
@RabbitListener(queues = "${xc-service-manage-media.mq.queue-media-video-processor}", containerFactory = "customContainerFactory")
public void receiveMediaProcessTask(String msg) {
// 1.解析消息内容得到mediaid
Map map = JSON.parseObject(msg, Map.class);
Object mediaId = map.get("mediaId");
// 2.那mediaId从数据库查询文件信息
Optional<MediaFile> optional = mediaFileRepository.findById((String) mediaId);
if (!optional.isPresent()) {
return;
我的媒资
需求分析
通过我的媒资可以铲鲟被教育机构拥有的妹子文件,阱行文件处理,删除文件,修改文件信息等操作,具体需求如下:
1.分页查询我的媒资文件
2.删除媒资文件
3.处理媒资文件
4.修改媒资文件
API
接口编写:
package com.xuecheng.api.media;
import com.xuecheng.framework.domain.media.MediaFile;
import com.xuecheng.framework.domain.media.request.QueryMediaFileRequest;
import com.xuecheng.framework.model.response.QueryResponseResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
/**
* @author Andrewer
* @version 1.0
* @project xcEduService01
* @description 管理媒资文件
* @date 2023/1/7 16:35:16
*/
@Api(value = "媒体文件管理" ,description = "媒体文件管理接口",tags = "媒体文件的增删改查")
public interface MediaFileController {
@ApiOperation("我的媒体文件查询列表")
//这里的查询条件,专门写一个类类型
public QueryResponseResult<MediaFile> findList(int page, int size, QueryMediaFileRequest queryMediaFileRequest);
}
服务端开发
Dao
package com.xuecheng.manage_media.dao;
import com.xuecheng.framework.domain.media.MediaFile;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface MediaFileRepository extends MongoRepository<MediaFile,String> {
}
Service
Service
package com.xuecheng.manage_media.service;
import com.xuecheng.framework.domain.media.MediaFile;
import com.xuecheng.framework.domain.media.request.QueryMediaFileRequest;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.framework.model.response.QueryResponseResult;
import com.xuecheng.framework.model.response.QueryResult;
import com.xuecheng.manage_media.dao.MediaFileRepository;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author Andrewer
* @version 1.0
* @project xcEduService01
* @description
* @date 2023/1/7 16:46:38
*/
@Service
public class MediaFileService {
@Autowired
MediaFileRepository mediaFileRepository;
//分页查询
public QueryResponseResult<MediaFile> findList(int page, int size, QueryMediaFileRequest queryMediaFileRequest) {
// 为了保证代码的正确性,这里对queryMediaFileRequest进行条件判断
if (queryMediaFileRequest == null) {
queryMediaFileRequest = new QueryMediaFileRequest();
}
// 条件值对象
MediaFile mediaFile = new MediaFile();
if (StringUtils.isNotEmpty(queryMediaFileRequest.getTag())) {
mediaFile.setTag(queryMediaFileRequest.getTag());
}
if (StringUtils.isNotEmpty(queryMediaFileRequest.getFileOriginalName())) {
mediaFile.setFileOriginalName(queryMediaFileRequest.getFileOriginalName());
}
if (StringUtils.isNotEmpty(queryMediaFileRequest.getProcessStatus())) {
mediaFile.setProcessStatus(queryMediaFileRequest.getProcessStatus());
}
// 条件匹配器
ExampleMatcher exampleMatcher = ExampleMatcher.matching()
.withMatcher("tag", ExampleMatcher.GenericPropertyMatchers.contains())
.withMatcher("fileOriginalName", ExampleMatcher.GenericPropertyMatchers.contains());
// 定义example 条件对象
Example<MediaFile> example = Example.of(mediaFile, exampleMatcher);
// 分页查询对象
if (page <= 0) {
page = 1;
}
page = page - 1;
if (size <= 0) {
size = 10;
}
Pageable pageable = new PageRequest(page, size);
// 分页查询
Page<MediaFile> all = mediaFileRepository.findAll(example, pageable);
// 总记录数
long totalElements = all.getTotalElements();
// 数据列表
List<MediaFile> content = all.getContent();
//返回的数据集
QueryResult<MediaFile> queryResult = new QueryResult<>();
queryResult.setList(content);
queryResult.setTotal(totalElements);
QueryResponseResult queryResponseResult = new QueryResponseResult(CommonCode.SUCCESS, queryResult);
return queryResponseResult;
}
}
Controller
package com.xuecheng.manage_media.controller;
import com.xuecheng.api.media.MediaFileControllerApi;
import com.xuecheng.framework.domain.media.MediaFile;
import com.xuecheng.framework.domain.media.request.QueryMediaFileRequest;
import com.xuecheng.framework.model.response.QueryResponseResult;
import com.xuecheng.manage_media.service.MediaFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Andrewer
* @version 1.0
* @project xcEduService01
* @description 媒资管理的增删改查
* @date 2023/1/7 16:43:23
*/
@RestController
@RequestMapping("/media/file")
public class MediaFileController implements MediaFileControllerApi {
@Autowired
MediaFileService mediaFileService;
@Override
@GetMapping("/list/{page}/{size}")
public QueryResponseResult<MediaFile> findList(@PathVariable("page") int page, @PathVariable("size") int size, QueryMediaFileRequest queryMediaFileRequest) {
return mediaFileService.findList(page,size,queryMediaFileRequest);
}
}
前端开发
API方法
query(){
mediaApi.media_list(this.params.page,this.params.size,this.params).then((res)=>{
console.log(res)
this.total = res.queryResult.total
this.list = res.queryResult.list
})
}
},
路由路径
/*页面列表*/
export const media_list = (page,size,params) => {
//params为json格式
//使用querystring将json对象转成key/value串
let querys = querystring.stringify(params)
return http.requestQuickGet(apiUrl+'/media/file/list/'+page+'/'+size+'/?'+querys)
}
页面
这里还有增删改查,我懒得做了
媒资与课程计划关联
需求分析
到目前位置,媒资管理已完成文件上传,视频处理,我的媒资等基本功能,其他模块亦可以使用媒资管理功能,本节主要讲解课程计划在编辑时如何选媒资文件.
操作的业务流程如下:
1.进入课程计划修改页面
2.选择视频
打开媒资文件查询窗口,找到该课程章节的视频,选择此视频
点击选择媒资文件,打开媒资文件列表
选择视频
Vue父子组件通信
上一章已是西安我的媒资页面,所以媒资查询窗口页面不需要在开发,将"我的媒资页面"作为一个组件在秀嘎i课程计划你页面中引用,如下图:
修改课程计划页面为父组件,我的媒资查询页面为子组件
问题1:
我的媒资页面在选择妹子文件时不允许显示,比如"视频处理"按钮,该如何控制?
这时,就需要父组件(修改课程计划页面)向子组件(我的媒资页面)传入一个变量,使用此变量来控制当前是否进入选择妹子文件业务,从而控制那些元素不显示,如下图:
问题2:
在我的妹子页面选择了妹子文件,如何将选择的妹子文件信息传到父组件?
这是,就需要子组件调用父组件的方法来解决此问题,如下图:
父组件(修改课程计划)
子组件(我的媒资查询)
保存视频信息
需求分析
用户进入课程计划页面,选择视频,将课程计划与视频信息保存在课程管理数据库中.
用户操作流程:
1.进入课程计划,点击选择视频,打开我的媒资查询页面
2.为课程计划选择对一个视频,点击选择
3.前端请求课程管理服务保存课程计划与视频信息
数据模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TIt2SvKD-1673147037567)(null)]
package com.xuecheng.framework.domain.course;
import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.io.Serializable;
/**
* Created by admin on 2018/2/7.
*/
@Data
@ToString
@Entity
@Table(name="teachplan_media")
@GenericGenerator(name = "jpa-assigned", strategy = "assigned")
public class TeachplanMedia implements Serializable {
private static final long serialVersionUID = -916357110051689485L;
@Id
@GeneratedValue(generator = "jpa-assigned")
@Column(name="teachplan_id")
private String teachplanId;
@Column(name="media_id")
private String mediaId;
@Column(name="media_fileoriginalname")
private String mediaFileOriginalName;
@Column(name="media_url")
private String mediaUrl;
private String courseId;
}
API接口
在courseControllerApi中写入
@ApiOperation("保存课程计划与媒资文件的关联")
public ResponseResult savemedia(TeachplanMedia teachplanMedia);
服务端开发
DAO
package com.xuecheng.manage_course.dao;
import com.xuecheng.framework.domain.course.CourseBase;
import com.xuecheng.framework.domain.course.TeachplanMedia;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* Created by Administrator.
*/
public interface TeachplanMediaRepository extends JpaRepository<TeachplanMedia,String> {
}
Service
// 保存视频媒资计划的关联
public ResponseResult saveMedia(TeachplanMedia teachplanMedia) {
// 关键参数的校验
if (teachplanMedia == null || StringUtils.isEmpty(teachplanMedia.getCourseId())) {
// 抛出异常
ExceptionCast.cast(CommonCode.INVALID_PARAM);
}
// 校验课程计划是否是三级
String teachplanId = teachplanMedia.getTeachplanId();
// 查询课程计划
Optional<Teachplan> optional = teachplanRepository.findById(teachplanId);
if (!optional.isPresent()){
ExceptionCast.cast(CommonCode.MEIDA_TEACHPLAN_ISNULL);
}
// 查询到教学计划
Teachplan teachplan = optional.get();
// 取出等级
String grade = teachplan.getGrade();
if (StringUtils.isEmpty(grade)||!grade.equals("3")){
// 只允许选择第三级目录
ExceptionCast.cast(CommonCode.MEDIA_TEACHPLAN_GRADEISNULL);
}
// 查询teachplanMedia
Optional<TeachplanMedia> mediaOptional = teachplanMediaRepository.findById(teachplanId);
TeachplanMedia teachplanMedia1 = null;
if (mediaOptional.isPresent()){
teachplanMedia1 = mediaOptional.get();
}else{
teachplanMedia1 = new TeachplanMedia() ;
}
// 将TeachplanMedia保存到数据库
teachplanMedia1.setCourseId(teachplan.getCourseid());//课程id
teachplanMedia1.setMediaId(teachplanMedia.getMediaId());//媒资文件的id
teachplanMedia1.setMediaFileOriginalName(teachplanMedia.getMediaFileOriginalName());//原始名称
teachplanMedia1.setMediaUrl(teachplanMedia.getMediaUrl());//媒资文件的路径
teachplanMediaRepository.save(teachplanMedia1);
//
return new ResponseResult(CommonCode.SUCCESS);
}
Controller
@Override
@PostMapping("/savemedia")
public ResponseResult savemedia(TeachplanMedia teachplanMedia) {
return courseService.saveMedia(teachplanMedia);
}
前端开发
API方法
API调用
测试
查询视频信息
需求分析
课程计划的视频信息保存后在也米娜无法查看,本解决课程计划页面显示相关联的媒资信息.
解决方案:
在获取课成计划树节点的信息是,将关联的媒资信息一并查询,在前端显示,下图说明了客户菜呢个计划显示的区域.
Dao
<!--这就是二级节点一对多的标签-->
<id column="two_id" property="id"></id>
<result column="two_pname" property="pname"></result>
<collection property="children" ofType="com.xuecheng.framework.domain.course.ext.TeachplanNode">
<id column="three_id" property="id"></id>
<result column="three_pname" property="pname"></result>
<result column="media_id" property="mediaId"></result>
<result column="media_fileoriginalname" property="mediaFileOriginalName"></result>
</collection>
</collection>
</resultMap>
<select id="selectList" parameterType="java.lang.String"
resultMap="teachplanMap">
SELECT a.id one_id,
a.pname one_pname,
b.id two_id,
b.pname two_pname,
c.id three_id,
c.pname three_pname,
teachplan_media.media_id,
teachplan_media.media_fileoriginalname
FROM teachplan a
LEFT JOIN teachplan b
ON b.parentid = a.id
LEFT JOIN teachplan c
ON c.parentid = b.id
LEFT JOIN teachplan_media
ON c.id = teachplan_media.teachplan_id
WHERE a.parentid = '0'
<if test="_parameter!=null and _parameter!=''">
AND a.courseid = #{courseId}
</if>
ORDER BY a.orderby,
b.orderby,
c.orderby
</select>
里面的TeachPlanNode
package com.xuecheng.framework.domain.course.ext;
import com.xuecheng.framework.domain.course.Teachplan;
import lombok.Data;
import lombok.ToString;
import java.util.List;
/**
* Created by admin on 2018/2/7.
*/
@Data
@ToString
public class TeachplanNode extends Teachplan {
//树状结构的子节点
List<TeachplanNode> children;
// 媒资文件的id
String mediaId;
// 媒资文件的原始名称
String mediaFileOriginalName;
}