refactor(video): 移除视频处理相关功能模块

- 删除 RabbitMQ 配置类及相关队列、交换机定义
- 移除视频控制器及其分片上传、合并处理接口
- 删除视频文件上传服务接口及实现类
- 移除视频处理服务接口及实现类
- 删除相关的 DTO 数据传输对象类
- 清理配置文件中 RabbitMQ 连接信息
This commit is contained in:
Chaos
2025-11-10 19:34:14 +08:00
parent 3683a9d8e0
commit c6d18d4979
9 changed files with 0 additions and 479 deletions

View File

@@ -1,37 +0,0 @@
package cn.nopj.chaos_api.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
public static final String QUEUE_NAME = "video.processing.queue";
public static final String EXCHANGE_NAME = "video.direct.exchange";
public static final String ROUTING_KEY = "video.processing.key";
@Bean
public Queue videoQueue() {
// durable: true, 队列持久化
return new Queue(QUEUE_NAME, true);
}
@Bean
public DirectExchange videoExchange() {
return new DirectExchange(EXCHANGE_NAME);
}
@Bean
public Binding binding(Queue queue, DirectExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
}
@Bean
public MessageConverter jsonMessageConverter() {
return new Fastjson2MessageConverter();
}
}

View File

@@ -1,92 +0,0 @@
package cn.nopj.chaos_api.service.impl;
import cn.nopj.chaos_api.config.RabbitMQConfig;
import cn.nopj.chaos_api.dto.VideoTaskPayload;
import cn.nopj.chaos_api.service.VideoFileUploadService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Map;
import java.util.UUID;
@Service
@Slf4j
public class VideoFileUploadServiceImpl implements VideoFileUploadService {
@Value("${file.upload.temp-dir}")
private String tempDir;
@Autowired
private RabbitTemplate rabbitTemplate; // 注入RabbitMQ模板
private static final long CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
public Map<String, Object> initUpload(String fileName, long totalSize) {
String uploadId = UUID.randomUUID().toString();
Path uploadDir = Paths.get(tempDir, uploadId);
try {
Files.createDirectories(uploadDir);
} catch (IOException e) {
throw new RuntimeException("无法创建临时上传目录", e);
}
return Map.of("uploadId", uploadId, "chunkSize", CHUNK_SIZE);
}
public void uploadChunk(String uploadId, int chunkIndex, byte[] bytes) throws IOException {
Path chunkPath = Paths.get(tempDir, uploadId, String.valueOf(chunkIndex));
Files.write(chunkPath, bytes);
}
public void mergeAndProcess(String uploadId, String fileName) throws IOException {
Path uploadDir = Paths.get(tempDir, uploadId);
Path mergedFilePath = Paths.get(tempDir, fileName);
// 合并文件
try (var destChannel = Files.newByteChannel(mergedFilePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
for (int i = 0; ; i++) {
Path chunkPath = uploadDir.resolve(String.valueOf(i));
if (!Files.exists(chunkPath)) break;
try (FileInputStream fis = new FileInputStream(chunkPath.toFile());
FileChannel sourceChannel = fis.getChannel()) {
sourceChannel.transferTo(0, sourceChannel.size(), destChannel);
}
Files.delete(chunkPath);
}
}
Files.delete(uploadDir);
// 发送消息到RabbitMQ而不是直接调用Service
pushResultToMQ(mergedFilePath.toString(), uploadId);
}
public void pushResultToMQ(String filePath,String uploadId) {
VideoTaskPayload payload = new VideoTaskPayload(filePath, uploadId);
rabbitTemplate.convertAndSend(
RabbitMQConfig.EXCHANGE_NAME,
RabbitMQConfig.ROUTING_KEY,
payload
);
log.info("已发送视频处理结果到消息队列: {}" , payload);
}
public void pushResultToMQ(String filePath) {
VideoTaskPayload payload = new VideoTaskPayload(filePath, UUID.randomUUID().toString());
rabbitTemplate.convertAndSend(
RabbitMQConfig.EXCHANGE_NAME,
RabbitMQConfig.ROUTING_KEY,
payload
);
log.info("已发送视频处理结果到消息队列: {}" , payload);
}
}

View File

@@ -1,181 +0,0 @@
package cn.nopj.chaos_api.service.impl;
import cn.nopj.chaos_api.config.RabbitMQConfig;
import cn.nopj.chaos_api.config.AppConfig;
import cn.nopj.chaos_api.dto.VideoTaskPayload;
import cn.nopj.chaos_api.service.VideoProcessingService;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Service
@Slf4j
public class VideoProcessingServiceImpl implements VideoProcessingService {
@Value("${file.upload.ffmpeg-path}")
private String ffmpegPath;
@Value("${file.upload.temp-dir}")
private String tempDir;
@Autowired
private AppConfig restTemplate;
@RabbitListener(queues = RabbitMQConfig.QUEUE_NAME)
public void listenForVideoTasks(VideoTaskPayload payload) {
System.out.println("收到视频处理任务: " + payload);
processVideo(payload.getSourceFilePath(), payload.getUploadId());
}
public void processVideo(String sourceFilePath, String uploadId) {
Path sourcePath = Paths.get(sourceFilePath);
String tempOutputDirName = "hls_temp_" + uploadId;
Path tempOutputDirPath = Paths.get(tempDir, tempOutputDirName);
try {
// 1. 创建临时HLS输出目录
Files.createDirectories(tempOutputDirPath);
String localM3u8Path = tempOutputDirPath.resolve("playlist.m3u8").toString();
String segmentFilename = tempOutputDirPath.resolve("segment%05d.jpg").toString();
// 2. 执行FFmpeg命令进行切片
ProcessBuilder processBuilder = new ProcessBuilder(
ffmpegPath, "-i", sourceFilePath, "-c:v", "libx264", "-c:a", "aac",
"-hls_time", "10", "-hls_list_size", "0", "-f", "hls", "-hls_segment_filename", segmentFilename, localM3u8Path
);
// ... (FFmpeg执行逻辑不变) ...
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
try (var reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line; while ((line = reader.readLine()) != null) { System.out.println("FFmpeg: " + line); }
}
if (process.waitFor() != 0) { throw new RuntimeException("FFmpeg切片失败"); }
// 3. 【核心】上传所有.ts文件到外部接口并收集返回的URL
Map<String, String> segmentUrlMap = new HashMap<>();
File[] segmentFiles = tempOutputDirPath.toFile().listFiles((dir, name) -> name.endsWith(".jpg"));
if (segmentFiles == null) { throw new RuntimeException("找不到生成的.jpg切片文件"); }
for (File segmentFile : segmentFiles) {
String returnedUrl = uploadFileToExternalServer(segmentFile);
if (returnedUrl == null) {
throw new RuntimeException("上传文件 " + segmentFile.getName() + " 失败");
}
segmentUrlMap.put(segmentFile.getName(), returnedUrl);
log.info("上传 {} 成功, 地址: {}", segmentFile.getName(), returnedUrl);
}
// 4. 【核心】根据返回的URL生成新的m3u8内容
String finalM3u8Content = createFinalM3u8Content(localM3u8Path, segmentUrlMap);
log.info("m3u8{}",finalM3u8Content);
// 5. 【核心】将最终的m3u8内容也上传到外部接口
Path finalM3u8File = tempOutputDirPath.resolve("final_playlist.m3u8");
Files.writeString(finalM3u8File, finalM3u8Content);
String finalM3u8Url = uploadFileToExternalServer(finalM3u8File.toFile());
log.info("视频处理完成最终M3U8访问地址:{}",finalM3u8Url);
// 在这里,你可以将 finalM3u8Url 保存到数据库
} catch (IOException | InterruptedException e) {
System.err.println("视频处理失败: " + e.getMessage());
e.printStackTrace();
} finally {
// 6. 清理所有本地临时文件
try {
if (Files.exists(sourcePath)) { Files.delete(sourcePath); }
if (Files.exists(tempOutputDirPath)) {
Files.walk(tempOutputDirPath)
.sorted(java.util.Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 调用外部接口上传单个文件
* @param file 要上传的文件
* @return 外部接口返回的文件URL
*/
private String uploadFileToExternalServer(File file) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.add("x-auth-token","47880955-1882-44ec-a250-a97a8f31a4eb");
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", new FileSystemResource(file)); // "file"是常见的表单字段名,请根据您的接口修改
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
try {
String requestUrl = "https://pinyp.vspjc.com:59789/melody/api/v1/oss/upload";
ResponseEntity<String> entity = restTemplate.restTemplate().postForEntity(requestUrl, requestEntity, String.class);
if (entity.getStatusCode() != HttpStatus.OK){
log.error("请求失败: {}", entity);
throw new RuntimeException("请求失败");
}
JSONObject jsonObject = JSONObject.parse(entity.getBody());
if (Objects.requireNonNull(jsonObject).getInteger("code") != 12200){
log.error("上传失败: {}", jsonObject);
throw new RuntimeException("上传失败,请检查接口是否失效");
}
String data = jsonObject.getString("data");
//data = https://qny-imimg.uuvuem.cn/df53/wx/20250719/8cc4f34394fb49ec90b1316ca9e26b86.jpg@,@image/jpeg@,@qiniu
String[] split = data.split("@");
String url = split[0];
log.info("上传成功: {}", url);
return url;
} catch (Exception e) {
log.error("上传文件失败: {}", e.getMessage());
return null;
}
}
/**
* 读取本地m3u8文件并用远程URL替换ts文件名
*/
private String createFinalM3u8Content(String localM3u8Path, Map<String, String> tsUrlMap) throws IOException {
StringBuilder newContent = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(localM3u8Path))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.endsWith(".jpg")) {
String remoteUrl = tsUrlMap.get(line.trim());
if (remoteUrl != null) {
newContent.append(remoteUrl).append("\n");
}
} else {
newContent.append(line).append("\n");
}
}
}
return newContent.toString();
}
}