feat(chaos): 实现视频分片上传和后台处理功能- 新增视频上传相关控制器、服务接口和实现类
- 实现了视频分片上传、合并和后台处理的逻辑 - 添加了 RabbitMQ 消息队列配置和消息转换器 -优化了 JWT 认证过滤器和日志记录 - 新增了跨域配置
This commit is contained in:
218
upload.html
Normal file
218
upload.html
Normal 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>
|
||||
Reference in New Issue
Block a user