From 86bdc7089b8301495573868d9936bf2b47cb8960 Mon Sep 17 00:00:00 2001 From: jinyaqiu Date: Mon, 14 Jul 2025 18:23:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B9=B6=E5=8F=91=E4=B8=BA3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/file-upload/backgroundUploader.ts | 137 +++++++++++++------ 1 file changed, 98 insertions(+), 39 deletions(-) diff --git a/components/file-upload/backgroundUploader.ts b/components/file-upload/backgroundUploader.ts index 368596b..11097b0 100644 --- a/components/file-upload/backgroundUploader.ts +++ b/components/file-upload/backgroundUploader.ts @@ -3,10 +3,12 @@ import * as BackgroundFetch from 'expo-background-fetch'; import * as ImageManipulator from 'expo-image-manipulator'; import * as MediaLibrary from 'expo-media-library'; import * as TaskManager from 'expo-task-manager'; +import pLimit from 'p-limit'; import { Alert } from 'react-native'; const BACKGROUND_UPLOAD_TASK = 'background-upload-task'; - +// 设置最大并发数 +const CONCURRENCY_LIMIT = 3; // 同时最多上传3个文件 // 获取指定时间范围内的媒体文件 export const getMediaByDateRange = async (startDate: Date, endDate: Date) => { try { @@ -107,25 +109,59 @@ const addMaterial = async (file: string, compressFile: string) => { // 上传文件到URL const uploadFile = async (file: File, uploadUrl: string): Promise => { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open('PUT', uploadUrl); - xhr.setRequestHeader('Content-Type', file.type); - - xhr.onload = () => { - if (xhr.status >= 200 && xhr.status < 300) { - resolve(); - } else { - reject(new Error(`Upload failed with status ${xhr.status}`)); - } - }; - - xhr.onerror = () => { - reject(new Error('Network error during upload')); - }; - - xhr.send(file); + console.log('=== Starting file upload ==='); + console.log('File info:', { + name: file.name, + type: file.type, + size: file.size, + lastModified: file.lastModified }); + console.log('Upload URL:', uploadUrl); + + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => { + console.log('Upload timeout triggered'); + controller.abort(); + }, 30000); + + console.log('Sending upload request...'); + const startTime = Date.now(); + + const response = await fetch(uploadUrl, { + method: 'PUT', + headers: { + 'Content-Type': file.type, + 'x-oss-forbid-overwrite': 'true' + }, + body: file, + signal: controller.signal + }); + + clearTimeout(timeoutId); + const endTime = Date.now(); + + console.log(`Upload completed in ${endTime - startTime}ms`, { + status: response.status, + statusText: response.statusText, + headers: Object.fromEntries(response.headers.entries()) + }); + + if (!response.ok) { + const errorText = await response.text().catch(() => ''); + console.error('Upload failed with response:', errorText); + throw new Error(`Upload failed: ${response.status} ${response.statusText}\n${errorText}`); + } + + console.log('Upload successful'); + } catch (error: any) { + console.error('Upload error details:', { + name: error.name, + message: error.message, + stack: error.stack + }); + throw error; + } }; // 检查并请求媒体库权限 @@ -177,11 +213,7 @@ const processMediaUpload = async (asset: MediaLibrary.Asset) => { const mimeType = isVideo ? 'video/mp4' : 'image/jpeg'; const file = new File([blob], filename, { type: mimeType }); - console.log("Original file prepared for upload:", { - name: file.name, - size: file.size, - type: file.type - }); + console.log("Original file prepared for upload:", file); // 获取上传URL并上传原始文件 const { upload_url, file_id } = await getUploadUrl(file, { @@ -212,6 +244,7 @@ const processMediaUpload = async (asset: MediaLibrary.Asset) => { size: compressedFile.size, type: compressedFile.type }); + console.log("compressedFile", compressedFile); // 获取上传URL并上传压缩文件 const { upload_url, file_id } = await getUploadUrl(compressedFile, { @@ -248,7 +281,7 @@ const processMediaUpload = async (asset: MediaLibrary.Asset) => { compressed: compressedResult.file_id } }; - } catch (error) { + } catch (error: any) { console.error('Error in processMediaUpload:', error); if (error.message === 'No media library permission') { throw error; @@ -294,7 +327,6 @@ TaskManager.defineTask(BACKGROUND_UPLOAD_TASK, async () => { const { hasPermission } = await checkMediaLibraryPermission(); if (!hasPermission) { console.log('Media library permission not granted'); - // 返回 NoData 而不是 Failed,这样系统可能会在稍后重试 return BackgroundFetch.BackgroundFetchResult.NoData; } @@ -312,22 +344,49 @@ TaskManager.defineTask(BACKGROUND_UPLOAD_TASK, async () => { console.log(`Found ${media.length} media items to process`); - // 处理每个媒体文件 - for (const item of media) { - try { - await processMediaUpload(item); - } catch (error) { - console.error(`Error processing media ${item.id}:`, error); - // 如果是权限错误,直接返回失败 - if (error.message === 'No media library permission') { - return BackgroundFetch.BackgroundFetchResult.Failed; + // 创建并发限制器 + const limit = pLimit(CONCURRENCY_LIMIT); + + // 准备所有上传任务 + const uploadPromises = media.map(item => + limit(async () => { + try { + await processMediaUpload(item); + return { success: true, id: item.id }; + } catch (error: any) { + console.error(`Error processing media ${item.id}:`, error); + if (error.message === 'No media library permission') { + throw error; // 权限错误直接抛出 + } + return { success: false, id: item.id, error }; } - // 其他错误继续处理下一个文件 - } + }) + ); + + // 等待所有上传任务完成 + const results = await Promise.allSettled(uploadPromises); + + // 统计结果 + const succeeded = results.filter(r => + r.status === 'fulfilled' && r.value.success + ).length; + const failed = results.length - succeeded; + + console.log(`Background upload task completed. Success: ${succeeded}, Failed: ${failed}`); + + // 如果有权限错误,返回失败 + const hasPermissionError = results.some(r => + r.status === 'rejected' || + (r.status === 'fulfilled' && r.value.error?.message === 'No media library permission') + ); + + if (hasPermissionError) { + return BackgroundFetch.BackgroundFetchResult.Failed; } - console.log('Background upload task completed'); - return BackgroundFetch.BackgroundFetchResult.NewData; + return succeeded > 0 ? + BackgroundFetch.BackgroundFetchResult.NewData : + BackgroundFetch.BackgroundFetchResult.NoData; } catch (error) { console.error('Error in background upload task:', error); return BackgroundFetch.BackgroundFetchResult.Failed;