一、前言
最近chatGPT
十分火爆,我也抽暇运用OpenAI
的接口搭建了个自用的AI
谈天网站Onlyy Bot
(运用需求key
)。今天,突发奇想让他帮我写代码,以便看看自己被替代的或许性有多高。下面是给他的指令。
完成一个大文件上传功用的代码,以下是整体思路和要求:语言为js
,运用axios
和vue
的组合式API
,大文件进行分片上传,分片时记载每个片段的顺序索引,运用spark-md5
经过文件内容核算hash值作为文件名并调用后端接口exsitFile
,传入hash值以及chunk总数量来判别该文件此前是否上传过。注意,exsitFile
接口回来的数据包含done和existChunks
两个字段:done是一个布尔值,用于标识整个大文件是否悉数上传完结;existChunks
是一个数组,存储各个chunk
对应的索引。上传每个文件片段时都需求将大文件的文件名、文件chunk
、以及该chunk
对应的索引一起经过接口uploadFileChunk
发送给后端,uploadFileChunk
接口回来根据后端现已接纳保存的chunk
数量和大文件的chunk总数核算得到的当前上传总进展百分比。如果某个chunk发送失利,则自动重试。
需求满意以下基本功用:
- 若该文件此前现已上传,且上传完结,则本次一键秒传;
- 若该文件此前现已上传过,但是并未悉数上传完结,则本次持续上传剩下的部分;
- 若该文件此前未上传过,则本次重新开端分片上传。
额外要求:
- 需求有进展显现;
- 运用
useWorker
优化; - 将其封装为一个hook,以供多处复用;
- 供给前端运用示例。
二、gpt == 生产力
?
以下是chatGPT
生成的内容,不确保完全正确:
这是一个比较复杂的需求,需求对前端的文件上传和后端的文件处理都有比较深入的了解。以下是JS
代码的完成,其中包含了文件分片、文件上传重试、文件进展核算等功用:
import axios from 'axios';
import SparkMD5 from 'spark-md5';
/**
* 核算文件的 MD5 值
* @param {File} file
* @returns {Promise<String>}
*/
function calculateMD5(file) {
return new Promise((resolve) => {
const fileReader = new FileReader();
const spark = new SparkMD5();
const CHUNK_SIZE = 2 * 1024 * 1024; // 每次读取文件片段的巨细
let currentChunk = 0;
fileReader.onload = function (e) {
spark.appendBinary(e.target.result);
currentChunk++;
// 递归读取文件片段,直到读取完整个文件
if (currentChunk < chunks) {
loadNext();
} else {
const result = spark.end();
resolve(result);
}
};
function loadNext() {
const start = currentChunk * CHUNK_SIZE;
const end = Math.min(file.size, start + CHUNK_SIZE);
fileReader.readAsBinaryString(file.slice(start, end));
}
const chunks = Math.ceil(file.size / CHUNK_SIZE);
loadNext();
});
}
/**
* 上传文件片段操作
* @param {String} url
* @param {FormData} chunkFormData
* @returns {Promise}
*/
function uploadChunk(url, chunkFormData) {
return axios.post(url, chunkFormData, {
headers: {
'Content-Type': 'multipart/form-data',
},
// 装备请求超时时刻和最大重试次数
timeout: 10000,
retry: 3,
retryDelay: 2000,
onRetry: (err) => {
console.log(`重试上传文件片段:${err}`);
},
});
}
/**
* 根据文件 MD5 判别文件是否上传过
* @param {String} url
* @param {String} md5
* @param {Number} chunks
* @returns {Promise<Object>} { done: Boolean, existChunks: Array }
*/
function checkFileExist(url, md5, chunks) {
return axios.post(url, {
md5: md5,
chunks: chunks,
});
}
/**
* 上传文件的 Hook
* @param {String} uploadUrl
* @param {String} existUrl
* @returns {Object}
*/
function useFileUploader(uploadUrl, existUrl) {
const progressRef = ref(0); // 文件上传进展的呼应引证
const isCompleteRef = ref(false); // 文件上传是否完结的呼应引证
const isSuccessRef = ref(false); // 文件上传是否成功的呼应引证
const isUploadingRef = ref(false); // 文件上传是否正在进行的呼应引证
/**
* 上传文件操作
* @param {File} file
* @returns {Promise}
*/
async function uploadFile(file) {
isUploadingRef.value = true;
const md5 = await calculateMD5(file);
const existFile = await checkFileExist(existUrl, md5, Math.ceil(file.size / chunkSize));
if (existFile.done && existFile.existChunks.length === 0) {
// 一键秒传
progressRef.value = 100;
isCompleteRef.value = true;
isSuccessRef.value = true;
return Promise.resolve();
} else if (existFile.existChunks.length === Math.ceil(file.size / chunkSize)) {
// 文件现已上传完结
progressRef.value = 100;
isCompleteRef.value = true;
isSuccessRef.value = true;
return Promise.resolve();
} else {
// 持续上传剩下的部分或重新开端上传
const startChunk = existFile.existChunks.length; // 断点方位
const formData = new FormData();
formData.append('filename', md5);
formData.append('totalChunks', Math.ceil(file.size / chunkSize));
for (let i = startChunk; i < Math.ceil(file.size / chunkSize); i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
formData.append('chunk', chunk);
formData.append('index', i.toString());
try {
await uploadChunk(uploadUrl, formData);
progressRef.value = ((i + 1) / Math.ceil(file.size / chunkSize)) * 100;
} catch (error) {
// 产生过错,重试
console.log(error);
i--;
}
}
isCompleteRef.value = true;
isSuccessRef.value = true;
return Promise.resolve();
}
}
return {
progressRef,
isCompleteRef,
isSuccessRef,
isUploadingRef,
uploadFile,
};
}
在此基础上,能够根据自己的需求进行调整和完善。一起供给一个简略的前端运用示例:
import { useFileUploader } from './useFileUploader';
export default {
setup() {
const {
progressRef,
isCompleteRef,
isSuccessRef,
isUploadingRef,
uploadFile,
} = useFileUploader('/upload', '/existFile');
function handleFileChange(event) {
const fileList = event.target.files;
if (fileList) {
const file = fileList[0];
uploadFile(file)
.then(() => {
console.log('文件上传成功!');
})
.catch(() => {
console.log('文件上传失利!');
});
}
}
return {
progressRef,
isCompleteRef,
isSuccessRef,
isUploadingRef,
handleFileChange,
};
},
};
(持续)
上面的代码还能够进一步优化,比方能够运用 Web Worker
进行文件分片的核算,以防止堵塞主线程。详细完成能够参阅以下代码:
// 核算文件 MD5 值的 Worker 线程代码,独立于主线程运作
self.importScripts('/spark-md5.min.js');
self.onmessage = function (e) {
const fileReader = new FileReader();
const spark = new SparkMD5();
const CHUNK_SIZE = 2 * 1024 * 1024;
let currentChunk = 0;
fileReader.onload = function (e) {
spark.appendBinary(e.target.result);
currentChunk++;
if (currentChunk < e.data.chunks) {
loadNext();
} else {
const result = spark.end();
self.postMessage(result);
}
};
function loadNext() {
const start = currentChunk * CHUNK_SIZE;
const end = Math.min(e.data.file.size, start + CHUNK_SIZE);
fileReader.readAsBinaryString(e.data.file.slice(start, end));
}
const chunks = Math.ceil(e.data.file.size / CHUNK_SIZE);
loadNext();
};
/**
* 核算文件的 MD5 值
* @param {File} file
* @returns {Promise<String>}
*/
function calculateMD5(file) {
return new Promise((resolve) => {
// 创建 Worker 目标
const worker = new Worker('/md5Worker.js');
worker.postMessage({
file: file,
chunks: Math.ceil(file.size / chunkSize),
});
worker.onmessage = function (e) {
resolve(e.data);
worker.terminate(); // 核算完结后毁掉 Worker 目标
};
});
}
关于文件分片的操作,也能够运用 Worker 线程来处理。代码如下:
self.onmessage = function (e) {
const chunkFormData = e.data.chunkFormData;
const url = e.data.url;
const retryTimes = e.data.retryTimes;
/**
* 上传文件片段操作
* @param {Number} retryTimes
* @returns {Promise}
*/
async function doUpload(retryTimes) {
try {
const response = await axios.post(url, chunkFormData, {
headers: {
'Content-Type': 'multipart/form-data',
},
// 装备请求超时时刻和最大重试次数
timeout: 10000,
retry: retryTimes, // 重试次数
retryDelay: 2000, // 重试延时
onRetry: (err) => {
console.log(`重试上传文件片段:${err}`);
},
});
self.postMessage(response.data);
} catch (error) {
if (retryTimes > 0) {
// 产生过错,持续重试
doUpload(retryTimes - 1);
} else {
self.postMessage(error);
}
}
}
doUpload(retryTimes);
};
运用 Worker
线程的优势在于能够防止主线程被堵塞,进步页面的呼应速度。但一起也需求注意 Worker
线程与主线程的通讯方法和数据传输格局,以及线程的毁掉和反常处理等问题。
(持续)
针对大文件的上传,还能够运用分布式上传的计划,行将文件分片上传到多个不同的服务器上,并行进行上传,然后进步上传速度和稳定性。详细完成能够参阅以下代码:
/**
* 根据文件名和分片序号生成分片文件名
* @param {String} filename
* @param {Number} index
* @returns {String}
*/
function getChunkFilename(filename, index) {
return `${filename}-${index}`;
}
/**
* 上传文件的 Hook
* @param {Array<String>} uploadUrls
* @param {String} existUrl
* @returns {Object}
*/
function useFileUploader(uploadUrls, existUrl) {
const progressRef = ref(0);
const isCompleteRef = ref(false);
const isSuccessRef = ref(false);
const isUploadingRef = ref(false);
async function uploadFile(file) {
isUploadingRef.value = true;
const md5 = await calculateMD5(file);
const existFile = await checkFileExist(existUrl, md5, Math.ceil(file.size / chunkSize));
if (existFile.done && existFile.existChunks.length === 0) {
progressRef.value = 100;
isCompleteRef.value = true;
isSuccessRef.value = true;
return Promise.resolve();
} else if (existFile.existChunks.length === Math.ceil(file.size / chunkSize)) {
progressRef.value = 100;
isCompleteRef.value = true;
isSuccessRef.value = true;
return Promise.resolve();
} else {
const startChunk = existFile.existChunks.length;
const formDataList = []; // FormData 目标列表
for (let i = startChunk; i < Math.ceil(file.size / chunkSize); i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const chunkFormData = new FormData();
const chunkFilename = getChunkFilename(md5, i);
chunkFormData.append('filename', md5);
chunkFormData.append('totalChunks', Math.ceil(file.size / chunkSize));
// 将分片保存到不同的服务端上
for (let i = 0; i < uploadUrls.length; i++) {
const uploadUrl = uploadUrls[i];
chunkFormData.append('chunk', chunk, chunkFilename);
chunkFormData.append('index', i.toString());
formDataList[i] = formDataList[i] || new FormData();
formDataList[i].append('filename', md5);
formDataList[i].append('totalChunks', Math.ceil(file.size / chunkSize));
formDataList[i].append('chunk', chunk, chunkFilename);
formDataList[i].append('index', i.toString());
}
}
// 将分片传送到不同的服务器上并行上传
const uploadPromises = formDataList.map((formData, index) =>
uploadChunk(uploadUrls[index], formData)
);
try {
const uploadResults = await Promise.all(uploadPromises);
const progressList = uploadResults.map(({ data }) => data.progress);
progressRef.value = (sum(progressList) / progressList.length) * 100;
isCompleteRef.value = true;
isSuccessRef.value = true;
} catch (error) {
console.log(error);
isCompleteRef.value = true;
isSuccessRef.value = false;
}
}
}
return {
progressRef,
isCompleteRef,
isSuccessRef,
isUploadingRef,
uploadFile,
};
}
以上是一个比较完整的大文件上传的完成计划,其中包含文件分片、断点续传、上传进展核算、上传重试、分布式上传等功用。部分代码完成或许需求根据详细事务场景进行调整和完善。
(持续)
最终,关于大文件上传,还需求注意以下几个问题:
- 文件分片巨细应该合适,一般选用
1MB - 10MB
之间的巨细比较适合,详细巨细能够根据网络环境以及文件巨细进行调整。 - 上传进展的核算应该准确,能够经过累加每个分片上传成功的进展来核算总进展。
- 断点续传和上传重试功用应该完善,能够经过在服务端记载已上传的分片信息以便续传,并对犯错的分片进行重试操作。
- 分布式上传能够运用多个不同的服务器进步上传速度和稳定性,能够运用后端负载均衡技能完成。
- 关于用户体会,还能够添加文件巨细、文件格局的约束,以及上传速度的显现等功用,进步用户的操作体会。
以上是关于大文件上传的一些完成计划和注意事项,期望能够对你有所帮助。
(持续)
作为 Web
前端工程师,咱们还需求掌握一些前端框架或许库中大文件上传的运用方法,比方 React
和 Vue
等。
下面是一个运用 React
和 antd
的大文件上传示例代码:
import { Upload, Button, message } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import { useRef, useState } from 'react';
import axios from 'axios';
import SparkMD5 from 'spark-md5';
function calculateMD5(file) {
return new Promise((resolve) => {
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
const chunkSize = 2 * 1024 * 1024;
let currentChunk = 0;
fileReader.onload = function (e) {
spark.append(e.target.result);
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
const result = spark.end();
resolve(result);
}
};
function loadNext() {
const start = currentChunk * chunkSize;
const end = Math.min(file.size, start + chunkSize);
const buffer = file.slice(start, end);
fileReader.readAsArrayBuffer(buffer);
}
const chunks = Math.ceil(file.size / chunkSize);
loadNext();
});
}
function chunkFile(file, chunkSize) {
const chunks = Math.ceil(file.size / chunkSize);
const chunksList = [];
let currentChunk = 0;
while (currentChunk < chunks) {
const start = currentChunk * chunkSize;
const end = Math.min(file.size, start + chunkSize);
const chunk = file.slice(start, end);
chunksList.push(chunk);
currentChunk++;
}
return chunksList;
}
function App() {
const [uploading, setUploading] = useState(false);
const chunkRefs = useRef([]);
const md5Ref = useRef('');
const handleFileChange = async ({ file }) => {
setUploading(true);
const md5 = await calculateMD5(file);
md5Ref.current = md5;
// 将文件分片并保存到 reference 目标中
const chunksList = chunkFile(file, 2 * 1024 * 1024);
chunkRefs.current = chunksList.map((chunk, index) => {
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('filename', file.name);
formData.append('total', chunksList.length);
formData.append('index', index.toString());
return formData;
});
// 上传分片
const uploadPromises = chunkRefs.current.map((formData) =>
axios.post('http://localhost:3000/upload', formData)
);
try {
await Promise.all(uploadPromises);
message.success('文件上传成功!');
} catch (error) {
console.error(error);
message.error('文件上传失利!');
}
setUploading(false);
};
return (
<div>
<Upload
name="file"
accept=".jpg,.jpeg,.png,.gif,.zip,.rar,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
multiple={false}
showUploadList={false}
beforeUpload={() => false}
onChange={handleFileChange}
>
<Button loading={uploading} icon={<UploadOutlined />}>
{uploading ? '上传中' : '开端上传'}
</Button>
</Upload>
</div>
);
}
export default App;
整体来说,在 antd
中完成大文件上传功用比较简略,只需求调用 Upload
组件,而且装备相关特点即可。需求注意的是,在 beforeUpload
函数中回来 false
,以防止 Upload
组件默许的上传操作。
Vue
中的大文件上传完成方法也比较相似,能够运用 ElementUI
或许其他相关组件库,详细运用方法能够参阅官方文档。
整体来说,运用前端框架和组件库的大文件上传完成,能够有效地进步开发效率和代码可维护性,一起也具有丰厚的功用和良好的用户体会。
(持续)
(gpt
现已不想写了,强行持续了一波)
关于大文件上传,还有一些常见的问题和注意事项,需求咱们在开发中进行防止和解决。
- 网络不稳定:在上传大文件时,网络不稳定很容易导致上传失利或许上传速度变慢。能够经过添加上传重试次数、调整网络参数等方法进行解决。
- 服务器资源耗费:大文件上传也会占用服务器的资源,特别是在分布式上传时,需求考虑服务器负载均衡等问题,而且合理运用缓存技能,削减服务器压力。
- 安全问题:如果数据传输过程中呈现安全问题,或许会导致数据走漏或许攻击。因而,在上传过程中需求选用加密、签名等技能,确保数据的安全性。
- 兼容性问题:在不同的浏览器和操作系统上,或许会存在兼容性问题,需求进行充分的测验和兼容性处理。
- 上传速度慢:当文件较大时,上传速度或许会很慢,影响用户的体会。能够选用分片上传和分布式上传等方法进步上传速度。
总之,大文件上传是一个比较复杂的后端开发问题,需求充分考虑数据安全性、上传速度、网络稳定性、兼容性等方面的因素,一起也需求前端工程师结合详细事务需求,进行灵敏的完成计划。
尽管有的当地逻辑不正确,不能开箱即用。但随着持续进化,这些问题也许最终都不再是问题。话说回来,关于gpt
写的这份代码,能打几分呢?