feat(ecommerce): use FormData binary upload instead of base64 dataUrl
- Add uploadAssetBinary method to aiGenerationClient (FormData + busboy) - Replace base64 dataUrl upload in uploadProductImages with direct blob upload via /oss/upload-binary multipart endpoint - This eliminates the DATA_URL_PATTERN regex parsing bug that produced 44-byte corrupt files on OSS, causing DashScope "image format illegal" errors Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -403,6 +403,22 @@ export const aiGenerationClient = {
|
|||||||
return readJsonResponse<{ url: string; ossKey?: string }>(res, "Asset upload response failed");
|
return readJsonResponse<{ url: string; ossKey?: string }>(res, "Asset upload response failed");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async uploadAssetBinary(blob: Blob, options?: { name?: string; mimeType?: string; scope?: string }): Promise<{ url: string; signedUrl?: string; ossKey?: string }> {
|
||||||
|
const form = new FormData();
|
||||||
|
form.append("file", blob, options?.name || "upload.png");
|
||||||
|
if (options?.scope) form.append("scope", options.scope);
|
||||||
|
if (options?.mimeType) form.append("mimeType", options.mimeType);
|
||||||
|
const res = await fetch(buildApiUrl("oss/upload-binary"), {
|
||||||
|
method: "POST",
|
||||||
|
headers: { ...buildAuthHeaders() },
|
||||||
|
body: form,
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
await throwResponseError(res, "Binary asset upload failed");
|
||||||
|
}
|
||||||
|
return readJsonResponse<{ url: string; ossKey?: string }>(res, "Binary asset upload response failed");
|
||||||
|
},
|
||||||
|
|
||||||
async uploadAssetByUrl(input: UploadAssetByUrlInput): Promise<{ url: string; signedUrl?: string; ossKey?: string }> {
|
async uploadAssetByUrl(input: UploadAssetByUrlInput): Promise<{ url: string; signedUrl?: string; ossKey?: string }> {
|
||||||
const res = await fetch(buildApiUrl("oss/upload-by-url"), {
|
const res = await fetch(buildApiUrl("oss/upload-by-url"), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
@@ -1326,15 +1326,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
for (const item of productImages) {
|
for (const item of productImages) {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(item.src);
|
const resp = await fetch(item.src);
|
||||||
const blob = await resp.blob();
|
const rawBlob = await resp.blob();
|
||||||
const mimeType = SUPPORTED_IMAGE_TYPES.has(blob.type) ? blob.type : "image/png";
|
const mimeType = SUPPORTED_IMAGE_TYPES.has(rawBlob.type) ? rawBlob.type : "image/png";
|
||||||
const dataUrl = await new Promise<string>((resolve, reject) => {
|
const blob = rawBlob.type === mimeType ? rawBlob : new Blob([rawBlob], { type: mimeType });
|
||||||
const reader = new FileReader();
|
const { url } = await aiGenerationClient.uploadAssetBinary(blob, { name: item.name, mimeType, scope: "ecommerce-product" });
|
||||||
reader.onload = () => resolve(String(reader.result));
|
|
||||||
reader.onerror = () => reject(reader.error);
|
|
||||||
reader.readAsDataURL(blob);
|
|
||||||
});
|
|
||||||
const { url } = await aiGenerationClient.uploadAsset({ dataUrl, name: item.name, mimeType });
|
|
||||||
urls.push(url);
|
urls.push(url);
|
||||||
} catch {
|
} catch {
|
||||||
// skip images that fail to upload
|
// skip images that fail to upload
|
||||||
|
|||||||
Reference in New Issue
Block a user