feat(chaos): 实现视频分片上传和后台处理功能- 新增视频上传相关控制器、服务接口和实现类

- 实现了视频分片上传、合并和后台处理的逻辑
- 添加了 RabbitMQ 消息队列配置和消息转换器
-优化了 JWT 认证过滤器和日志记录
- 新增了跨域配置
This commit is contained in:
Chaos
2025-07-20 07:17:30 +08:00
parent 287394e8f5
commit 3683a9d8e0
23 changed files with 916 additions and 7 deletions

218
upload.html Normal file
View File

@@ -0,0 +1,218 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分片上传示例</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f2f5;
}
.container {
background: white;
padding: 2rem 3rem;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0,0,0,0.1);
text-align: center;
width: 90%;
max-width: 500px;
}
h2 {
color: #333;
margin-bottom: 1.5rem;
}
#fileInput {
display: none;
}
.file-label {
display: inline-block;
padding: 10px 20px;
background-color: #e9ecef;
border: 2px dashed #ced4da;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.3s, border-color 0.3s;
margin-bottom: 1rem;
}
.file-label:hover {
background-color: #dee2e6;
border-color: #adb5bd;
}
#fileName {
font-style: italic;
color: #6c757d;
margin-bottom: 1.5rem;
min-height: 20px;
}
#uploadButton {
width: 100%;
padding: 12px 20px;
border: none;
background: linear-gradient(45deg, #007bff, #0056b3);
color: white;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
transition: transform 0.2s, box-shadow 0.2s;
}
#uploadButton:disabled {
background: #adb5bd;
cursor: not-allowed;
}
#uploadButton:not(:disabled):hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
}
.progress-bar {
width: 100%;
background-color: #e9ecef;
border-radius: 8px;
margin-top: 1.5rem;
overflow: hidden;
}
.progress {
width: 0%;
height: 24px;
background: linear-gradient(45deg, #28a745, #218838);
border-radius: 8px;
text-align: center;
color: white;
line-height: 24px;
font-weight: bold;
transition: width 0.4s ease-in-out;
}
#status {
margin-top: 1rem;
color: #212529;
font-weight: 500;
}
</style>
</head>
<body>
<div class="container">
<h2>视频分片上传</h2>
<label for="fileInput" class="file-label">选择文件</label>
<input type="file" id="fileInput" />
<div id="fileName">未选择文件</div>
<button id="uploadButton">上传</button>
<div class="progress-bar">
<div class="progress" id="progressBar">0%</div>
</div>
<p id="status"></p>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
const fileInput = document.getElementById('fileInput');
const uploadButton = document.getElementById('uploadButton');
const progressBar = document.getElementById('progressBar');
const statusEl = document.getElementById('status');
const fileNameEl = document.getElementById('fileName');
// =================================================================
// 【重要】请将这里替换为您通过登录接口获取到的真实JWT Token
const JWT_TOKEN = "Chaos "
+ "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjaGFvcyIsImlhdCI6MTc1MjkwNjk4NCwiZXhwIjoxNzUzNTExNzg0fQ.fG8bSGqji9BfKVoSpMKy5GvSQuzXNdlc7Km94nkuUyPOPVBcGLSBafzSBONxn7ECYcGhS0jmmNK-_z207zy-UA"
// =================================================================
const api = axios.create({
baseURL: 'http://localhost:18888/api/video', // 确保这个基础URL与您的后端服务匹配
headers: { 'Authorization': JWT_TOKEN }
});
fileInput.addEventListener('change', () => {
if (fileInput.files.length > 0) {
fileNameEl.textContent = fileInput.files[0].name;
} else {
fileNameEl.textContent = '未选择文件';
}
});
uploadButton.addEventListener('click', async () => {
const file = fileInput.files[0];
if (!file) {
alert('请先选择文件!');
return;
}
uploadButton.disabled = true;
statusEl.textContent = '正在初始化上传...';
updateProgress(0);
try {
// 步骤 1: 初始化上传,获取 uploadId 和 chunkSize
const initResponse = await api.post('/init', {
fileName: file.name,
totalSize: file.size
});
const { uploadId, chunkSize } = initResponse.data.data;
statusEl.textContent = '初始化成功,开始上传分片...';
// 步骤 2: 分片并并发上传
const totalChunks = Math.ceil(file.size / chunkSize);
const uploadPromises = [];
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('uploadId', uploadId);
formData.append('chunkIndex', i);
const promise = api.post('/chunk', formData, {
onUploadProgress: (progressEvent) => {
// 这个回调是单个分片的进度,我们在这里简单更新总进度
const percentCompleted = Math.round(((i * chunkSize) + progressEvent.loaded) * 100 / file.size);
updateProgress(percentCompleted);
}
});
uploadPromises.push(promise);
}
// 等待所有分片上传完成
await Promise.all(uploadPromises);
statusEl.textContent = '所有分片上传完毕,正在合并文件...';
// 步骤 3: 通知后端合并文件
const mergeResponse = await api.post('/merge', {
uploadId: uploadId,
fileName: file.name
});
statusEl.textContent = mergeResponse.data.message || '文件上传成功,后台处理中!';
updateProgress(100);
} catch (error) {
console.error('上传失败:', error);
statusEl.textContent = '上传失败: ' + (error.response?.data?.message || error.message);
updateProgress(0, true); // 出错时重置进度条为红色
} finally {
uploadButton.disabled = false;
}
});
function updateProgress(percentage, isError = false) {
progressBar.style.width = percentage + '%';
progressBar.textContent = percentage + '%';
if (isError) {
progressBar.style.background = 'linear-gradient(45deg, #dc3545, #c82333)';
} else {
progressBar.style.background = 'linear-gradient(45deg, #28a745, #218838)';
}
}
</script>
</body>
</html>