将 Blob 反应为通过节点端点上传文件 - 文件已损坏
React Blob to File upload via Node endpoint - file is corrupt
我已经构建了一个 Slack 风格的头像图像上传和裁剪功能,但我无法在不损坏的情况下保存裁剪后的图像。使用相同端点和方法的原始文件上传工作正常。这是从 Blob 手动创建的裁剪副本,它总是损坏的。
步骤非常简单:
- Select 并上传图像文件
- 裁剪(react-image-crop)出现
- Select 区域,点击保存
第一步,文件输入改变时上传文件。文件被发送到流端点,该端点将文件上传到 s3 存储桶。
<button type="button">Upload</button>
<input
type="file"
accept="image/*"
onChange={onFileChange}
/>
...
const onFileChange = async e => {
e.preventDefault();
let uploadedFile = e.target.files[0];
await onSave(uploadedFile);
};
uploadedFile 变量是从输入控件返回的文件对象。这很好用!还没有问题。
在第 3 步中,一旦您选择了图像的一个区域,就会通过 react-image-crop 生成一个 Blob。
const getCroppedImage = (source, config) => {
const canvas = document.createElement("canvas");
const scaleX = source.naturalWidth / source.width;
const scaleY = source.naturalHeight / source.height;
canvas.width = config.width;
canvas.height = config.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(
source,
config.x * scaleX,
config.y * scaleY,
config.width * scaleX,
config.height * scaleY,
0,
0,
config.width,
config.height
);
let mimeType = mime.lookup(userProfile.image_file.split(".").at(-1));
return new Promise((resolve, reject) => {
canvas.toBlob(blob => {
if (!blob) {
reject(new Error("Canvas is empty"));
return;
}
resolve(blob); //***THIS BLOB...
}, mimeType);
});
};
这个Blob是有效的,因为我在保存之前在屏幕上显示了选择的区域:
const AvatarPreview = () => {
if (activeAvatar) {
return <ImageCropper imageToCrop={activeAvatar} onImageCropped={onImageCropped} />;
}
return <Icon icon="bi:person" />;
};
我将 react-image-crop 生成的 Blob 填充到一个 File 对象中,因为这是我的代码所期望的,就像第 1 步一样。
const onImageCropped = croppedBlob => { //***IS PASSED IN HERE
let croppedImg = URL.createObjectURL(croppedBlob);
setActiveAvatar(croppedImg);
const reader = new FileReader();
reader.readAsDataURL(croppedBlob);
reader.addEventListener("load", () => {
let { result } = reader;
let resultMimeType = result.split(";")[0].split(":")[1];
let croppedFile = new File([result], userProfile.image_file, { type: resultMimeType }); //***NEW File object, from Blob
setCroppedAvatar(croppedFile);
}, false);
};
<button type="button" onClick={() => onSave(croppedAvatar)}>Save</button>
上面FileReader加载的“结果”是base64图片数据:
'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAAHRyWFlaAAABZAAAABRnWFlaAAABeAAAABRiWFlaAAABjAAAABRyVFJDAAABoAAAAChnVFJDAAABoAAAAChiVFJDAAABoAAAACh3dHB0AAAByAAAABRjcHJ0AAAB3AAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAFgAAAAcAHMAUgBHAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…QCMFWeFZ9u3fPsWY3hh9uVHWllbSwtGASrIOQc1t6rt0OBqG6x7aVfdY859pnIwfDDhCfpwBUyaSus4VZO+susGGM60zrB1TuEjx5Ov76FBKUgNTltIQlP4UpQghKQMAAAADHApt6lv1x1HeHrzdny9KkJbDrpHLhQhKNx91EJBJ9SSaQkLS+4HTwvalKvmQMZ/PFa8hBCRntnirYUDqVYtEWlQ8JYJSrv8vnTy6W2h5/qBYmCgqbcmtgLSMg+YUyYD4aUUqTlJ4I+td+FLethRhai0sgoUOCg+hFPBwcxjruUie1k/VDOmenKkIcS2iJCzyeAAmvF7qjqFzVWurzfHV7jJluKBJ/hBwP6VL1u+KvqlD0lL0VeLt+2rZLjmO25MyqSwNuPK7nKh8l7vlioFejqU8tx453EnIPcmrWrvW5VFf6zK0GifTWs1n6TRoUrIaDZBSeDSVUzyMTY7gBIORR/EcXwTnFEozfc0xVwOYT/2Q=='
新的 File 对象对我来说似乎是合法的:
然后我再次通过端点发送图像,并上传。它出现在存储桶中,文件大小似乎是合法的(不是像断流所指示的 0KB。)
但是,在下载并尝试打开文件时,文件已损坏。我想我在某处遗漏了一个选项......一些小的调整可以使这项工作?文件对象的格式不正确吗?我该如何进一步解决这个问题?
已修复。当然,只需一行代码即可使一切正常。我所要做的就是改变 FileReader 加载结果的方式:
const onImageCropped = async croppedBlob => {
let croppedImg = URL.createObjectURL(croppedBlob);
let mimeType = croppedBlob.type;
setActiveAvatar(croppedImg);
let reader = new FileReader();
reader.readAsArrayBuffer(croppedBlob); //Fixed the issue
reader.addEventListener("load", () => {
let { result } = reader;
let croppedFile = new File([result], userProfile.image_file, { type: mimeType });
setCroppedAvatar(croppedFile);
}, false);
};
无论如何,结果更优雅。改为使用 FileReader 的 readAsArrayBuffer。我猜在使用 readAsDataUrl 时缓冲区中有额外的信息,破坏了图像。
我已经构建了一个 Slack 风格的头像图像上传和裁剪功能,但我无法在不损坏的情况下保存裁剪后的图像。使用相同端点和方法的原始文件上传工作正常。这是从 Blob 手动创建的裁剪副本,它总是损坏的。
步骤非常简单:
- Select 并上传图像文件
- 裁剪(react-image-crop)出现
- Select 区域,点击保存
第一步,文件输入改变时上传文件。文件被发送到流端点,该端点将文件上传到 s3 存储桶。
<button type="button">Upload</button>
<input
type="file"
accept="image/*"
onChange={onFileChange}
/>
...
const onFileChange = async e => {
e.preventDefault();
let uploadedFile = e.target.files[0];
await onSave(uploadedFile);
};
uploadedFile 变量是从输入控件返回的文件对象。这很好用!还没有问题。
在第 3 步中,一旦您选择了图像的一个区域,就会通过 react-image-crop 生成一个 Blob。
const getCroppedImage = (source, config) => {
const canvas = document.createElement("canvas");
const scaleX = source.naturalWidth / source.width;
const scaleY = source.naturalHeight / source.height;
canvas.width = config.width;
canvas.height = config.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(
source,
config.x * scaleX,
config.y * scaleY,
config.width * scaleX,
config.height * scaleY,
0,
0,
config.width,
config.height
);
let mimeType = mime.lookup(userProfile.image_file.split(".").at(-1));
return new Promise((resolve, reject) => {
canvas.toBlob(blob => {
if (!blob) {
reject(new Error("Canvas is empty"));
return;
}
resolve(blob); //***THIS BLOB...
}, mimeType);
});
};
这个Blob是有效的,因为我在保存之前在屏幕上显示了选择的区域:
const AvatarPreview = () => {
if (activeAvatar) {
return <ImageCropper imageToCrop={activeAvatar} onImageCropped={onImageCropped} />;
}
return <Icon icon="bi:person" />;
};
我将 react-image-crop 生成的 Blob 填充到一个 File 对象中,因为这是我的代码所期望的,就像第 1 步一样。
const onImageCropped = croppedBlob => { //***IS PASSED IN HERE
let croppedImg = URL.createObjectURL(croppedBlob);
setActiveAvatar(croppedImg);
const reader = new FileReader();
reader.readAsDataURL(croppedBlob);
reader.addEventListener("load", () => {
let { result } = reader;
let resultMimeType = result.split(";")[0].split(":")[1];
let croppedFile = new File([result], userProfile.image_file, { type: resultMimeType }); //***NEW File object, from Blob
setCroppedAvatar(croppedFile);
}, false);
};
<button type="button" onClick={() => onSave(croppedAvatar)}>Save</button>
上面FileReader加载的“结果”是base64图片数据:
'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAAHRyWFlaAAABZAAAABRnWFlaAAABeAAAABRiWFlaAAABjAAAABRyVFJDAAABoAAAAChnVFJDAAABoAAAAChiVFJDAAABoAAAACh3dHB0AAAByAAAABRjcHJ0AAAB3AAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAFgAAAAcAHMAUgBHAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…QCMFWeFZ9u3fPsWY3hh9uVHWllbSwtGASrIOQc1t6rt0OBqG6x7aVfdY859pnIwfDDhCfpwBUyaSus4VZO+susGGM60zrB1TuEjx5Ov76FBKUgNTltIQlP4UpQghKQMAAAADHApt6lv1x1HeHrzdny9KkJbDrpHLhQhKNx91EJBJ9SSaQkLS+4HTwvalKvmQMZ/PFa8hBCRntnirYUDqVYtEWlQ8JYJSrv8vnTy6W2h5/qBYmCgqbcmtgLSMg+YUyYD4aUUqTlJ4I+td+FLethRhai0sgoUOCg+hFPBwcxjruUie1k/VDOmenKkIcS2iJCzyeAAmvF7qjqFzVWurzfHV7jJluKBJ/hBwP6VL1u+KvqlD0lL0VeLt+2rZLjmO25MyqSwNuPK7nKh8l7vlioFejqU8tx453EnIPcmrWrvW5VFf6zK0GifTWs1n6TRoUrIaDZBSeDSVUzyMTY7gBIORR/EcXwTnFEozfc0xVwOYT/2Q=='
新的 File 对象对我来说似乎是合法的:
然后我再次通过端点发送图像,并上传。它出现在存储桶中,文件大小似乎是合法的(不是像断流所指示的 0KB。)
但是,在下载并尝试打开文件时,文件已损坏。我想我在某处遗漏了一个选项......一些小的调整可以使这项工作?文件对象的格式不正确吗?我该如何进一步解决这个问题?
已修复。当然,只需一行代码即可使一切正常。我所要做的就是改变 FileReader 加载结果的方式:
const onImageCropped = async croppedBlob => {
let croppedImg = URL.createObjectURL(croppedBlob);
let mimeType = croppedBlob.type;
setActiveAvatar(croppedImg);
let reader = new FileReader();
reader.readAsArrayBuffer(croppedBlob); //Fixed the issue
reader.addEventListener("load", () => {
let { result } = reader;
let croppedFile = new File([result], userProfile.image_file, { type: mimeType });
setCroppedAvatar(croppedFile);
}, false);
};
无论如何,结果更优雅。改为使用 FileReader 的 readAsArrayBuffer。我猜在使用 readAsDataUrl 时缓冲区中有额外的信息,破坏了图像。