有这么一个需求:需要上传几千张图片到后端接口 使用 python 的时候,可以线程池来实现并发控制 import os from loguru import logger from pathlib import Path from PIL import Image from mark import BASE_DIR from PIL import UnidentifiedImageError import requests from concurrent.futures.thread import ThreadPoolExecutor pool = ThreadPoolExecutor(10) images_dir: Path = Path( '/Volumes/MyPassport/iv测试图片集') def get_file_size(file_path: Path) -> int: return os.path.getsize(file_path) formats = ('jpg', 'jpeg', 'png') images = [images_dir / f for f in os.listdir(images_dir) if f.endswith(formats) and not f.startswith('.')] images.sort() logger.debug(f'一共有 {len(images)} 个图片') def func(index: int, image: Image): try: logger.debug(f'{index}/{len(images)-1} {image.name}') with open(image, 'rb') as file: response = requests.post( 'http://127.0.0.1:6200/meta/image/file', files={ 'file': file }, data={ 'network': 'vgg16' }) assert response.status_code == 200 except (UnidentifiedImageError, OSError) as error: logger.warning(error) os.remove(image) for index, image in enumerate(images): pool.submit(func, index, image) pool.shutdown(wait=True) 但是使用 vue+js 的时候,我不知道可以怎么操作 我用 vue+js 写的函数如下 const handleUpload = () => { fileList_1.value.forEach((item, index) => { const formData = new FormData(); formData.append("file", item.originFileObj); console.log(`第 ${index + 1} 个文件上传`, item.name); axios .post("/api/meta/image/file", formData) .then((response) => { if (response.status === 500) { console.error(`第 ${index + 1} 个文件上传失败:`, response.data); message.error(response.data.message); } else { responseBody.value = response.data; responseData.value = response.data; message.success(item.name + " 上传成功", 2); } }) .catch((error) => { console.error('捕捉到错误了', error); message.error(error.response.data.message); }); }) }; 这个函数有一个问题,当在浏览器同时选中 1000 个图片,然后点击上传的时候,貌似会同时发出 1000 个 HTTP 请求,这个是非常危险的,而且会让浏览器变得非常的卡顿 我希望可以实现:不管需要上传多少个文件,并发上传数不超过 10 我的 vue+js 完整代码如下 使用说明: 选择图片 评分范围在 0-100 分,100 分是满分。如果图片相似度太小,会出现负分 --> 选择图片 上传 {{ item.hash_code }} import { PlusOutlined, LoadingOutlined } from "@ant-design/icons-vue"; import { ref, reactive } from "vue"; import { message } from "ant-design-vue"; import { UploadOutlined } from "@ant-design/icons-vue"; import axios from "axios"; import { onMounted } from "vue"; import router from "@/router"; onMounted(async () => { document.title = "图片录入"; // 设置浏览器标签页的标题 }); function getBase64(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result); reader.onerror = (error) => reject(error); }); } const fileList_1 = ref([]); const handleBeforeUpload1 = (file) => { // fileList_1.value.splice(0); return false; // 阻止默认上传行为 }; const previewVisible = ref(false); const previewImage = ref(""); const previewTitle = ref(""); const handleCancel = () => { previewVisible.value = false; previewTitle.value = ""; }; const handlePreview = async (file) => { if (!file.url && !file.preview) { file.preview = await getBase64(file.originFileObj); } previewImage.value = file.url || file.preview; previewVisible.value = true; previewTitle.value = file.name || file.url.substring(file.url.lastIndexOf("/") + 1); }; const responseBody = ref(null); const responseData = ref([]); const handleUpload = () => { fileList_1.value.forEach((item, index) => { const formData = new FormData(); formData.append("file", item.originFileObj); console.log(`第 ${index + 1} 个文件上传`, item.name); axios .post("/api/meta/image/file", formData) .then((response) => { if (response.status === 500) { console.error(`第 ${index + 1} 个文件上传失败:`, response.data); message.error(response.data.message); } else { responseBody.value = response.data; responseData.value = response.data; message.success(item.name + " 上传成功", 2); } }) .catch((error) => { console.error('捕捉到错误了', error); message.error(error.response.data.message); }); }) }; const navigateToRoot = () => { router.push("/"); }; body { background-color: #e9ecef !important; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .gray-placeholder { color: gray; } .container { margin: 0 auto; /* 居中显示 */ margin-top: 20px; max-width: 1440px; /* 设置最大宽度为900px */ background-color: #ffffff; /* 浅灰色 */ border-radius: 0.25rem; } .container-item { padding: 25px; border-width: 0 0 1px; margin-bottom: 20px; } .theme-icon { width: 64px; /* 设置图标的宽度 */ height: 64px; /* 设置图标的高度 */ } .avatar-uploader>.ant-upload { width: 128px; height: 128px; } .ant-upload-select-picture-card i { font-size: 32px; color: #999; } .ant-upload-select-picture-card .ant-upload-text { margin-top: 8px; color: #666; }