浏览代码

分块未合并

xiang 2 周之前
父节点
当前提交
07869ce6eb

+ 2 - 0
package.json

@@ -25,6 +25,7 @@
     "react-dom": "^19.1.0",
     "react-h5-audio-player": "^3.10.1",
     "react-router-dom": "^7.6.2",
+    "spark-md5": "^3.0.2",
     "zustand": "^5.0.5"
   },
   "devDependencies": {
@@ -34,6 +35,7 @@
     "@types/react": "^19.1.2",
     "@types/react-dom": "^19.1.2",
     "@types/react-router-dom": "^5.3.3",
+    "@types/spark-md5": "^3.0.5",
     "@vitejs/plugin-react": "^4.4.1",
     "eslint": "^9.25.0",
     "eslint-plugin-react-hooks": "^5.2.0",

+ 16 - 0
pnpm-lock.yaml

@@ -53,6 +53,9 @@ importers:
       react-router-dom:
         specifier: ^7.6.2
         version: 7.6.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+      spark-md5:
+        specifier: ^3.0.2
+        version: 3.0.2
       zustand:
         specifier: ^5.0.5
         version: 5.0.5(@types/react@19.1.8)(react@19.1.0)
@@ -75,6 +78,9 @@ importers:
       '@types/react-router-dom':
         specifier: ^5.3.3
         version: 5.3.3
+      '@types/spark-md5':
+        specifier: ^3.0.5
+        version: 3.0.5
       '@vitejs/plugin-react':
         specifier: ^4.4.1
         version: 4.5.2(vite@6.3.5(@types/node@24.0.3))
@@ -685,6 +691,9 @@ packages:
   '@types/react@19.1.8':
     resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==}
 
+  '@types/spark-md5@3.0.5':
+    resolution: {integrity: sha512-lWf05dnD42DLVKQJZrDHtWFidcLrHuip01CtnC2/S6AMhX4t9ZlEUj4iuRlAnts0PQk7KESOqKxeGE/b6sIPGg==}
+
   '@typescript-eslint/eslint-plugin@8.34.1':
     resolution: {integrity: sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1607,6 +1616,9 @@ packages:
     resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
     engines: {node: '>=0.10.0'}
 
+  spark-md5@3.0.2:
+    resolution: {integrity: sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==}
+
   string-convert@0.2.1:
     resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==}
 
@@ -2289,6 +2301,8 @@ snapshots:
     dependencies:
       csstype: 3.1.3
 
+  '@types/spark-md5@3.0.5': {}
+
   '@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0)(typescript@5.8.3))(eslint@9.29.0)(typescript@5.8.3)':
     dependencies:
       '@eslint-community/regexpp': 4.12.1
@@ -3387,6 +3401,8 @@ snapshots:
 
   source-map-js@1.2.1: {}
 
+  spark-md5@3.0.2: {}
+
   string-convert@0.2.1: {}
 
   strip-json-comments@3.1.1: {}

+ 10 - 1
src/apis/upload.ts

@@ -21,4 +21,13 @@ export const uploadFile = (bucket: string, file: File, md5: string, onProgress?:
       }
     },
   });
-};
+};
+export const chunkUploadApi = (formData) => {
+  return request('/media/upload/part', {
+    method: 'POST',
+    data: formData,
+    headers: {
+      'Content-Type': 'multipart/form-data',
+    }
+  });
+}

+ 11 - 5
src/pages/layout/pages/musician/MusicianDashboardPage/works/UploadMv/index.tsx

@@ -3,6 +3,7 @@ import { Upload, Button, Form, Input, Select, message } from 'antd';
 import { UploadOutlined } from '@ant-design/icons';
 import './index.css';
 import { uploadFile } from '@/apis/upload';
+import { chunkUpload } from '@/utils/chunkUpload';
 
 const { Option } = Select;
 
@@ -37,20 +38,25 @@ const UploadMv = () => {
     }
   };
 
+  // 更新你的 customUploadVideo 函数
   const customUploadVideo = async (options: any) => {
     const { file, onSuccess, onError, onProgress } = options;
     try {
-      const response = await uploadFile('VIDEO_FILES', file, '', onProgress);
-      onSuccess(response);
-      messageApi.success('视频上传成功');
+      console.log(file);
+
+      // 使用分片上传
+      const result = await chunkUpload(file, (progress) => {
+        onProgress({ percent: progress });
+      });
+      onSuccess({ url: result });
+      messageApi.success('视频分片上传成功');
     } catch (error: any) {
       console.error('视频上传失败:', error);
-      const errorMessage = error.response?.data?.error || '视频上传失败';
+      const errorMessage = error.message || '视频上传失败';
       messageApi.error(errorMessage);
       onError(error);
     }
   };
-
   return (
     <div className="upload-mv-container">
       {contextHolder}

+ 43 - 18
src/utils/calculateMD5.ts

@@ -1,23 +1,48 @@
-import CryptoJS from 'crypto-js'
+// src/utils/calculateMD5.ts
+import SparkMd5 from 'spark-md5';
+
 const calculateMD5 = (file: File): Promise<string> => {
   return new Promise((resolve, reject) => {
-    const reader = new FileReader()
-    reader.onload = (e) => {
-      try {
-        // 使用ArrayBuffer方式读取文件
-        const buffer = e.target?.result as ArrayBuffer
-        const wordArray = CryptoJS.lib.WordArray.create(buffer)
-        const hash = CryptoJS.MD5(wordArray)
-        resolve(hash.toString())
-      } catch (error) {
-        reject(error)
-      }
+    if (file.size === 0) {
+      resolve('');
+      return;
     }
-    reader.onerror = () => {
-      reject(new Error('文件读取失败'))
+
+    // 对于大文件使用spark-md5
+    const chunkSize = 2 * 1024 * 1024; // 2MB chunks
+    const chunks = Math.ceil(file.size / chunkSize);
+    let currentChunk = 0;
+    const spark = new SparkMd5(); // 直接使用导入的SparkMd5
+    const fileReader = new FileReader();
+
+    function loadNext() {
+      const start = currentChunk * chunkSize;
+      const end = Math.min(start + chunkSize, file.size);
+
+      fileReader.readAsBinaryString(file.slice(start, end));
     }
-    reader.readAsArrayBuffer(file)
-  })
-}
 
-export default calculateMD5
+    fileReader.onload = (e) => {
+      spark.appendBinary(e.target?.result as string);
+      currentChunk++;
+
+      if (currentChunk < chunks) {
+        loadNext();
+      } else {
+        resolve(spark.end());
+      }
+    };
+
+    fileReader.onerror = () => {
+      reject(new Error('文件读取失败'));
+    };
+
+    fileReader.onabort = () => {
+      reject(new Error('文件读取中断'));
+    };
+
+    loadNext();
+  });
+};
+
+export default calculateMD5;

+ 85 - 0
src/utils/chunkUpload.ts

@@ -0,0 +1,85 @@
+import { chunkUploadApi } from '@/apis/upload';
+import calculateMD5 from './calculateMD5';
+
+interface Chunk {
+  file: Blob;
+  index: number;
+}
+
+interface UploadPartResponse {
+  code?: number;
+  message?: string;
+  data?: any;
+}
+
+const CHUNK_SIZE = 2 * 1024 * 1024; // 2MB per chunk
+
+export const chunkUpload = async (
+  file: File,
+  onProgress?: (progress: number) => void,
+  abortSignal?: AbortSignal
+): Promise<string> => {
+  const chunks: Chunk[] = [];
+  const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
+  const fileKey = await calculateMD5(file);
+
+  // 创建分片
+  for (let i = 0; i < totalChunks; i++) {
+    const start = i * CHUNK_SIZE;
+    const end = Math.min(start + CHUNK_SIZE, file.size);
+    const chunk = file.slice(start, end);
+
+    chunks.push({
+      file: chunk,
+      index: i + 1
+    });
+  }
+
+  // 上传进度跟踪
+  let uploadedChunks = 0;
+
+  const uploadPromises = chunks.map(chunk => {
+    return uploadChunk(
+      chunk.file,
+      fileKey,
+      chunk.index,
+      totalChunks,
+      onProgress ? () => {
+        uploadedChunks++;
+        const progress = Math.round((uploadedChunks / totalChunks) * 100);
+        onProgress(progress);
+      } : undefined
+    );
+  });
+
+  // 等待所有分片上传完成
+  await Promise.all(uploadPromises);
+
+  return `分片上传完成,文件标识: ${fileKey}`;
+};
+
+const uploadChunk = async (
+  chunk: Blob,
+  fileKey: string,
+  chunkNumber: number,
+  totalChunks: number,
+  onProgress?: () => void
+): Promise<UploadPartResponse> => {
+  const formData = new FormData();
+  formData.append('file', chunk);
+  formData.append('fileKey', fileKey);
+  formData.append('chunkNumber', chunkNumber.toString());
+  formData.append('totalChunks', totalChunks.toString());
+
+  try {
+    const response = await chunkUploadApi(formData)
+    if (onProgress) {
+      onProgress();
+    }
+
+    return { message: response.data.message || '上传成功' };
+  } catch (error: any) {
+    console.error(`分片 ${chunkNumber} 上传失败:`, error);
+    throw error;
+  }
+}