react antd upload custom request处理多个文件上传的问题
背景:第一次请求需要请求后端返回aws 一个link,再往link push文件,再调用另一个接口告诉后端已经上传成功,拿到返回值。 再把返回值传给业务api... 多文件上传一直是循环触发custom request,并且文件上传完之后,需要利用websocket实时更改页面文件的状态
// Upload
interface BotFile {
botFileId: string;
url: string;
botFileKey: string;
openaiFileId: string | null;
type: number;
fileUId: string;
}
const [defaultFileList, setDefaultFileList] = useState([]);
// current uploaded file (antd Upload OnChange)
const [currentUploadFileList, setCurrentUploadFileList] = useState<RcFile[]>([]);
// aws completed upload files
const [uploadedFileList, setUploadedFileList] = useState<BotFile[]>([]);
const uploadFilesProps = (type: any) => {
const allowedFileTypes = [
'.txt',
'.docx',
'.pdf',
'.md',
'.csv',
'.json',
'.xlsx',
'.xls',
'.jpg',
'.jpeg',
'.png',
];
const maxTotalSize = 500 * 1024 * 1024;
return {
name: 'file',
multiple: true,
listType: 'picture',
directory: type,
showUploadList: false,
action: '/api/sapien-storage/v1/file/frontEndUploads',
data: { tenantId: currentUser?.tenantId, type: 'LOCAL_FILE' },
headers: {
Authorization: `Bearer ${getToken()}`,
'api-pass-key': getRasKey(),
},
fileList: defaultFileList,
accept: allowedFileTypes.join(','),
beforeUpload(file: RcFile, fileList2: RcFile[]) {
setShowCard(true);
const ext = file.name.slice(((file.name.lastIndexOf('.') - 1) >>> 0) + 1).toLowerCase();
if (!allowedFileTypes.includes(ext)) {
message.error(file.name + ' Unsupported file format.');
return Upload.LIST_IGNORE;
}
if (file.size === 0) {
updateUploadStatus(file, 'error', 'File cannot be empty');
return Upload.LIST_IGNORE;
}
if (ext === '.xlsx' && file.size > 3145728) {
updateUploadStatus(file, 'error', 'Max 3MB');
return Upload.LIST_IGNORE;
} else if (
(ext === '.jpg' || ext === '.jpeg' || ext === '.png') &&
file.size > 10 * 1024 * 1024
) {
updateUploadStatus(file, 'error', 'Max 10MB');
return Upload.LIST_IGNORE;
} else if (file.size > 52428800) {
updateUploadStatus(file, 'error', 'Max 50MB');
return Upload.LIST_IGNORE;
}
let currentTotalSize = 0;
let currentUploadFileSize = 0;
fileList2.forEach((item) => {
currentUploadFileSize += item.size || 0;
});
defaultFileList.forEach((item) => {
currentTotalSize += item.size || 0;
});
if (currentTotalSize + currentUploadFileSize > maxTotalSize) {
if (limitMsg) {
updateUploadStatus(file, 'error', 'Max 500MB');
setLimitMsg(false);
setTimeout(() => {
setLimitMsg(true);
}, 1000);
}
return Upload.LIST_IGNORE;
}
},
onProgress: (progressEvent: any, file: any) => {
const percent = Math.floor((progressEvent.loaded / progressEvent.total) * 100);
setUploadStatus((prevStatus) => {
const newStatus: any = [...prevStatus];
const fileIndex = newStatus.findIndex((item: any) => item.uid === file.uid);
if (fileIndex !== -1) {
newStatus[fileIndex].status = 'uploading';
newStatus[fileIndex].percent = percent;
} else {
newStatus.push({
uid: file.uid,
name: file.name,
status: 'uploading',
percent: percent,
size: file.size,
});
}
return newStatus;
});
},
onChange(info: any) {
if (info.file.status === 'done') {
if (info.file.response.code === '1001') {
setPricingPlanContent(info.file.response.message);
setPricingPlanModalVisible(true);
setUploadStatus(info.fileList.filter((item: any) => item.uid !== info.file.uid));
}
setCurrentUploadFileList((prevList) => [...prevList, info.file]);
}
setDefaultFileList(info.fileList);
if (fileListContainerRef.current) {
fileListContainerRef.current.scrollTop = fileListContainerRef.current.scrollHeight;
}
},
customRequest(options: any) {
const { file, onSuccess, onError, onProgress } = options;
const formData = {
fileInfos: [
{
fileName: file.name,
contentType: file.type,
length: file.size.toString(),
},
],
fileConstants: 'LOCAL_FILE',
};
const xhr = new XMLHttpRequest();
setUploadRequests((prevRequests) => {
const newRequests = new Map(prevRequests);
newRequests.set(file.uid, xhr);
return newRequests;
});
xhr.upload.onprogress = (event) => {
const percent = Math.floor((event.loaded / event.total) * 100);
onProgress({ percent }, file);
};
xhr.onload = () => {
if (xhr.status < 200 || xhr.status >= 300) {
onError(new Error('Upload error'));
setUploadStatus((prevStatus) => {
const newStatus = prevStatus.map((item: any) => {
if (item.uid === file.uid) {
return { ...item, status: 'error' };
}
return item;
});
return newStatus;
});
return;
}
const response = JSON.parse(xhr.responseText);
const uploadResData = response.data[0];
const awsUrl = uploadResData.link;
const awsXhr = new XMLHttpRequest();
setUploadRequests((prevRequests) => {
const newRequests = new Map(prevRequests);
newRequests.set(file.uid, awsXhr);
return newRequests;
});
awsXhr.upload.onprogress = (event) => {
const percent = Math.floor((event.loaded / event.total) * 100);
onProgress({ percent }, file);
};
awsXhr.onload = () => {
if (awsXhr.status < 200 || awsXhr.status >= 300) {
onError(new Error('AWS Upload error'));
setUploadStatus((prevStatus) => {
const newStatus = prevStatus.map((item: any) => {
if (item.uid === file.uid) {
return { ...item, status: 'error' };
}
return item;
});
return newStatus;
});
return;
}
if (awsXhr.status === 200) {
const awsCompletesFileUploadXhr = new XMLHttpRequest();
setUploadRequests((prevRequests) => {
const newRequests = new Map(prevRequests);
newRequests.set(file.uid, awsCompletesFileUploadXhr);
return newRequests;
});
const awsCompletesFileUploadFormData = {
fileInfos: response.data,
fileConstants: 'LOCAL_FILE',
};
awsCompletesFileUploadXhr.upload.onprogress = (event) => {
const percent = Math.floor((event.loaded / event.total) * 100);
onProgress({ percent }, file);
};
awsCompletesFileUploadXhr.open(
'POST',
'/api/sapien-storage/v1/file/awsCompletesFileUpload',
true,
);
awsCompletesFileUploadXhr.onload = () => {
if (xhr.status < 200 || xhr.status >= 300) {
onError(new Error('Upload error'));
setUploadStatus((prevStatus) => {
const newStatus = prevStatus.map((item: any) => {
if (item.uid === file.uid) {
return { ...item, status: 'error' };
}
return item;
});
return newStatus;
});
return;
}
const awsXhrResponse: API.awsCompletedResponse = JSON.parse(
awsCompletesFileUploadXhr.responseText,
);
if (awsXhrResponse && awsXhrResponse.success) {
const awsResData = awsXhrResponse.data[0];
const paramData = {
botFileId: awsResData.id,
url: awsResData.link,
botFileKey: awsResData.name,
openaiFileId: awsResData.openaiFileId,
type: 1,
fileUId: file.uid,
};
setUploadedFileList((prevList) => [...prevList, paramData]);
setUploadStatus((prevStatus) => {
const newStatus = prevStatus.map((item: any) => {
if (item.uid === file.uid) {
return { ...item, status: 'success', percent: 100 };
}
return item;
});
return newStatus;
});
}
};
awsCompletesFileUploadXhr.onerror = () => {
onError(new Error('AWS Completion error'));
setUploadStatus((prevStatus) => {
const newStatus = prevStatus.map((item: any) => {
if (item.uid === file.uid) {
return { ...item, status: 'error' };
}
return item;
});
return newStatus;
});
};
awsCompletesFileUploadXhr.setRequestHeader('Content-Type', 'application/json');
awsCompletesFileUploadXhr.setRequestHeader('Authorization', `Bearer ${getToken()}`);
const rasKey = getRasKey();
if (typeof rasKey === 'string') {
awsCompletesFileUploadXhr.setRequestHeader('api-pass-key', rasKey);
}
awsCompletesFileUploadXhr.send(JSON.stringify(awsCompletesFileUploadFormData));
}
onSuccess(response);
};
awsXhr.onerror = () => {
onError(new Error('AWS Upload error'));
setUploadStatus((prevStatus) => {
const newStatus = prevStatus.map((item: any) => {
if (item.uid === file.uid) {
return { ...item, status: 'error' };
}
return item;
});
return newStatus;
});
};
awsXhr.open('PUT', awsUrl, true);
awsXhr.setRequestHeader('Content-Type', file.type);
awsXhr.send(file);
};
xhr.onerror = () => {
onError(new Error('Upload error'));
setUploadStatus((prevStatus) => {
const newStatus = prevStatus.map((item: any) => {
if (item.uid === file.uid) {
return { ...item, status: 'error' };
}
return item;
});
return newStatus;
});
};
xhr.open('POST', '/api/sapien-storage/v1/file/frontEndUploads', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Authorization', `Bearer ${getToken()}`);
const rasKey = getRasKey();
if (typeof rasKey === 'string') {
xhr.setRequestHeader('api-pass-key', rasKey);
}
xhr.send(JSON.stringify(formData));
},
};
};
useEffect(() => {
if (currentUploadFileList.length === uploadedFileList.length) {
updateFileList(uploadedFileList);
}
}, [uploadedFileList]);
// upload close
const interruptUpload = (file: any) => {
const xhr = uploadRequests.get(file.uid);
if (xhr) {
xhr.abort();
setUploadStatus((prevStatus) => {
const newStatus: any = [...prevStatus];
const fileIndex = newStatus.findIndex((item: any) => item.uid === file.uid);
if (fileIndex !== -1) {
newStatus[fileIndex].status = 'suspend';
newStatus[fileIndex].percent = 0;
}
return newStatus;
});
setUploadRequests((prevRequests) => {
const newRequests = new Map(prevRequests);
newRequests.delete(file.uid);
return newRequests;
});
setDefaultFileList((prevFileList: any) => {
const newFileList = prevFileList.map((item: any) => {
if (item.uid === file.uid) {
return { ...item, status: 'error' };
}
return item;
});
return newFileList;
});
const newFileList = defaultFileList.map((item: any) => {
if (item.uid === file.uid) {
return { ...item, status: 'error' };
}
return item;
});
updateFileList(newFileList);
}
};
const deleteUpload = (file: any) => {
const index = uploadStatus.findIndex((item) => item === file);
if (index > -1) {
const newUploadStatus = [...uploadStatus];
newUploadStatus.splice(index, 1);
setUploadStatus(newUploadStatus);
}
};
// Upload End
onChange 里面setDefaultFileList就是把上传的文件列表放里面,方便之后对比文件上传的数量
setUploadRequests 方法是我用来close 上传用的,用不到请忽略
setUploadStatus 方法是我用来展示上传的状态用的,用不到可以忽略
下面这个代码就是我组装已经上传完的数据,之后用来作对比的
const paramData = {
botFileId: awsResData.id,
url: awsResData.link,
botFileKey: awsResData.name,
openaiFileId: awsResData.openaiFileId,
type: 1,
fileUId: file.uid,
};
setUploadedFileList((prevList) => [...prevList, paramData]);
比较长度,如果一致,那就走上传的逻辑
useEffect(() => {
if (currentUploadFileList.length === uploadedFileList.length) {
updateFileList(uploadedFileList);
}
}, [uploadedFileList]);
七八个前端经手了,到我这里我也不知道该咋样了,反正最后是实现了